mirror of https://github.com/wb2osz/direwolf.git
Version 1.0 - Initial commit
Changes to be committed: new file: .gitattributes new file: .gitignore new file: APRStt-Implementation-Notes.pdf new file: CHANGES.txt new file: LICENSE-dire-wolf.txt new file: LICENSE-other.txt new file: Makefile.linux new file: Makefile.win new file: Quick-Start-Guide-Windows.pdf new file: Raspberry-Pi-APRS.pdf new file: User-Guide.pdf new file: aclients.c new file: aprs_tt.c new file: aprs_tt.h new file: atest.c new file: audio.c new file: audio.h new file: audio_win.c new file: ax25_pad.c new file: ax25_pad.h new file: beacon.c new file: beacon.h new file: config.c new file: config.h new file: decode_aprs.c new file: decode_aprs.h new file: dedupe.c new file: dedupe.h new file: demod.c new file: demod.h new file: demod_9600.c new file: demod_9600.h new file: demod_afsk.c new file: demod_afsk.h new file: digipeater.c new file: digipeater.h new file: direwolf.c new file: direwolf.conf new file: direwolf.desktop new file: direwolf.h new file: dsp.c new file: dsp.h new file: dtmf.c new file: dtmf.h new file: dw-icon.ico new file: dw-icon.png new file: dw-icon.rc new file: dw-start.sh new file: dwgps.c new file: dwgps.h new file: encode_aprs.c new file: encode_aprs.h new file: fcs_calc.c new file: fcs_calc.h new file: fsk_demod_agc.h new file: fsk_demod_state.h new file: fsk_filters.h new file: fsk_gen_filter.h new file: gen_packets.c new file: gen_tone.c new file: gen_tone.h new file: hdlc_rec.c new file: hdlc_rec.h new file: hdlc_rec2.c new file: hdlc_rec2.h new file: hdlc_send.c new file: hdlc_send.h new file: igate.c new file: igate.h new file: kiss.c new file: kiss.h new file: kiss_frame.c new file: kiss_frame.h new file: kissnet.c new file: kissnet.h new file: latlong.c new file: latlong.h new file: ll2utm.c new file: misc/README-dire-wolf.txt new file: misc/strcasestr.c new file: misc/strsep.c new file: misc/strtok_r.c new file: morse.c new file: multi_modem.c new file: multi_modem.h new file: ptt.c new file: ptt.h new file: pttest.c new file: rdq.c new file: rdq.h new file: redecode.c new file: redecode.h new file: regex/COPYING new file: regex/INSTALL new file: regex/LICENSES new file: regex/NEWS new file: regex/README new file: regex/README-dire-wolf.txt new file: regex/re_comp.h new file: regex/regcomp.c new file: regex/regex.c new file: regex/regex.h new file: regex/regex_internal.c new file: regex/regex_internal.h new file: regex/regexec.c new file: rrbb.c new file: rrbb.h new file: server.c new file: server.h new file: symbols-new.txt new file: symbols.c new file: symbols.h new file: symbolsX.txt new file: textcolor.c new file: textcolor.h new file: tocalls.txt new file: tq.c new file: tq.h new file: tt_text.c new file: tt_text.h new file: tt_user.c new file: tt_user.h new file: tune.h new file: udp_test.c new file: utm/LatLong-UTMconversion.c new file: utm/LatLong-UTMconversion.h new file: utm/README.txt new file: utm/SwissGrid.cpp new file: utm/UTMConversions.cpp new file: utm/constants.h new file: utm2ll.c new file: version.h new file: xmit.c new file: xmit.h
This commit is contained in:
commit
8978f2de6c
|
@ -0,0 +1,17 @@
|
||||||
|
# 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
|
|
@ -0,0 +1,85 @@
|
||||||
|
# Custom
|
||||||
|
*.docx
|
||||||
|
z*
|
||||||
|
*.log
|
||||||
|
*bak*
|
||||||
|
*~
|
||||||
|
*.xlsx
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# 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
|
Binary file not shown.
|
@ -0,0 +1,167 @@
|
||||||
|
----------------
|
||||||
|
Revision history
|
||||||
|
----------------
|
||||||
|
|
||||||
|
|
||||||
|
-----------
|
||||||
|
Version 1.0a May 2014
|
||||||
|
-----------
|
||||||
|
|
||||||
|
* Bug fix:
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-----------
|
||||||
|
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
|
||||||
|
-----------
|
||||||
|
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
|
||||||
|
* Bugs Fixed:
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* 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
|
||||||
|
-----------
|
||||||
|
|
||||||
|
|
||||||
|
More error checking and messages for invalid APRS data.
|
||||||
|
|
||||||
|
|
||||||
|
-----------
|
||||||
|
Version 0.4
|
||||||
|
-----------
|
||||||
|
|
||||||
|
First general availability.
|
||||||
|
|
|
@ -0,0 +1,281 @@
|
||||||
|
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
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
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.
|
|
@ -0,0 +1,289 @@
|
||||||
|
#
|
||||||
|
# Makefile for Linux version of Dire Wolf.
|
||||||
|
#
|
||||||
|
|
||||||
|
all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients
|
||||||
|
|
||||||
|
CC = gcc
|
||||||
|
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# Let's look at impact of various optimization levels.
|
||||||
|
#
|
||||||
|
# Benchmark results with Ubuntu gcc version 4.6.3, 32 bit platform.
|
||||||
|
# Intel(R) Celeron(R) CPU 2.53GHz. Appears to have only 32 bit instructions.
|
||||||
|
#
|
||||||
|
# seconds options, comments
|
||||||
|
# ------ -----------------
|
||||||
|
# 123 -O2
|
||||||
|
# 128 -O3 Slower than -O2 ?
|
||||||
|
# 123 -Ofast (should be same as -O3 -ffastmath)
|
||||||
|
# 126 -Ofast -march=pentium
|
||||||
|
# 88 -Ofast -msse
|
||||||
|
# 108 -Ofast -march=pentium -msse
|
||||||
|
# 88 -Ofast -march=pentium3 (this implies -msse)
|
||||||
|
# 89 -Ofast -march=pentium3 -msse
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Raspberry Pi, ARM11 (ARMv6 + VFP2)
|
||||||
|
# gcc (Debian 4.6.3-14+rpi1) 4.6.3
|
||||||
|
#
|
||||||
|
# seconds options, comments
|
||||||
|
# ------ -----------------
|
||||||
|
# 1015 -O2
|
||||||
|
# 948 -O3
|
||||||
|
# 928 -Ofast
|
||||||
|
# 937 -Ofast -fmpu=vfp (worse, no option for vfp2)
|
||||||
|
#
|
||||||
|
# Are we getting any vectorizing?
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Release 0.9 added a new feature than can consume a lot of CPU
|
||||||
|
# power: multiple AFSK demodulators running in parallel.
|
||||||
|
# These spend a lot of time spinning around in little loops
|
||||||
|
# calculating the sums of products for the DSP filters.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
# Here we find out if the gcc compiler is generating code
|
||||||
|
# for the i386. If so, we add the option to assume we will
|
||||||
|
# have at least a Pentium 3 to run on.
|
||||||
|
#
|
||||||
|
# When generating code for the x86_64 target, it is automatically
|
||||||
|
# assumed that the SSE instructions are available.
|
||||||
|
#
|
||||||
|
# If you are using gcc version 4.6 or later, you might get a
|
||||||
|
# small improvement by using the new "-Ofast" option that is
|
||||||
|
# not available in older compilers.
|
||||||
|
# "-O3" is used here for compatibility with older compilers.
|
||||||
|
#
|
||||||
|
# You might also get some improvement by using "-march=native"
|
||||||
|
# to fine tune the application for your particular type of
|
||||||
|
# hardware.
|
||||||
|
#
|
||||||
|
# If you are planning to distribute the binary version to
|
||||||
|
# other people (in some ham radio software collection), avoid
|
||||||
|
# fine tuning it for your particular computer. It could
|
||||||
|
# cause compatibility issues for those with older computers.
|
||||||
|
#
|
||||||
|
|
||||||
|
arch := $(shell echo | gcc -E -dM - | grep __i386__)
|
||||||
|
|
||||||
|
ifneq ($(arch),)
|
||||||
|
CFLAGS := -DUSE_ALSA -O3 -march=pentium3 -pthread
|
||||||
|
else
|
||||||
|
CFLAGS := -DUSE_ALSA -O3 -pthread
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
# Uncomment following lines to enable GPS interface.
|
||||||
|
# DO NOT USE THIS. Still needs more work.
|
||||||
|
#CFLAGS += -DENABLE_GPS
|
||||||
|
#LDLIBS += -lgps
|
||||||
|
|
||||||
|
|
||||||
|
# Name of current directory.
|
||||||
|
# Used to generate zip file name for distribution.
|
||||||
|
|
||||||
|
z=$(notdir ${CURDIR})
|
||||||
|
|
||||||
|
|
||||||
|
# Main application.
|
||||||
|
|
||||||
|
direwolf : direwolf.o config.o demod.o dsp.o demod_afsk.o demod_9600.o hdlc_rec.o \
|
||||||
|
hdlc_rec2.o multi_modem.o redecode.o rdq.o rrbb.o \
|
||||||
|
fcs_calc.o ax25_pad.o \
|
||||||
|
decode_aprs.o symbols.o server.o kiss.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \
|
||||||
|
gen_tone.o audio.o digipeater.o dedupe.o tq.o xmit.o \
|
||||||
|
ptt.o beacon.o dwgps.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 \
|
||||||
|
utm.a
|
||||||
|
$(CC) $(CFLAGS) -o $@ $^ -lpthread -lrt -lasound $(LDLIBS) -lm
|
||||||
|
|
||||||
|
|
||||||
|
# Optimization for slow processors.
|
||||||
|
|
||||||
|
demod.o : fsk_fast_filter.h
|
||||||
|
|
||||||
|
demod_afsk.o : fsk_fast_filter.h
|
||||||
|
|
||||||
|
|
||||||
|
fsk_fast_filter.h : demod_afsk.c
|
||||||
|
$(CC) $(CFLAGS) -o gen_fff -DGEN_FFF demod_afsk.c dsp.c textcolor.c -lm
|
||||||
|
./gen_fff > fsk_fast_filter.h
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
utm.a : LatLong-UTMconversion.o
|
||||||
|
ar -cr $@ $^
|
||||||
|
|
||||||
|
LatLong-UTMconversion.o : utm/LatLong-UTMconversion.c
|
||||||
|
$(CC) $(CFLAGS) -c -o $@ $^
|
||||||
|
|
||||||
|
|
||||||
|
# Optional install step.
|
||||||
|
# TODO: Review file locations.
|
||||||
|
# TODO: Handle Linux variations correctly.
|
||||||
|
# The Raspberry Pi has ~/Desktop but Ubuntu does not.
|
||||||
|
# For now, just put reference to it at the end so only last step fails.
|
||||||
|
|
||||||
|
install : direwolf decode_aprs tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png direwolf.desktop
|
||||||
|
sudo install direwolf /usr/local/bin
|
||||||
|
sudo install decode_aprs /usr/local/bin
|
||||||
|
sudo install text2tt /usr/local/bin
|
||||||
|
sudo install tt2text /usr/local/bin
|
||||||
|
sudo install ll2utm /usr/local/bin
|
||||||
|
sudo install utm2ll /usr/local/bin
|
||||||
|
sudo install aclients /usr/local/bin
|
||||||
|
sudo install -D --mode=644 tocalls.txt /usr/share/direwolf/tocalls.txt
|
||||||
|
sudo install -D --mode=644 symbols-new.txt /usr/share/direwolf/symbols-new.txt
|
||||||
|
sudo install -D --mode=644 symbolsX.txt /usr/share/direwolf/symbolsX.txt
|
||||||
|
sudo install -D --mode=644 dw-icon.png /usr/share/direwolf/dw-icon.png
|
||||||
|
sudo install -D --mode=644 direwolf.desktop /usr/share/applications/direwolf.desktop
|
||||||
|
cp direwolf.conf ~
|
||||||
|
cp dw-start.sh ~
|
||||||
|
sudo install -D --mode=644 CHANGES.txt /usr/local/share/doc/direwolf/CHANGES.txt
|
||||||
|
sudo install -D --mode=644 LICENSE-dire-wolf.txt /usr/local/share/doc/direwolf/LICENSE-dire-wolf.txt
|
||||||
|
sudo install -D --mode=644 LICENSE-other.txt /usr/local/share/doc/direwolf/LICENSE-other.txt
|
||||||
|
sudo install -D --mode=644 User-Guide.pdf /usr/local/share/doc/direwolf/User-Guide.pdf
|
||||||
|
sudo install -D --mode=644 Raspberry-Pi-APRS.pdf /usr/local/share/doc/direwolf/Raspberry-Pi-APRS.pdf
|
||||||
|
sudo install -D --mode=644 APRStt-Implementation-Notes.pdf /usr/local/share/doc/direwolf/APRStt-Implementation-Notes.pdf
|
||||||
|
sudo install -D --mode=644 Quick-Start-Guide-Windows.pdf /usr/local/share/doc/direwolf/Quick-Start-Guide-Windows.pdf
|
||||||
|
ln -f -s /usr/share/applications/direwolf.desktop ~/Desktop/direwolf.desktop
|
||||||
|
|
||||||
|
|
||||||
|
# Separate application to decode raw data.
|
||||||
|
|
||||||
|
decode_aprs : decode_aprs.c symbols.c ax25_pad.c textcolor.c fcs_calc.c
|
||||||
|
$(CC) $(CFLAGS) -o decode_aprs -DTEST $^ -lm
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Convert between text and touch tone representation.
|
||||||
|
|
||||||
|
text2tt : tt_text.c
|
||||||
|
$(CC) $(CFLAGS) -DENC_MAIN -o text2tt tt_text.c
|
||||||
|
|
||||||
|
tt2text : tt_text.c
|
||||||
|
$(CC) $(CFLAGS) -DDEC_MAIN -o tt2text tt_text.c
|
||||||
|
|
||||||
|
|
||||||
|
# Convert between Latitude/Longitude and UTM coordinates.
|
||||||
|
|
||||||
|
ll2utm : ll2utm.c utm.a
|
||||||
|
$(CC) $(CFLAGS) -I utm -o $@ $^ -lm
|
||||||
|
|
||||||
|
utm2ll : utm2ll.c utm.a
|
||||||
|
$(CC) $(CFLAGS) -I utm -o $@ $^ -lm
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Test application to generate sound.
|
||||||
|
|
||||||
|
gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c textcolor.c
|
||||||
|
$(CC) $(CFLAGS) -o $@ $^ -lasound -lm
|
||||||
|
|
||||||
|
demod.o : tune.h
|
||||||
|
demod_afsk.o : tune.h
|
||||||
|
demod_9600.o : tune.h
|
||||||
|
|
||||||
|
testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o fcs_calc.c ax25_pad.c decode_aprs.c symbols.c tune.h textcolor.c
|
||||||
|
$(CC) $(CFLAGS) -o atest $^ -lm
|
||||||
|
./atest 02_Track_2.wav | grep "packets decoded in" > atest.out
|
||||||
|
|
||||||
|
|
||||||
|
# Unit test for AFSK demodulator
|
||||||
|
|
||||||
|
|
||||||
|
atest : 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 symbols.c textcolor.c
|
||||||
|
$(CC) $(CFLAGS) -o $@ $^ -lm
|
||||||
|
time ./atest ../direwolf-0.2/02_Track_2.wav
|
||||||
|
|
||||||
|
# Unit test for inner digipeater algorithm
|
||||||
|
|
||||||
|
|
||||||
|
dtest : digipeater.c ax25_pad.c dedupe.c fcs_calc.c tq.c textcolor.c
|
||||||
|
$(CC) $(CFLAGS) -DTEST -o $@ $^
|
||||||
|
./dtest
|
||||||
|
|
||||||
|
|
||||||
|
# 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 -lrt
|
||||||
|
./udptest
|
||||||
|
|
||||||
|
|
||||||
|
# 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 $@ $^
|
||||||
|
|
||||||
|
|
||||||
|
SRCS = direwolf.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c multi_modem.c fcs_calc.c ax25_pad.c decode_aprs.c symbols.c \
|
||||||
|
server.c kiss.c kissnet.c kiss_frame.c hdlc_send.c fcs_calc.c gen_tone.c audio.c \
|
||||||
|
digipeater.c dedupe.c tq.c xmit.c beacon.c encode_aprs.c latlong.c encode_aprs.c latlong.c
|
||||||
|
|
||||||
|
|
||||||
|
depend : $(SRCS)
|
||||||
|
makedepend $(INCLUDES) $^
|
||||||
|
|
||||||
|
|
||||||
|
clean :
|
||||||
|
rm -f direwolf decode_aprs text2tt tt2text ll2utm utm2ll fsk_fast_filter.h *.o *.a
|
||||||
|
echo " " > tune.h
|
||||||
|
|
||||||
|
|
||||||
|
# Package it up for distribution.
|
||||||
|
|
||||||
|
dist-src : CHANGES.txt User-Guide.pdf Quick-Start-Guide-Windows.pdf Raspberry-Pi-APRS.pdf \
|
||||||
|
direwolf.desktop dw-start.sh
|
||||||
|
rm -f fsk_fast_filter.h
|
||||||
|
echo " " > tune.h
|
||||||
|
rm -f ../$z-src.zip
|
||||||
|
(cd .. ; zip $z-src.zip $z/CHANGES.txt $z/LICENSE* \
|
||||||
|
$z/User-Guide.pdf $z/Quick-Start-Guide-Windows.pdf $z/Raspberry-Pi-APRS.pdf \
|
||||||
|
$z/Makefile* $z/*.c $z/*.h $z/regex/* $z/misc/* $z/utm/* \
|
||||||
|
$z/*.conf $z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \
|
||||||
|
$z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \
|
||||||
|
$z/direwolf.desktop $z/dw-start.sh )
|
||||||
|
|
||||||
|
|
||||||
|
#User-Guide.pdf : User-Guide.docx
|
||||||
|
# echo "***** User-Guide.pdf is out of date *****"
|
||||||
|
|
||||||
|
#Quick-Start-Guide-Windows.pdf : Quick-Start-Guide-Windows.docx
|
||||||
|
# echo "***** Quick-Start-Guide-Windows.pdf is out of date *****"
|
||||||
|
|
||||||
|
#Raspberry-Pi-APRS.pdf : Raspberry-Pi-APRS.docx
|
||||||
|
# echo "***** Raspberry-Pi-APRS.pdf is out of date *****"
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
|
@ -0,0 +1,326 @@
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
#TODO: put -Ofast back in.
|
||||||
|
|
||||||
|
CC = gcc
|
||||||
|
#CFLAGS = -g -Wall -Ofast -march=pentium3 -msse -Iregex -mthreads -DUSE_REGEX_STATIC
|
||||||
|
CFLAGS = -g -Wall -march=pentium3 -msse -Iregex -mthreads -DUSE_REGEX_STATIC
|
||||||
|
AR = ar
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
# Name of zip file for distribution.
|
||||||
|
|
||||||
|
z=$(notdir ${CURDIR})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Main application.
|
||||||
|
|
||||||
|
demod.o : fsk_demod_state.h
|
||||||
|
demod_9600.o : fsk_demod_state.h
|
||||||
|
demod_afsk.o : fsk_demod_state.h
|
||||||
|
|
||||||
|
|
||||||
|
direwolf : direwolf.o config.o demod.o dsp.o demod_afsk.o demod_9600.o hdlc_rec.o \
|
||||||
|
hdlc_rec2.o multi_modem.o redecode.o rdq.o rrbb.o \
|
||||||
|
fcs_calc.o ax25_pad.o \
|
||||||
|
decode_aprs.o symbols.o server.o kiss.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \
|
||||||
|
gen_tone.o audio_win.o digipeater.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 \
|
||||||
|
dw-icon.o regex.a misc.a utm.a
|
||||||
|
$(CC) $(CFLAGS) -g -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 : demod_afsk.c
|
||||||
|
$(CC) $(CFLAGS) -o gen_fff -DGEN_FFF demod_afsk.c dsp.c textcolor.c
|
||||||
|
./gen_fff > fsk_fast_filter.h
|
||||||
|
|
||||||
|
|
||||||
|
utm.a : LatLong-UTMconversion.o
|
||||||
|
ar -cr $@ $^
|
||||||
|
|
||||||
|
LatLong-UTMconversion.o : utm/LatLong-UTMconversion.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
|
||||||
|
#
|
||||||
|
|
||||||
|
regex.a : regex.o
|
||||||
|
ar -cr $@ $^
|
||||||
|
|
||||||
|
regex.o : regex/regex.c
|
||||||
|
$(CC) $(CFLAGS) -Dbool=int -Dtrue=1 -Dfalse=0 -c -o $@ $^
|
||||||
|
|
||||||
|
|
||||||
|
# There are also a couple other functions in the misc
|
||||||
|
# subdirectory that are missing on Windows.
|
||||||
|
|
||||||
|
misc.a : strsep.o strtok_r.o strcasestr.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 $@ $^
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Separate application to decode raw data.
|
||||||
|
|
||||||
|
decode_aprs : decode_aprs.c symbols.c ax25_pad.c textcolor.c fcs_calc.c regex.a misc.a
|
||||||
|
$(CC) $(CFLAGS) -o decode_aprs -DTEST $^
|
||||||
|
|
||||||
|
|
||||||
|
# Convert between text and touch tone representation.
|
||||||
|
|
||||||
|
text2tt : tt_text.c
|
||||||
|
$(CC) $(CFLAGS) -DENC_MAIN -o text2tt tt_text.c
|
||||||
|
|
||||||
|
tt2text : tt_text.c
|
||||||
|
$(CC) $(CFLAGS) -DDEC_MAIN -o tt2text tt_text.c
|
||||||
|
|
||||||
|
|
||||||
|
# Convert between Latitude/Longitude and UTM coordinates.
|
||||||
|
|
||||||
|
ll2utm : ll2utm.c utm.a
|
||||||
|
$(CC) $(CFLAGS) -I utm -o $@ $^
|
||||||
|
|
||||||
|
utm2ll : utm2ll.c utm.a
|
||||||
|
$(CC) $(CFLAGS) -I utm -o $@ $^
|
||||||
|
|
||||||
|
|
||||||
|
# Test application to generate sound.
|
||||||
|
|
||||||
|
gen_packets : gen_packets.o ax25_pad.o hdlc_send.o fcs_calc.o gen_tone.o textcolor.o misc.a regex.a
|
||||||
|
$(CC) $(CFLAGS) -o $@ $^
|
||||||
|
|
||||||
|
# For tweaking the demodulator.
|
||||||
|
|
||||||
|
demod.o : tune.h
|
||||||
|
demod_9600.o : tune.h
|
||||||
|
demod_afsk.o : tune.h
|
||||||
|
|
||||||
|
|
||||||
|
testagc : atest.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 regex.a misc.a \
|
||||||
|
fsk_demod_agc.h
|
||||||
|
rm -f atest.exe
|
||||||
|
$(CC) $(CFLAGS) -DNOFIX -o atest $^
|
||||||
|
./atest ../direwolf-0.2/02_Track_2.wav | grep "packets decoded in" >atest.out
|
||||||
|
|
||||||
|
|
||||||
|
noisy3.wav : gen_packets
|
||||||
|
./gen_packets -B 300 -n 100 -o noisy3.wav
|
||||||
|
|
||||||
|
testagc3 : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \
|
||||||
|
rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c symbols.c textcolor.c regex.a misc.a \
|
||||||
|
tune.h
|
||||||
|
rm -f atest.exe
|
||||||
|
$(CC) $(CFLAGS) -o atest $^
|
||||||
|
./atest -B 300 -P D -D 3 noisy3.wav | grep "packets decoded in" >atest.out
|
||||||
|
|
||||||
|
|
||||||
|
noisy96.wav : gen_packets
|
||||||
|
./gen_packets -B 9600 -n 100 -o noisy96.wav
|
||||||
|
|
||||||
|
testagc9 : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \
|
||||||
|
rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c symbols.c textcolor.c regex.a misc.a \
|
||||||
|
tune.h
|
||||||
|
rm -f atest.exe
|
||||||
|
$(CC) $(CFLAGS) -o atest $^
|
||||||
|
./atest -B 9600 ../walkabout9600.wav | grep "packets decoded in" >atest.out
|
||||||
|
#./atest -B 9600 noisy96.wav | grep "packets decoded in" >atest.out
|
||||||
|
|
||||||
|
|
||||||
|
# Unit test for AFSK demodulator
|
||||||
|
|
||||||
|
|
||||||
|
atest : atest.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 misc.a regex.a \
|
||||||
|
fsk_fast_filter.h
|
||||||
|
$(CC) $(CFLAGS) -o $@ $^
|
||||||
|
echo " " > tune.h
|
||||||
|
./atest ..\\direwolf-0.2\\02_Track_2.wav
|
||||||
|
|
||||||
|
atest9 : atest.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 misc.a regex.a \
|
||||||
|
fsk_fast_filter.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
|
||||||
|
|
||||||
|
|
||||||
|
dtest : digipeater.c ax25_pad.c dedupe.c fcs_calc.c tq.c textcolor.c misc.a regex.a
|
||||||
|
$(CC) $(CFLAGS) -DTEST -o $@ $^
|
||||||
|
./dtest
|
||||||
|
rm dtest.exe
|
||||||
|
|
||||||
|
# Unit test for APRStt.
|
||||||
|
|
||||||
|
ttest : aprs_tt.c tt_text.c misc.a utm.a
|
||||||
|
$(CC) $(CFLAGS) -DTT_MAIN -o ttest aprs_tt.c tt_text.c misc.a utm.a
|
||||||
|
|
||||||
|
|
||||||
|
# Unit test for IGate
|
||||||
|
|
||||||
|
itest : igate.c textcolor.c ax25_pad.c fcs_calc.c misc.a regex.a
|
||||||
|
$(CC) $(CFLAGS) -DITEST -g -o $@ $^ -lwinmm -lws2_32
|
||||||
|
|
||||||
|
|
||||||
|
# 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 -lrt
|
||||||
|
./udptest
|
||||||
|
|
||||||
|
|
||||||
|
# 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) -g -o $@ $^ -lwinmm -lws2_32
|
||||||
|
|
||||||
|
|
||||||
|
SRCS = direwolf.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c \
|
||||||
|
hdlc_rec2.c multi_modem.c redecode.c rdq.c rrbb.c \
|
||||||
|
fcs_calc.c ax25_pad.c decode_aprs.c symbols.c \
|
||||||
|
server.c kiss.c kissnet.c kiss_frame.c hdlc_send.c fcs_calc.c gen_tone.c audio_win.c \
|
||||||
|
digipeater.c dedupe.c tq.c xmit.c beacon.c \
|
||||||
|
encode_aprs.c latlong.c \
|
||||||
|
dtmf.c aprs_tt.c tt_text.c igate.c
|
||||||
|
|
||||||
|
|
||||||
|
depend : $(SRCS)
|
||||||
|
makedepend $(INCLUDES) $^
|
||||||
|
|
||||||
|
|
||||||
|
clean :
|
||||||
|
rm -f *.o *.a *.exe fsk_fast_filter.h noisy96.wav
|
||||||
|
echo " " > tune.h
|
||||||
|
|
||||||
|
|
||||||
|
# Package it up for distribution: Prebuilt Windows & source versions.
|
||||||
|
|
||||||
|
|
||||||
|
dist-win : direwolf.exe decode_aprs.exe CHANGES.txt User-Guide.pdf Quick-Start-Guide-Windows.pdf \
|
||||||
|
Raspberry-Pi-APRS.pdf APRStt-Implementation-Notes.pdf
|
||||||
|
rm -f ../$z-win.zip
|
||||||
|
zip ../$z-win.zip CHANGES.txt User-Guide.pdf Quick-Start-Guide-Windows.pdf \
|
||||||
|
Raspberry-Pi-APRS.pdf APRStt-Implementation-Notes.pdf LICENSE* *.conf \
|
||||||
|
direwolf.exe decode_aprs.exe tocalls.txt symbols-new.txt symbolsX.txt \
|
||||||
|
text2tt.exe tt2text.exe ll2utm.exe utm2ll.exe aclients.exe
|
||||||
|
|
||||||
|
dist-src : CHANGES.txt User-Guide.pdf Quick-Start-Guide-Windows.pdf Raspberry-Pi-APRS.pdf \
|
||||||
|
APRStt-Implementation-Notes.pdf \
|
||||||
|
direwolf.desktop dw-start.sh \
|
||||||
|
tocalls.txt symbols-new.txt symbolsX.txt
|
||||||
|
rm -f fsk_fast_filter.h
|
||||||
|
echo " " > tune.h
|
||||||
|
rm -f ../$z-src.zip
|
||||||
|
(cd .. ; zip $z-src.zip \
|
||||||
|
$z/CHANGES.txt $z/LICENSE* \
|
||||||
|
$z/User-Guide.pdf $z/Quick-Start-Guide-Windows.pdf \
|
||||||
|
$z/Raspberry-Pi-APRS.pdf $z/APRStt-Implementation-Notes.pdf \
|
||||||
|
$z/Makefile* $z/*.c $z/*.h $z/regex/* $z/misc/* $z/utm/* \
|
||||||
|
$z/*.conf $z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \
|
||||||
|
$z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \
|
||||||
|
$z/direwolf.desktop $z/dw-start.sh )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
User-Guide.pdf : User-Guide.docx
|
||||||
|
echo "***** User-Guide.pdf is out of date *****"
|
||||||
|
|
||||||
|
Quick-Start-Guide-Windows.pdf : Quick-Start-Guide-Windows.docx
|
||||||
|
echo "***** Quick-Start-Guide-Windows.pdf is out of date *****"
|
||||||
|
|
||||||
|
Raspberry-Pi-APRS.pdf : Raspberry-Pi-APRS.docx
|
||||||
|
echo "***** Raspberry-Pi-APRS.pdf is out of date *****"
|
||||||
|
|
||||||
|
APRStt-Implementation-Notes.pdf : APRStt-Implementation-Notes.docx
|
||||||
|
echo "***** APRStt-Implementation-Notes.pdf is out of date *****"
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,825 @@
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* 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 8000=AGWPE 8002=DireWolf COM1=D710A
|
||||||
|
*
|
||||||
|
* This will connect to multiple physical or virtual
|
||||||
|
* TNCs, read packets from them, and display results.
|
||||||
|
*
|
||||||
|
*---------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Native Windows: Use the Winsock interface.
|
||||||
|
* Linux: Use the BSD socket interface.
|
||||||
|
* Cygwin: Can use either one.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
|
||||||
|
#include <winsock2.h>
|
||||||
|
// default is 0x0400
|
||||||
|
#undef _WIN32_WINNT
|
||||||
|
#define _WIN32_WINNT 0x0501 /* Minimum OS version is XP. */
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#else
|
||||||
|
//#define __USE_XOPEN2KXSI 1
|
||||||
|
//#define __USE_XOPEN 1
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <sys/errno.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#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__
|
||||||
|
sprintf (pStringBuf, "%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__
|
||||||
|
sprintf (pStringBuf, "%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:
|
||||||
|
sprintf (pStringBuf, "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: aclients 8000=AGWPE 8002=DireWolf COM1=D710A
|
||||||
|
*
|
||||||
|
* Each command line argument is TCP port number or a
|
||||||
|
* serial port name. Follow by = and a text description
|
||||||
|
* of what is connected.
|
||||||
|
*
|
||||||
|
* For now, everything is assumed to be on localhost.
|
||||||
|
* Maybe someday we might recognize host:port=description.
|
||||||
|
*
|
||||||
|
*---------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#define MAX_CLIENTS 6
|
||||||
|
|
||||||
|
/* Obtained from the command line. */
|
||||||
|
|
||||||
|
static int num_clients;
|
||||||
|
|
||||||
|
static char hostname[MAX_CLIENTS][50];
|
||||||
|
static char port[MAX_CLIENTS][30];
|
||||||
|
static char description[MAX_CLIENTS][50];
|
||||||
|
|
||||||
|
#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<num_clients; j++) {
|
||||||
|
char stemp[100];
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
strcpy (stemp, argv[j+1]);
|
||||||
|
p = strtok (stemp, "=");
|
||||||
|
if (p == NULL) {
|
||||||
|
printf ("Internal error 1\n");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
strcpy (hostname[j], "localhost");
|
||||||
|
strcpy (port[j], p);
|
||||||
|
p = strtok (NULL, "=");
|
||||||
|
if (p == NULL) {
|
||||||
|
printf ("Missing description after %s\n", port[j]);
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
strcpy (description[j], p);
|
||||||
|
}
|
||||||
|
|
||||||
|
//printf ("_WIN32_WINNT = %04x\n", _WIN32_WINNT);
|
||||||
|
//for (j=0; j<num_clients; j++) {
|
||||||
|
// printf ("%s,%s,%s\n", hostname[j], port[j], description[j]);
|
||||||
|
//}
|
||||||
|
|
||||||
|
memset (packets, ' ', (size_t)LINE_WIDTH);
|
||||||
|
packets[LINE_WIDTH] = '\0';
|
||||||
|
|
||||||
|
for (j=0; j<num_clients; j++) {
|
||||||
|
packet_count[j] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (j=0; j<num_clients; j++) {
|
||||||
|
#if __WIN32__
|
||||||
|
if (isdigit(port[j][0])) {
|
||||||
|
client_th[j] = (HANDLE)_beginthreadex (NULL, 0, client_thread_net, (void *)j, 0, NULL);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
client_th[j] = (HANDLE)_beginthreadex (NULL, 0, client_thread_serial, (void *)j, 0, NULL);
|
||||||
|
}
|
||||||
|
if (client_th[j] == NULL) {
|
||||||
|
printf ("Internal error: Could not create client thread %d.\n", j);
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (isdigit(port[j][0])) {
|
||||||
|
e = pthread_create (&client_tid[j], NULL, client_thread_net, (void *)(long)j);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
e = pthread_create (&client_tid[j], NULL, client_thread_serial, (void *)(long)j);
|
||||||
|
}
|
||||||
|
if (e != 0) {
|
||||||
|
perror("Internal error: Could not create client thread.");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
start_time = time(NULL);
|
||||||
|
next_print_time = start_time + (PRINT_MINUTES) * 60;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Print results from clients.
|
||||||
|
*/
|
||||||
|
while (1) {
|
||||||
|
int k;
|
||||||
|
int something;
|
||||||
|
|
||||||
|
SLEEP_MS(100);
|
||||||
|
|
||||||
|
something = 0;
|
||||||
|
for (k=0; k<LINE_WIDTH && ! something; k++) {
|
||||||
|
if (packets[k] != ' ') {
|
||||||
|
something = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (something) {
|
||||||
|
/* time for others to catch up. */
|
||||||
|
SLEEP_MS(200);
|
||||||
|
|
||||||
|
printf ("%s\n", packets);
|
||||||
|
memset (packets, ' ', (size_t)LINE_WIDTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
now = time(NULL);
|
||||||
|
if (now >= next_print_time) {
|
||||||
|
next_print_time = now + (PRINT_MINUTES) * 60;
|
||||||
|
|
||||||
|
printf ("\nTotals after %d minutes", (int)((now - start_time) / 60));
|
||||||
|
|
||||||
|
for (j=0; j<num_clients; j++) {
|
||||||
|
printf (", %s %d", description[j], packet_count[j]);
|
||||||
|
}
|
||||||
|
printf ("\n\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return 0; // unreachable
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: client_thread_net
|
||||||
|
*
|
||||||
|
* Purpose: Establish connection with a TNC via network.
|
||||||
|
*
|
||||||
|
* Inputs: arg - My instance index, 0 thru MAX_CLIENTS-1.
|
||||||
|
*
|
||||||
|
* Outputs: packets - Received packets are put in the corresponding column.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#define MAX_HOSTS 30
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
static unsigned __stdcall client_thread_net (void *arg)
|
||||||
|
#else
|
||||||
|
static void * client_thread_net (void *arg)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
int my_index;
|
||||||
|
struct addrinfo hints;
|
||||||
|
struct addrinfo *ai_head = NULL;
|
||||||
|
struct addrinfo *ai;
|
||||||
|
struct addrinfo *hosts[MAX_HOSTS];
|
||||||
|
int num_hosts, n;
|
||||||
|
int err;
|
||||||
|
char ipaddr_str[46]; /* text form of IP address */
|
||||||
|
#if __WIN32__
|
||||||
|
WSADATA wsadata;
|
||||||
|
#endif
|
||||||
|
/*
|
||||||
|
* File descriptor for socket to server.
|
||||||
|
* Set to -1 if not connected.
|
||||||
|
* (Don't use SOCKET type because it is unsigned.)
|
||||||
|
*/
|
||||||
|
int server_sock = -1;
|
||||||
|
struct agwpe_s mon_cmd;
|
||||||
|
char data[1024];
|
||||||
|
int use_chan = -1;
|
||||||
|
|
||||||
|
|
||||||
|
my_index = (int)(long)arg;
|
||||||
|
|
||||||
|
#if DEBUGx
|
||||||
|
printf ("DEBUG: client_thread_net %d start, port = '%s'\n", my_index, port[my_index]);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
err = WSAStartup (MAKEWORD(2,2), &wsadata);
|
||||||
|
if (err != 0) {
|
||||||
|
printf("WSAStartup failed: %d\n", err);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
|
||||||
|
printf("Could not find a usable version of Winsock.dll\n");
|
||||||
|
WSACleanup();
|
||||||
|
//sleep (1);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
memset (&hints, 0, sizeof(hints));
|
||||||
|
|
||||||
|
hints.ai_family = AF_UNSPEC; /* Allow either IPv4 or IPv6. */
|
||||||
|
// hints.ai_family = AF_INET; /* IPv4 only. */
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
hints.ai_protocol = IPPROTO_TCP;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Connect to TNC server.
|
||||||
|
*/
|
||||||
|
|
||||||
|
ai_head = NULL;
|
||||||
|
err = getaddrinfo(hostname[my_index], port[my_index], &hints, &ai_head);
|
||||||
|
if (err != 0) {
|
||||||
|
#if __WIN32__
|
||||||
|
printf ("Can't get address for server %s, err=%d\n",
|
||||||
|
hostname[my_index], WSAGetLastError());
|
||||||
|
#else
|
||||||
|
printf ("Can't get address for server %s, %s\n",
|
||||||
|
hostname[my_index], gai_strerror(err));
|
||||||
|
#endif
|
||||||
|
freeaddrinfo(ai_head);
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG_DNS
|
||||||
|
printf ("getaddrinfo returns:\n");
|
||||||
|
#endif
|
||||||
|
num_hosts = 0;
|
||||||
|
for (ai = ai_head; ai != NULL; ai = ai->ai_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; n<num_hosts; n++) {
|
||||||
|
ia_to_text (hosts[n]->ai_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; n<num_hosts; n++) {
|
||||||
|
int is;
|
||||||
|
|
||||||
|
ai = hosts[n];
|
||||||
|
|
||||||
|
ia_to_text (ai->ai_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
|
||||||
|
(void)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 < 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;
|
||||||
|
|
||||||
|
//printf ("server %d, portx = %d\n", my_index, mon_cmd.portx);
|
||||||
|
|
||||||
|
use_chan == mon_cmd.portx;
|
||||||
|
pp = ax25_from_frame ((unsigned char *)(data+1), mon_cmd.data_len-1, -1);
|
||||||
|
ax25_format_addrs (pp, result);
|
||||||
|
info_len = ax25_get_info (pp, (unsigned char **)(&pinfo));
|
||||||
|
pinfo[info_len] = '\0';
|
||||||
|
strcat (result, pinfo);
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
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.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: <<UI>>:
|
||||||
|
* `c0+!h4>/]"4a}146.520MHz Listening, V-Alert & WLNK-1=
|
||||||
|
*
|
||||||
|
* N8VIM>BEACON,W1XM,WB2OSZ-1,WIDE2*: <UI>:
|
||||||
|
* !4240.85N/07133.99W_PHG72604/ Pepperell, MA. WX. 442.9+ PL100
|
||||||
|
*
|
||||||
|
* Don't know why some are <<UI>> and some <UI>.
|
||||||
|
*
|
||||||
|
* 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 */
|
|
@ -0,0 +1,100 @@
|
||||||
|
|
||||||
|
/* 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_MACRO } type;
|
||||||
|
|
||||||
|
char pattern[20]; /* e.g. B998, B5bbbdddd, B2xxyy, Byyyxxx */
|
||||||
|
/* 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 {
|
||||||
|
char zone[8];
|
||||||
|
double scale;
|
||||||
|
double x_offset;
|
||||||
|
double y_offset;
|
||||||
|
} utm;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
char *definition;
|
||||||
|
} macro;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Configuratin options for APRStt.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define TT_MAX_XMITS 10
|
||||||
|
|
||||||
|
struct tt_config_s {
|
||||||
|
|
||||||
|
int obj_xmit_chan; /* Channel to transmit object report. */
|
||||||
|
char obj_xmit_header[AX25_MAX_ADDRS*AX25_MAX_ADDR_LEN];
|
||||||
|
/* e.g. "WB2OSZ-5>APDW07,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. */
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void aprs_tt_init (struct tt_config_s *p_config);
|
||||||
|
|
||||||
|
void aprs_tt_button (int chan, char button);
|
||||||
|
|
||||||
|
/* Error codes for sending responses to user. */
|
||||||
|
|
||||||
|
#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. */
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* end aprs_tt.h */
|
|
@ -0,0 +1,447 @@
|
||||||
|
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011,2012,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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: atest.c
|
||||||
|
*
|
||||||
|
* Purpose: Unit test 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.
|
||||||
|
* This results in .WMA files.
|
||||||
|
*
|
||||||
|
* (4) Upload the .WMA file(s) to http://media.io/ and
|
||||||
|
* convert to .WAV format.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Comparison to others:
|
||||||
|
*
|
||||||
|
* Here are some other scores from Track 2 of the TNC Test CD:
|
||||||
|
* http://sites.google.com/site/ki4mcw/Home/arduino-tnc
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Version 0.4 decoded 870 packets.
|
||||||
|
*
|
||||||
|
* After a little tweaking, version 0.5 decodes 931 packets.
|
||||||
|
*
|
||||||
|
* After more tweaking, version 0.6 gets 965 packets.
|
||||||
|
* This is without the option to retry after getting a bad FCS.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
// #define X 1
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
//#include <fcntl.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
|
||||||
|
|
||||||
|
#define ATEST_C 1
|
||||||
|
|
||||||
|
#include "audio.h"
|
||||||
|
#include "demod.h"
|
||||||
|
// #include "fsk_demod_agc.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
#include "ax25_pad.h"
|
||||||
|
#include "hdlc_rec2.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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. */
|
||||||
|
} ;
|
||||||
|
|
||||||
|
/* 8 bit samples are unsigned bytes */
|
||||||
|
/* in range of 0 .. 255. */
|
||||||
|
|
||||||
|
/* 16 bit samples are signed short */
|
||||||
|
/* in range of -32768 .. +32767. */
|
||||||
|
|
||||||
|
static struct wav_header header;
|
||||||
|
static FILE *fp;
|
||||||
|
static int e_o_f;
|
||||||
|
static int packets_decoded = 0;
|
||||||
|
static int decimate = 1; /* Reduce that sampling rate. */
|
||||||
|
/* 1 = normal, 2 = half, etc. */
|
||||||
|
|
||||||
|
|
||||||
|
int main (int argc, char *argv[])
|
||||||
|
{
|
||||||
|
|
||||||
|
//int err;
|
||||||
|
int c;
|
||||||
|
struct audio_s modem;
|
||||||
|
int channel;
|
||||||
|
time_t start_time;
|
||||||
|
|
||||||
|
text_color_init(1);
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* First apply defaults.
|
||||||
|
*/
|
||||||
|
|
||||||
|
memset (&modem, 0, sizeof(modem));
|
||||||
|
|
||||||
|
modem.num_channels = DEFAULT_NUM_CHANNELS;
|
||||||
|
modem.samples_per_sec = DEFAULT_SAMPLES_PER_SEC;
|
||||||
|
modem.bits_per_sample = DEFAULT_BITS_PER_SAMPLE;
|
||||||
|
|
||||||
|
/* TODO: should have a command line option for this. */
|
||||||
|
/* Results v0.9: 971/69, 990/64, 992/65, 992/67, 1004/476 */
|
||||||
|
|
||||||
|
modem.fix_bits = RETRY_NONE;
|
||||||
|
modem.fix_bits = RETRY_SINGLE;
|
||||||
|
modem.fix_bits = RETRY_DOUBLE;
|
||||||
|
//modem.fix_bits = RETRY_TRIPLE;
|
||||||
|
//modem.fix_bits = RETRY_TWO_SEP;
|
||||||
|
|
||||||
|
for (channel=0; channel<MAX_CHANS; channel++) {
|
||||||
|
|
||||||
|
modem.modem_type[channel] = AFSK;
|
||||||
|
|
||||||
|
modem.mark_freq[channel] = DEFAULT_MARK_FREQ;
|
||||||
|
modem.space_freq[channel] = DEFAULT_SPACE_FREQ;
|
||||||
|
modem.baud[channel] = DEFAULT_BAUD;
|
||||||
|
strcpy (modem.profiles[channel], "C");
|
||||||
|
// temp
|
||||||
|
// strcpy (modem.profiles[channel], "F");
|
||||||
|
modem.num_subchan[channel] = strlen(modem.profiles[channel]);
|
||||||
|
|
||||||
|
modem.num_freq[channel] = 1;
|
||||||
|
modem.offset[channel] = 0;
|
||||||
|
// temp test
|
||||||
|
//modem.num_subchan[channel] = modem.num_freq[channel] = 3;
|
||||||
|
//modem.num_subchan[channel] = modem.num_freq[channel] = 5;
|
||||||
|
//modem.offset[channel] = 100;
|
||||||
|
|
||||||
|
//strcpy (modem.ptt_device[channel], "");
|
||||||
|
//modem.ptt_line[channel] = PTT_NONE;
|
||||||
|
|
||||||
|
//modem.slottime[channel] = DEFAULT_SLOTTIME;
|
||||||
|
//modem.persist[channel] = DEFAULT_PERSIST;
|
||||||
|
//modem.txdelay[channel] = DEFAULT_TXDELAY;
|
||||||
|
//modem.txtail[channel] = DEFAULT_TXTAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
int this_option_optind = optind ? optind : 1;
|
||||||
|
int option_index = 0;
|
||||||
|
static struct option long_options[] = {
|
||||||
|
{"future1", 1, 0, 0},
|
||||||
|
{"future2", 0, 0, 0},
|
||||||
|
{"future3", 1, 0, 'c'},
|
||||||
|
{0, 0, 0, 0}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ':' following option character means arg is required. */
|
||||||
|
|
||||||
|
c = getopt_long(argc, argv, "B:P:D:",
|
||||||
|
long_options, &option_index);
|
||||||
|
if (c == -1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
|
||||||
|
case 'B': /* -B for data Bit rate */
|
||||||
|
/* 300 implies 1600/1800 AFSK. */
|
||||||
|
/* 1200 implies 1200/2200 AFSK. */
|
||||||
|
/* 9600 implies scrambled. */
|
||||||
|
|
||||||
|
modem.baud[0] = atoi(optarg);
|
||||||
|
|
||||||
|
printf ("Data rate set to %d bits / second.\n", modem.baud[0]);
|
||||||
|
|
||||||
|
if (modem.baud[0] < 100 || modem.baud[0] > 10000) {
|
||||||
|
fprintf (stderr, "Use a more reasonable bit rate in range of 100 - 10000.\n");
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
if (modem.baud[0] < 600) {
|
||||||
|
modem.modem_type[0] = AFSK;
|
||||||
|
modem.mark_freq[0] = 1600;
|
||||||
|
modem.space_freq[0] = 1800;
|
||||||
|
}
|
||||||
|
else if (modem.baud[0] > 2400) {
|
||||||
|
modem.modem_type[0] = SCRAMBLE;
|
||||||
|
modem.mark_freq[0] = 0;
|
||||||
|
modem.space_freq[0] = 0;
|
||||||
|
printf ("Using scrambled baseband signal rather than AFSK.\n");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
modem.modem_type[0] = AFSK;
|
||||||
|
modem.mark_freq[0] = 1200;
|
||||||
|
modem.space_freq[0] = 2200;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'P': /* -P for modem profile. */
|
||||||
|
|
||||||
|
printf ("Demodulator profile set to \"%s\"\n", optarg);
|
||||||
|
strcpy (modem.profiles[0], optarg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'D': /* -D reduce sampling rate for lower CPU usage. */
|
||||||
|
|
||||||
|
decimate = atoi(optarg);
|
||||||
|
printf ("Decimate factor = %d\n", decimate);
|
||||||
|
modem.decimate[0] = decimate;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '?':
|
||||||
|
|
||||||
|
/* Unknown option message was already printed. */
|
||||||
|
//usage (argv);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
/* Should not be here. */
|
||||||
|
printf("?? getopt returned character code 0%o ??\n", c);
|
||||||
|
//usage (argv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optind >= argc) {
|
||||||
|
printf ("Specify .WAV file name on command line.\n");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fp = fopen(argv[optind], "rb");
|
||||||
|
if (fp == NULL) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
fprintf (stderr, "Couldn't open file for read: %s\n", argv[optind]);
|
||||||
|
//perror ("more info?");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
start_time = time(NULL);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read the file header.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fread (&header, sizeof(header), (size_t)1, fp);
|
||||||
|
|
||||||
|
assert (header.nchannels == 1 || header.nchannels == 2);
|
||||||
|
assert (header.wbitspersample == 8 || header.wbitspersample == 16);
|
||||||
|
|
||||||
|
modem.samples_per_sec = header.nsamplespersec;
|
||||||
|
modem.samples_per_sec = modem.samples_per_sec;
|
||||||
|
modem.bits_per_sample = header.wbitspersample;
|
||||||
|
modem.num_channels = header.nchannels;
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
printf ("%d samples per second\n", modem.samples_per_sec);
|
||||||
|
printf ("%d bits per sample\n", modem.bits_per_sample);
|
||||||
|
printf ("%d audio channels\n", modem.num_channels);
|
||||||
|
printf ("%d audio bytes in file\n", (int)(header.datasize));
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize the AFSK demodulator and HDLC decoder.
|
||||||
|
*/
|
||||||
|
multi_modem_init (&modem);
|
||||||
|
|
||||||
|
|
||||||
|
e_o_f = 0;
|
||||||
|
while ( ! e_o_f)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
int audio_sample;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
for (c=0; c<modem.num_channels; c++)
|
||||||
|
{
|
||||||
|
|
||||||
|
/* This reads either 1 or 2 bytes depending on */
|
||||||
|
/* bits per sample. */
|
||||||
|
|
||||||
|
audio_sample = demod_get_sample ();
|
||||||
|
|
||||||
|
if (audio_sample >= 256 * 256)
|
||||||
|
e_o_f = 1;
|
||||||
|
|
||||||
|
#define ONE_CHAN 1 /* only use one audio channel. */
|
||||||
|
|
||||||
|
#if ONE_CHAN
|
||||||
|
if (c != 0) continue;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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);
|
||||||
|
printf ("\n\n");
|
||||||
|
printf ("%d packets decoded in %d seconds.\n", packets_decoded, (int)(time(NULL) - start_time));
|
||||||
|
|
||||||
|
exit (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Simulate sample from the audio device.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int audio_get (void)
|
||||||
|
{
|
||||||
|
int ch;
|
||||||
|
|
||||||
|
ch = getc(fp);
|
||||||
|
|
||||||
|
if (ch < 0) 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;
|
||||||
|
int alevel;
|
||||||
|
int subchan;
|
||||||
|
|
||||||
|
|
||||||
|
chan = rrbb_get_chan(rrbb);
|
||||||
|
subchan = rrbb_get_subchan(rrbb);
|
||||||
|
alevel = rrbb_get_audio_level(rrbb);
|
||||||
|
|
||||||
|
hdlc_rec2_try_to_fix_later (rrbb, chan, subchan, alevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is called when we have a good frame.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void app_process_rec_packet (int chan, int subchan, packet_t pp, int alevel, retry_t retries, char *spectrum)
|
||||||
|
{
|
||||||
|
|
||||||
|
//int err;
|
||||||
|
//char *p;
|
||||||
|
char stemp[500];
|
||||||
|
unsigned char *pinfo;
|
||||||
|
int info_len;
|
||||||
|
int h;
|
||||||
|
char heard[20];
|
||||||
|
//packet_t pp;
|
||||||
|
|
||||||
|
|
||||||
|
packets_decoded++;
|
||||||
|
|
||||||
|
|
||||||
|
ax25_format_addrs (pp, stemp);
|
||||||
|
|
||||||
|
info_len = ax25_get_info (pp, &pinfo);
|
||||||
|
|
||||||
|
/* Print so we can see what is going on. */
|
||||||
|
|
||||||
|
#if 1
|
||||||
|
/* Display audio input level. */
|
||||||
|
/* Who are we hearing? Original station or digipeater. */
|
||||||
|
|
||||||
|
h = ax25_get_heard(pp);
|
||||||
|
ax25_get_addr_with_ssid(pp, h, heard);
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
printf ("\n");
|
||||||
|
|
||||||
|
if (h != AX25_SOURCE) {
|
||||||
|
printf ("Digipeater ");
|
||||||
|
}
|
||||||
|
printf ("%s audio level = %d [%s] %s\n", heard, alevel, retry_text[(int)retries], spectrum);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Display non-APRS packets in a different color.
|
||||||
|
|
||||||
|
if (ax25_is_aprs(pp)) {
|
||||||
|
text_color_set(DW_COLOR_REC);
|
||||||
|
printf ("[%d] ", chan);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
printf ("[%d] ", chan);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf ("%s", stemp); /* stations followed by : */
|
||||||
|
ax25_safe_print ((char *)pinfo, info_len, 0);
|
||||||
|
printf ("\n");
|
||||||
|
|
||||||
|
ax25_delete (pp);
|
||||||
|
|
||||||
|
} /* end app_process_rec_packet */
|
||||||
|
|
||||||
|
|
||||||
|
/* end atest.c */
|
|
@ -0,0 +1,208 @@
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Module: audio.h
|
||||||
|
*
|
||||||
|
* Purpose: Interface to audio device commonly called a "sound card."
|
||||||
|
*
|
||||||
|
*---------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef AUDIO_H
|
||||||
|
#define AUDIO_H 1
|
||||||
|
|
||||||
|
#include "direwolf.h" /* for MAX_CHANS used throughout the application. */
|
||||||
|
#include "hdlc_rec2.h" /* for enum retry_e */
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 purpos I/O. */
|
||||||
|
|
||||||
|
typedef enum ptt_method_e ptt_method_t;
|
||||||
|
|
||||||
|
enum ptt_line_e { PTT_LINE_RTS = 1, PTT_LINE_DTR = 2 };
|
||||||
|
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 };
|
||||||
|
|
||||||
|
struct audio_s {
|
||||||
|
|
||||||
|
/* Properites of the sound device. */
|
||||||
|
|
||||||
|
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). */
|
||||||
|
|
||||||
|
enum audio_in_type_e audio_in_type;
|
||||||
|
/* Where is input (receive) audio coming from? */
|
||||||
|
|
||||||
|
/* Common to all channels. */
|
||||||
|
|
||||||
|
enum retry_e fix_bits; /* Level of effort to recover from */
|
||||||
|
/* a bad FCS on the frame. */
|
||||||
|
|
||||||
|
/* Properties for each audio channel, common to receive and transmit. */
|
||||||
|
/* Can be different for each radio channel. */
|
||||||
|
|
||||||
|
enum modem_t {AFSK, NONE, SCRAMBLE} modem_type[MAX_CHANS];
|
||||||
|
/* Usual AFSK. */
|
||||||
|
/* Baseband signal. */
|
||||||
|
/* Scrambled http://www.amsat.org/amsat/articles/g3ruh/109/fig03.gif */
|
||||||
|
|
||||||
|
int decimate[MAX_CHANS]; /* Reduce AFSK sample rate by this factor to */
|
||||||
|
/* decrease computational requirements. */
|
||||||
|
|
||||||
|
int mark_freq[MAX_CHANS]; /* Two tones for AFSK modulation, in Hz. */
|
||||||
|
int space_freq[MAX_CHANS]; /* Standard tones are 1200 and 2200 for 1200 baud. */
|
||||||
|
|
||||||
|
int baud[MAX_CHANS]; /* Data bits (more accurately, symbols) per second. */
|
||||||
|
/* Standard rates are 1200 for VHF and 300 for HF. */
|
||||||
|
|
||||||
|
char profiles[MAX_CHANS][16]; /* 1 or more of ABC etc. */
|
||||||
|
|
||||||
|
int num_freq[MAX_CHANS]; /* Number of different frequency pairs for decoders. */
|
||||||
|
|
||||||
|
int offset[MAX_CHANS]; /* Spacing between filter frequencies. */
|
||||||
|
|
||||||
|
int num_subchan[MAX_CHANS]; /* Total number of modems / hdlc decoders for each channel. */
|
||||||
|
/* Potentially it could be product of strlen(profiles) * num_freq. */
|
||||||
|
/* Currently can't use both at once. */
|
||||||
|
|
||||||
|
|
||||||
|
/* Additional properties for transmit. */
|
||||||
|
|
||||||
|
ptt_method_t ptt_method[MAX_CHANS]; /* serial port or GPIO. */
|
||||||
|
|
||||||
|
char ptt_device[MAX_CHANS][20]; /* Serial device name for PTT. e.g. COM1 or /dev/ttyS0 */
|
||||||
|
|
||||||
|
ptt_line_t ptt_line[MAX_CHANS]; /* Control line wehn using serial port. */
|
||||||
|
/* PTT_RTS, PTT_DTR. */
|
||||||
|
|
||||||
|
int ptt_gpio[MAX_CHANS]; /* GPIO number. */
|
||||||
|
|
||||||
|
int ptt_invert[MAX_CHANS]; /* Invert the output. */
|
||||||
|
|
||||||
|
int slottime[MAX_CHANS]; /* Slot time in 10 mS units for persistance algorithm. */
|
||||||
|
/* Typical value is 10 meaning 100 milliseconds. */
|
||||||
|
|
||||||
|
int persist[MAX_CHANS]; /* 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[MAX_CHANS]; /* After turning on the transmitter, */
|
||||||
|
/* send "flags" for txdelay * 10 mS. */
|
||||||
|
/* Default value is 30 meaning 300 milliseconds. */
|
||||||
|
|
||||||
|
int txtail[MAX_CHANS]; /* 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. */
|
||||||
|
};
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
#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 /* Formerly 44100. */
|
||||||
|
/* Software defined radio often uses 48000. */
|
||||||
|
|
||||||
|
#define DEFAULT_BITS_PER_SAMPLE 16
|
||||||
|
|
||||||
|
#define DEFAULT_FIX_BITS RETRY_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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Typical transmit timings for VHF.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DEFAULT_SLOTTIME 10
|
||||||
|
#define DEFAULT_PERSIST 63
|
||||||
|
#define DEFAULT_TXDELAY 30
|
||||||
|
#define DEFAULT_TXTAIL 10 /* not sure yet. */
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 (void);
|
||||||
|
|
||||||
|
int audio_put (int c);
|
||||||
|
|
||||||
|
int audio_flush (void);
|
||||||
|
|
||||||
|
int audio_wait (int duration);
|
||||||
|
|
||||||
|
int audio_close (void);
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* ifdef AUDIO_H */
|
||||||
|
|
||||||
|
|
||||||
|
/* end audio.h */
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,298 @@
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* 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. */
|
||||||
|
|
||||||
|
#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_NO_LAYER_3 0xf0 /* protocol ID */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef AX25_PAD_C /* Keep this hidden - implementation could change. */
|
||||||
|
|
||||||
|
struct packet_s {
|
||||||
|
|
||||||
|
int magic1; /* for error checking. */
|
||||||
|
|
||||||
|
#define MAGIC 0x41583235
|
||||||
|
|
||||||
|
struct packet_s *nextp; /* Pointer to next in queue. */
|
||||||
|
|
||||||
|
int num_addr; /* Number of elements used in two below. */
|
||||||
|
/* Range of 0 .. AX25_MAX_ADDRS. */
|
||||||
|
|
||||||
|
char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
|
||||||
|
/* Contains the address without the ssid. */
|
||||||
|
/* Why is it larger than 7? */
|
||||||
|
/* Messages from an IGate server can have longer */
|
||||||
|
/* addresses after qAC. Up to 9 observed so far. */
|
||||||
|
|
||||||
|
/* usual human readable form. e.g. WB20SZ-15 */
|
||||||
|
|
||||||
|
unsigned char ssid_etc[AX25_MAX_ADDRS]; /* SSID octet from each address. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 and is normally 1.
|
||||||
|
*
|
||||||
|
* 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 the_rest_len; /* Frame length minus the address part. */
|
||||||
|
|
||||||
|
unsigned char the_rest[2 + 3 + AX25_MAX_INFO_LEN + 1];
|
||||||
|
/* The rest after removing the addresses. */
|
||||||
|
/* Includes control, protocol ID, Information, */
|
||||||
|
/* and throw in one more for a character */
|
||||||
|
/* string nul terminator. */
|
||||||
|
|
||||||
|
int magic2; /* Will get stomped on if above overflows. */
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#else /* Public view. */
|
||||||
|
|
||||||
|
struct packet_s {
|
||||||
|
int secret;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct packet_s *packet_t;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#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
|
||||||
|
* "modulo 128 operation" is in effect. Unfortunately, it seems
|
||||||
|
* this can be determined only by examining the XID frames and
|
||||||
|
* keeping this information for each connection.
|
||||||
|
* We can assume 1 for our purposes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static inline int ax25_get_control_offset (packet_t this_p)
|
||||||
|
{
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int ax25_get_num_control (packet_t this_p)
|
||||||
|
{
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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_num_control(this_p));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ax25_get_num_pid (packet_t this_p)
|
||||||
|
{
|
||||||
|
int c;
|
||||||
|
int pid;
|
||||||
|
|
||||||
|
c = this_p->the_rest[ax25_get_control_offset(this_p)];
|
||||||
|
|
||||||
|
if ( (c & 0x01) == 0 || /* I xxxx xxx0 */
|
||||||
|
c == 0x03 || c == 0x13) { /* UI 000x 0011 */
|
||||||
|
|
||||||
|
pid = this_p->the_rest[ax25_get_pid_offset(this_p)];
|
||||||
|
if (pid == 0xff) {
|
||||||
|
return (2); /* pid 1111 1111 means another follows. */
|
||||||
|
}
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* APRS always has an Information field with at least one octet for the
|
||||||
|
* Data Type Indicator. AX.25 has this for only 5 frame types depending
|
||||||
|
* on the control field.
|
||||||
|
* xxxx xxx0 I
|
||||||
|
* 000x 0011 UI
|
||||||
|
* 101x 1111 XID
|
||||||
|
* 111x 0011 TEST
|
||||||
|
* 100x 0111 FRMR
|
||||||
|
*/
|
||||||
|
|
||||||
|
static inline int ax25_get_info_offset (packet_t this_p)
|
||||||
|
{
|
||||||
|
return (ax25_get_num_control(this_p) + ax25_get_num_pid(this_p));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ax25_get_num_info (packet_t this_p)
|
||||||
|
{
|
||||||
|
int len;
|
||||||
|
|
||||||
|
len = this_p->the_rest_len - ax25_get_num_control(this_p) - ax25_get_num_pid(this_p);
|
||||||
|
if (len < 0) {
|
||||||
|
len = 0; /* print error? */
|
||||||
|
}
|
||||||
|
return (len);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//static packet_t ax25_new (void);
|
||||||
|
|
||||||
|
extern void ax25_delete (packet_t pp);
|
||||||
|
|
||||||
|
extern void ax25_clear (packet_t pp);
|
||||||
|
|
||||||
|
extern packet_t ax25_from_text (char *, int strict);
|
||||||
|
|
||||||
|
extern packet_t ax25_from_frame (unsigned char *data, int len, int alevel);
|
||||||
|
|
||||||
|
extern packet_t ax25_dup (packet_t copy_from);
|
||||||
|
|
||||||
|
extern int ax25_parse_addr (char *in_addr, int strict, char *out_addr, int *out_ssid, int *out_heard);
|
||||||
|
|
||||||
|
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 *);
|
||||||
|
|
||||||
|
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_info (packet_t pp, unsigned char **paddr);
|
||||||
|
|
||||||
|
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_format_addrs (packet_t pp, char *);
|
||||||
|
|
||||||
|
extern int ax25_pack (packet_t pp, unsigned char result[AX25_MAX_PACKET_LEN]);
|
||||||
|
|
||||||
|
extern int ax25_is_aprs (packet_t pp);
|
||||||
|
|
||||||
|
extern int ax25_get_control (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);
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* AX25_PAD_H */
|
||||||
|
|
||||||
|
/* end ax25_pad.h */
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,681 @@
|
||||||
|
//#define DEBUG 1
|
||||||
|
//#define DEBUG_SIM 1
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011,2013,2014 John Langner, WB2OSZ
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Module: beacon.c
|
||||||
|
*
|
||||||
|
* Purpose: Transmit messages on a fixed schedule.
|
||||||
|
*
|
||||||
|
* Description: Transmit periodic messages as specified in the config file.
|
||||||
|
*
|
||||||
|
*---------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
#if __WIN32__
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "ax25_pad.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
#include "audio.h"
|
||||||
|
#include "tq.h"
|
||||||
|
#include "xmit.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "digipeater.h"
|
||||||
|
#include "version.h"
|
||||||
|
#include "encode_aprs.h"
|
||||||
|
#include "beacon.h"
|
||||||
|
#include "latlong.h"
|
||||||
|
#include "dwgps.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Are we using GPS data?
|
||||||
|
* Incremented if tracker beacons configured.
|
||||||
|
* Cleared if dwgps_init fails.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static int g_using_gps = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Save pointers to configuration settings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static struct misc_config_s *g_misc_config_p;
|
||||||
|
static struct digi_config_s *g_digi_config_p;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
static unsigned __stdcall beacon_thread (void *arg);
|
||||||
|
#else
|
||||||
|
static void * beacon_thread (void *arg);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: beacon_init
|
||||||
|
*
|
||||||
|
* Purpose: Initialize the beacon process.
|
||||||
|
*
|
||||||
|
* Inputs: pconfig - misc. configuration from config file.
|
||||||
|
* pdigi - digipeater configuration from config file.
|
||||||
|
* Use to obtain "mycall" for each channel.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Outputs: Remember required information for future use.
|
||||||
|
*
|
||||||
|
* Description: Initialize the queue to be empty and set up other
|
||||||
|
* mechanisms for sharing it between different threads.
|
||||||
|
*
|
||||||
|
* Start up xmit_thread to actually send the packets
|
||||||
|
* at the appropriate time.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void beacon_init (struct misc_config_s *pconfig, struct digi_config_s *pdigi)
|
||||||
|
{
|
||||||
|
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_misc_config_p = pconfig;
|
||||||
|
g_digi_config_p = pdigi;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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; j<g_misc_config_p->num_beacons; j++) {
|
||||||
|
int chan = g_misc_config_p->beacon[j].chan;
|
||||||
|
|
||||||
|
if (chan < 0) chan = 0; /* For IGate, use channel 0 call. */
|
||||||
|
|
||||||
|
if (chan < pdigi->num_chans) {
|
||||||
|
|
||||||
|
if (strlen(pdigi->mycall[chan]) > 0 && strcasecmp(pdigi->mycall[chan], "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:
|
||||||
|
|
||||||
|
#if defined(GPS_ENABLED) || defined(DEBUG_SIM)
|
||||||
|
g_using_gps++;
|
||||||
|
#else
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Config file, line %d: GPS tracker feature is not enabled.\n", g_misc_config_p->beacon[j].lineno);
|
||||||
|
g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
|
||||||
|
continue;
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BEACON_CUSTOM:
|
||||||
|
|
||||||
|
/* INFO is required. */
|
||||||
|
|
||||||
|
if (g_misc_config_p->beacon[j].custom_info == NULL) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Config file, line %d: INFO 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_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 next time for each beacon.
|
||||||
|
*/
|
||||||
|
|
||||||
|
now = time(NULL);
|
||||||
|
|
||||||
|
for (j=0; j<g_misc_config_p->num_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].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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Connect to GPS receiver if any tracker beacons are configured.
|
||||||
|
* If open fails, disable all tracker beacons.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if DEBUG_SIM
|
||||||
|
|
||||||
|
g_using_gps = 1;
|
||||||
|
|
||||||
|
#elif ENABLE_GPS
|
||||||
|
|
||||||
|
if (g_using_gps > 0) {
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = dwgps_init();
|
||||||
|
if (err != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("All tracker beacons disabled.\n");
|
||||||
|
g_using_gps = 0;
|
||||||
|
|
||||||
|
for (j=0; j<g_misc_config_p->num_beacons; j++) {
|
||||||
|
if (g_misc_config_p->beacon[j].btype == BEACON_TRACKER) {
|
||||||
|
g_misc_config_p->beacon[j].btype = BEACON_IGNORE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Start up thread for processing only if at least one is valid.
|
||||||
|
*/
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
for (j=0; j<g_misc_config_p->num_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 KNOTS_TO_MPH 1.150779
|
||||||
|
|
||||||
|
#define MIN(x,y) ((x) < (y) ? (x) : (y))
|
||||||
|
|
||||||
|
|
||||||
|
/* Difference between two angles. */
|
||||||
|
|
||||||
|
static inline float heading_change (float a, float b)
|
||||||
|
{
|
||||||
|
float diff;
|
||||||
|
|
||||||
|
diff = fabs(a - b);
|
||||||
|
if (diff <= 180.)
|
||||||
|
return (diff);
|
||||||
|
else
|
||||||
|
return (360. - diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
static unsigned __stdcall beacon_thread (void *arg)
|
||||||
|
#else
|
||||||
|
static void * beacon_thread (void *arg)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
int j;
|
||||||
|
time_t earliest;
|
||||||
|
time_t now;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Information from GPS.
|
||||||
|
*/
|
||||||
|
int fix = 0; /* 0 = none, 2 = 2D, 3 = 3D */
|
||||||
|
double my_lat = 0; /* degrees */
|
||||||
|
double my_lon = 0;
|
||||||
|
float my_course = 0; /* degrees */
|
||||||
|
float my_speed_knots = 0;
|
||||||
|
float my_speed_mph = 0;
|
||||||
|
float my_alt = 0; /* meters */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SmartBeaconing state.
|
||||||
|
*/
|
||||||
|
time_t sb_prev_time = 0; /* Time of most recent transmission. */
|
||||||
|
float sb_prev_course = 0; /* Most recent course reported. */
|
||||||
|
//float sb_prev_speed_mph; /* Most recent speed reported. */
|
||||||
|
int sb_every; /* Calculated time between transmissions. */
|
||||||
|
|
||||||
|
|
||||||
|
#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
|
||||||
|
now = time(NULL);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
|
||||||
|
assert (g_misc_config_p->num_beacons >= 1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sleep until time for the earliest scheduled or
|
||||||
|
* the soonest we could transmit due to corner pegging.
|
||||||
|
*/
|
||||||
|
|
||||||
|
earliest = g_misc_config_p->beacon[0].next;
|
||||||
|
for (j=1; j<g_misc_config_p->num_beacons; j++) {
|
||||||
|
if (g_misc_config_p->beacon[j].btype == BEACON_IGNORE)
|
||||||
|
continue;
|
||||||
|
earliest = MIN(g_misc_config_p->beacon[j].next, earliest);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_misc_config_p->sb_configured && g_using_gps) {
|
||||||
|
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 DEBUG_SIM
|
||||||
|
FILE *fp;
|
||||||
|
char cs[40];
|
||||||
|
|
||||||
|
fp = fopen ("c:\\cygwin\\tmp\\cs", "r");
|
||||||
|
if (fp != NULL) {
|
||||||
|
fscanf (fp, "%f %f", &my_course, &my_speed_knots);
|
||||||
|
fclose (fp);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fprintf (stderr, "Can't read /tmp/cs.\n");
|
||||||
|
}
|
||||||
|
fix = 3;
|
||||||
|
my_speed_mph = KNOTS_TO_MPH * my_speed_knots;
|
||||||
|
my_lat = 42.99;
|
||||||
|
my_lon = 71.99;
|
||||||
|
my_alt = 100;
|
||||||
|
#else
|
||||||
|
if (g_using_gps) {
|
||||||
|
|
||||||
|
fix = dwgps_read (&my_lat, &my_lon, &my_speed_knots, &my_course, &my_alt);
|
||||||
|
my_speed_mph = KNOTS_TO_MPH * my_speed_knots;
|
||||||
|
|
||||||
|
/* Don't complain here for no fix. */
|
||||||
|
/* Possibly at the point where about to transmit. */
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Run SmartBeaconing calculation if configured and GPS data available.
|
||||||
|
*/
|
||||||
|
if (g_misc_config_p->sb_configured && g_using_gps && fix >= 2) {
|
||||||
|
|
||||||
|
if (my_speed_mph > g_misc_config_p->sb_fast_speed) {
|
||||||
|
sb_every = g_misc_config_p->sb_fast_rate;
|
||||||
|
}
|
||||||
|
else if (my_speed_mph < g_misc_config_p->sb_slow_speed) {
|
||||||
|
sb_every = g_misc_config_p->sb_slow_rate;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Can't divide by 0 assuming sb_slow_speed > 0. */
|
||||||
|
sb_every = ( g_misc_config_p->sb_fast_rate * g_misc_config_p->sb_fast_speed ) / my_speed_mph;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG_SIM
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("SB: fast %d %d slow %d %d speed=%.1f every=%d\n",
|
||||||
|
g_misc_config_p->sb_fast_speed, g_misc_config_p->sb_fast_rate,
|
||||||
|
g_misc_config_p->sb_slow_speed, g_misc_config_p->sb_slow_rate,
|
||||||
|
my_speed_mph, sb_every);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test for "Corner Pegging" if moving.
|
||||||
|
*/
|
||||||
|
if (my_speed_mph >= 1.0) {
|
||||||
|
int turn_threshold = g_misc_config_p->sb_turn_angle +
|
||||||
|
g_misc_config_p->sb_turn_slope / my_speed_mph;
|
||||||
|
|
||||||
|
#if DEBUG_SIM
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("SB-moving: course %.0f prev %.0f thresh %d\n",
|
||||||
|
my_course, sb_prev_course, turn_threshold);
|
||||||
|
#endif
|
||||||
|
if (heading_change(my_course, sb_prev_course) > turn_threshold &&
|
||||||
|
now >= sb_prev_time + g_misc_config_p->sb_turn_time) {
|
||||||
|
|
||||||
|
/* Send it now. */
|
||||||
|
for (j=0; j<g_misc_config_p->num_beacons; j++) {
|
||||||
|
if (g_misc_config_p->beacon[j].btype == BEACON_TRACKER) {
|
||||||
|
g_misc_config_p->beacon[j].next = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} /* significant change in direction */
|
||||||
|
} /* is moving */
|
||||||
|
} /* apply SmartBeaconing */
|
||||||
|
|
||||||
|
|
||||||
|
for (j=0; j<g_misc_config_p->num_beacons; j++) {
|
||||||
|
|
||||||
|
if (g_misc_config_p->beacon[j].btype == BEACON_IGNORE)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (g_misc_config_p->beacon[j].next <= now) {
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
strcpy (mycall, "NOCALL");
|
||||||
|
|
||||||
|
if (g_misc_config_p->beacon[j].chan == -1) {
|
||||||
|
strcpy (mycall, g_digi_config_p->mycall[0]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
strcpy (mycall, g_digi_config_p->mycall[g_misc_config_p->beacon[j].chan]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prepare the monitor format header.
|
||||||
|
*/
|
||||||
|
|
||||||
|
strcpy (beacon_text, mycall);
|
||||||
|
strcat (beacon_text, ">");
|
||||||
|
sprintf (stemp, "%s%1d%1d", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION);
|
||||||
|
strcat (beacon_text, stemp);
|
||||||
|
if (g_misc_config_p->beacon[j].via) {
|
||||||
|
strcat (beacon_text, ",");
|
||||||
|
strcat (beacon_text, g_misc_config_p->beacon[j].via);
|
||||||
|
}
|
||||||
|
strcat (beacon_text, ":");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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].compress, g_misc_config_p->beacon[j].lat, g_misc_config_p->beacon[j].lon,
|
||||||
|
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,
|
||||||
|
0, 0, /* course, speed */
|
||||||
|
g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset,
|
||||||
|
g_misc_config_p->beacon[j].comment,
|
||||||
|
info);
|
||||||
|
strcat (beacon_text, info);
|
||||||
|
g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every;
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
0, 0, /* course, speed */
|
||||||
|
g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset, g_misc_config_p->beacon[j].comment,
|
||||||
|
info);
|
||||||
|
strcat (beacon_text, info);
|
||||||
|
g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BEACON_TRACKER:
|
||||||
|
|
||||||
|
if (fix >= 2) {
|
||||||
|
int coarse; /* APRS encoder wants 1 - 360. */
|
||||||
|
/* 0 means none or unknown. */
|
||||||
|
|
||||||
|
coarse = (int)roundf(my_course);
|
||||||
|
if (coarse == 0) {
|
||||||
|
coarse = 360;
|
||||||
|
}
|
||||||
|
encode_position (g_misc_config_p->beacon[j].compress,
|
||||||
|
my_lat, my_lon,
|
||||||
|
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(my_speed_knots),
|
||||||
|
g_misc_config_p->beacon[j].freq, g_misc_config_p->beacon[j].tone, g_misc_config_p->beacon[j].offset,
|
||||||
|
g_misc_config_p->beacon[j].comment,
|
||||||
|
info);
|
||||||
|
strcat (beacon_text, info);
|
||||||
|
|
||||||
|
/* Remember most recent tracker beacon. */
|
||||||
|
|
||||||
|
sb_prev_time = now;
|
||||||
|
sb_prev_course = my_course;
|
||||||
|
//sb_prev_speed_mph = my_speed_mph;
|
||||||
|
|
||||||
|
/* Calculate time for next transmission. */
|
||||||
|
if (g_misc_config_p->sb_configured) {
|
||||||
|
g_misc_config_p->beacon[j].next = now + sb_every;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g_misc_config_p->beacon[j].next = now + 2;
|
||||||
|
continue; /* No fix. Try again in a couple seconds. */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BEACON_CUSTOM:
|
||||||
|
|
||||||
|
if (g_misc_config_p->beacon[j].custom_info != NULL) {
|
||||||
|
strcat (beacon_text, g_misc_config_p->beacon[j].custom_info);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Internal error. custom_info is null. %s %d\n", __FILE__, __LINE__);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
g_misc_config_p->beacon[j].next = now + g_misc_config_p->beacon[j].every;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BEACON_IGNORE:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
|
||||||
|
} /* switch beacon type. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parse monitor format into form for transmission.
|
||||||
|
*/
|
||||||
|
pp = ax25_from_text (beacon_text, strict);
|
||||||
|
|
||||||
|
if (pp != NULL) {
|
||||||
|
|
||||||
|
/* Send to IGate server or radio. */
|
||||||
|
|
||||||
|
if (g_misc_config_p->beacon[j].chan == -1) {
|
||||||
|
#if 1
|
||||||
|
text_color_set(DW_COLOR_XMIT);
|
||||||
|
dw_printf ("[ig] %s\n", beacon_text);
|
||||||
|
#endif
|
||||||
|
igate_send_rec_packet (0, pp);
|
||||||
|
ax25_delete (pp);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tq_append (g_misc_config_p->beacon[j].chan, TQ_PRIO_1_LO, pp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* if time to send it */
|
||||||
|
|
||||||
|
} /* for each configured beacon */
|
||||||
|
|
||||||
|
} /* do forever */
|
||||||
|
|
||||||
|
} /* end beacon_thread */
|
||||||
|
|
||||||
|
/* end beacon.c */
|
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
/* beacon.h */
|
||||||
|
|
||||||
|
void beacon_init (struct misc_config_s *pconfig, struct digi_config_s *pdigi);
|
|
@ -0,0 +1,130 @@
|
||||||
|
|
||||||
|
/*----------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* 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 "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 };
|
||||||
|
|
||||||
|
#define MAX_BEACONS 30
|
||||||
|
|
||||||
|
struct misc_config_s {
|
||||||
|
|
||||||
|
int num_channels; /* Number of radio channels. */
|
||||||
|
|
||||||
|
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[40]; /* Serial port name for our end of the */
|
||||||
|
/* virtual null modem for native Windows apps. */
|
||||||
|
|
||||||
|
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 */
|
||||||
|
|
||||||
|
|
||||||
|
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. */
|
||||||
|
|
||||||
|
int chan; /* Send to Channel for transmission. -1 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. */
|
||||||
|
|
||||||
|
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. */
|
||||||
|
|
||||||
|
double lat; /* Latitude and longitude. */
|
||||||
|
double lon;
|
||||||
|
|
||||||
|
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. */
|
||||||
|
|
||||||
|
|
||||||
|
} 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 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 */
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
/* decode_aprs.h */
|
||||||
|
|
||||||
|
extern void decode_aprs (packet_t pp);
|
||||||
|
|
|
@ -0,0 +1,243 @@
|
||||||
|
//
|
||||||
|
// 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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* 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 <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
|
||||||
|
#include "ax25_pad.h"
|
||||||
|
#include "dedupe.h"
|
||||||
|
#include "fcs_calc.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* 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<HISTORY_MAX; j++) {
|
||||||
|
if (history[j].time_stamp >= now - history_time &&
|
||||||
|
history[j].checksum == crc &&
|
||||||
|
history[j].xmit_channel == chan) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* end dedupe.c */
|
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
|
||||||
|
void dedupe_init (int ttl);
|
||||||
|
|
||||||
|
void dedupe_remember (packet_t pp, int chan);
|
||||||
|
|
||||||
|
int dedupe_check (packet_t pp, int chan);
|
||||||
|
|
||||||
|
|
||||||
|
/* end dedupe.h */
|
|
@ -0,0 +1,570 @@
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011,2012,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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
// #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.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 <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "audio.h"
|
||||||
|
#include "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"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Properties of the radio channels.
|
||||||
|
|
||||||
|
static struct audio_s modem;
|
||||||
|
|
||||||
|
// Current state of all the decoders.
|
||||||
|
|
||||||
|
static struct demodulator_state_s demodulator_state[MAX_CHANS][MAX_SUBCHANS];
|
||||||
|
|
||||||
|
|
||||||
|
#define UPSAMPLE 2
|
||||||
|
|
||||||
|
static int sample_sum[MAX_CHANS][MAX_SUBCHANS];
|
||||||
|
static int sample_count[MAX_CHANS][MAX_SUBCHANS];
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: demod_init
|
||||||
|
*
|
||||||
|
* Purpose: Initialize the demodulator(s) used for reception.
|
||||||
|
*
|
||||||
|
* Inputs: pa - Pointer to modem_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. */
|
||||||
|
int subchan; /* for each modem for channel. */
|
||||||
|
char profile;
|
||||||
|
//float fc;
|
||||||
|
|
||||||
|
struct demodulator_state_s *D;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Save parameters for later use.
|
||||||
|
*/
|
||||||
|
memcpy (&modem, pa, sizeof(modem));
|
||||||
|
|
||||||
|
for (chan = 0; chan < modem.num_channels; chan++) {
|
||||||
|
|
||||||
|
assert (chan >= 0 && chan < MAX_CHANS);
|
||||||
|
|
||||||
|
switch (modem.modem_type[chan]) {
|
||||||
|
|
||||||
|
case AFSK:
|
||||||
|
/*
|
||||||
|
* Pick a good default demodulator if none specified.
|
||||||
|
*/
|
||||||
|
if (strlen(modem.profiles[chan]) == 0) {
|
||||||
|
|
||||||
|
if (modem.baud[chan] < 600) {
|
||||||
|
|
||||||
|
/* This has been optimized for 300 baud. */
|
||||||
|
|
||||||
|
strcpy (modem.profiles[chan], "D");
|
||||||
|
if (modem.samples_per_sec > 40000) {
|
||||||
|
modem.decimate[chan] = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
#if __arm__
|
||||||
|
/* We probably don't have a lot of CPU power available. */
|
||||||
|
|
||||||
|
if (modem.baud[chan] == FFF_BAUD &&
|
||||||
|
modem.mark_freq[chan] == FFF_MARK_FREQ &&
|
||||||
|
modem.space_freq[chan] == FFF_SPACE_FREQ &&
|
||||||
|
modem.samples_per_sec == FFF_SAMPLES_PER_SEC) {
|
||||||
|
|
||||||
|
modem.profiles[chan][0] = FFF_PROFILE;
|
||||||
|
modem.profiles[chan][1] = '\0';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
strcpy (modem.profiles[chan], "A");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
strcpy (modem.profiles[chan], "C");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modem.decimate[chan] == 0) modem.decimate[chan] = 1;
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("Channel %d: %d baud, AFSK %d & %d Hz, %s, %d sample rate",
|
||||||
|
chan, modem.baud[chan],
|
||||||
|
modem.mark_freq[chan], modem.space_freq[chan],
|
||||||
|
modem.profiles[chan],
|
||||||
|
modem.samples_per_sec);
|
||||||
|
if (modem.decimate[chan] != 1)
|
||||||
|
dw_printf (" / %d", modem.decimate[chan]);
|
||||||
|
dw_printf (".\n");
|
||||||
|
|
||||||
|
if (strlen(modem.profiles[chan]) > 1) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Multiple profiles, usually for 1200 baud.
|
||||||
|
*/
|
||||||
|
assert (modem.num_subchan[chan] == strlen(modem.profiles[chan]));
|
||||||
|
|
||||||
|
for (subchan = 0; subchan < modem.num_subchan[chan]; subchan++) {
|
||||||
|
|
||||||
|
int mark, space;
|
||||||
|
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
|
||||||
|
D = &demodulator_state[chan][subchan];
|
||||||
|
|
||||||
|
profile = modem.profiles[chan][subchan];
|
||||||
|
mark = modem.mark_freq[chan];
|
||||||
|
space = modem.space_freq[chan];
|
||||||
|
|
||||||
|
if (modem.num_subchan[chan] != 1) {
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf (" %d.%d: %c %d & %d\n", chan, subchan, profile, mark, space);
|
||||||
|
}
|
||||||
|
|
||||||
|
demod_afsk_init (modem.samples_per_sec / modem.decimate[chan], modem.baud[chan],
|
||||||
|
mark, space,
|
||||||
|
profile,
|
||||||
|
D);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/*
|
||||||
|
* Possibly multiple frequency pairs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
assert (modem.num_freq[chan] == modem.num_subchan[chan]);
|
||||||
|
assert (strlen(modem.profiles[chan]) == 1);
|
||||||
|
|
||||||
|
for (subchan = 0; subchan < modem.num_freq[chan]; subchan++) {
|
||||||
|
|
||||||
|
int mark, space, k;
|
||||||
|
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
|
||||||
|
D = &demodulator_state[chan][subchan];
|
||||||
|
|
||||||
|
profile = modem.profiles[chan][0];
|
||||||
|
|
||||||
|
k = subchan * modem.offset[chan] - ((modem.num_subchan[chan] - 1) * modem.offset[chan]) / 2;
|
||||||
|
mark = modem.mark_freq[chan] + k;
|
||||||
|
space = modem.space_freq[chan] + k;
|
||||||
|
|
||||||
|
if (modem.num_subchan[chan] != 1) {
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf (" %d.%d: %c %d & %d\n", chan, subchan, profile, mark, space);
|
||||||
|
}
|
||||||
|
|
||||||
|
demod_afsk_init (modem.samples_per_sec / modem.decimate[chan], modem.baud[chan],
|
||||||
|
mark, space,
|
||||||
|
profile,
|
||||||
|
D);
|
||||||
|
|
||||||
|
} /* for subchan */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("Channel %d: %d baud, %d sample rate x %d.\n",
|
||||||
|
chan, modem.baud[chan],
|
||||||
|
modem.samples_per_sec, UPSAMPLE);
|
||||||
|
|
||||||
|
subchan = 0;
|
||||||
|
D = &demodulator_state[chan][subchan];
|
||||||
|
|
||||||
|
demod_9600_init (UPSAMPLE * modem.samples_per_sec, modem.baud[chan], D);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
} /* switch on modulation type. */
|
||||||
|
|
||||||
|
} /* for chan ... */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for (chan=0; chan<MAX_CHANS; chan++)
|
||||||
|
{
|
||||||
|
for (subchan = 0; subchan < modem.num_subchan[chan]; subchan++) {
|
||||||
|
struct demodulator_state_s *D;
|
||||||
|
|
||||||
|
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
|
||||||
|
|
||||||
|
sample_sum[chan][subchan] = 0;
|
||||||
|
sample_count[chan][subchan] = subchan; /* stagger */
|
||||||
|
|
||||||
|
D = &demodulator_state[chan][subchan];
|
||||||
|
|
||||||
|
/* For collecting input signal level. */
|
||||||
|
|
||||||
|
D->lev_period = modem.samples_per_sec * 0.100; // Samples in 0.100 seconds.
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
|
||||||
|
} /* end demod_init */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: demod_get_sample
|
||||||
|
*
|
||||||
|
* Purpose: Get one audio sample fromt the sound input source.
|
||||||
|
*
|
||||||
|
* Returns: -32768 .. 32767 for a valid audio sample.
|
||||||
|
* 256*256 for end of file or other error.
|
||||||
|
*
|
||||||
|
* Global In: modem.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 (void)
|
||||||
|
{
|
||||||
|
int x1, x2;
|
||||||
|
signed short sam; /* short to force sign extention. */
|
||||||
|
|
||||||
|
|
||||||
|
assert (modem.bits_per_sample == 8 || modem.bits_per_sample == 16);
|
||||||
|
|
||||||
|
|
||||||
|
if (modem.bits_per_sample == 8) {
|
||||||
|
|
||||||
|
x1 = audio_get();
|
||||||
|
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(); /* lower byte first */
|
||||||
|
if (x1 < 0) return(FSK_READ_ERR);
|
||||||
|
|
||||||
|
x2 = audio_get();
|
||||||
|
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, abs_fsam;
|
||||||
|
int k;
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG4
|
||||||
|
static FILE *demod_log_fp = NULL;
|
||||||
|
static int seq = 0; /* for log file name */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int j;
|
||||||
|
int demod_data;
|
||||||
|
struct demodulator_state_s *D;
|
||||||
|
|
||||||
|
assert (chan >= 0 && chan < MAX_CHANS);
|
||||||
|
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
|
||||||
|
|
||||||
|
D = &demodulator_state[chan][subchan];
|
||||||
|
|
||||||
|
|
||||||
|
#if 1 /* TODO: common level detection. */
|
||||||
|
|
||||||
|
/* Scale to nice number, TODO: range -1.0 to +1.0, not 2. */
|
||||||
|
|
||||||
|
fsam = sam / 16384.0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Accumulate measure of the input signal level.
|
||||||
|
*/
|
||||||
|
abs_fsam = fsam >= 0 ? fsam : -fsam;
|
||||||
|
|
||||||
|
if (abs_fsam > D->lev_peak_acc) {
|
||||||
|
D->lev_peak_acc = abs_fsam;
|
||||||
|
}
|
||||||
|
D->lev_sum_acc += abs_fsam;
|
||||||
|
|
||||||
|
D->lev_count++;
|
||||||
|
if (D->lev_count >= D->lev_period) {
|
||||||
|
D->lev_prev_peak = D->lev_last_peak;
|
||||||
|
D->lev_last_peak = D->lev_peak_acc;
|
||||||
|
D->lev_peak_acc = 0;
|
||||||
|
|
||||||
|
D->lev_prev_ave = D->lev_last_ave;
|
||||||
|
D->lev_last_ave = D->lev_sum_acc / D->lev_count;
|
||||||
|
D->lev_sum_acc = 0;
|
||||||
|
|
||||||
|
D->lev_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Select decoder based on modulation type.
|
||||||
|
*/
|
||||||
|
|
||||||
|
switch (modem.modem_type[chan]) {
|
||||||
|
|
||||||
|
case AFSK:
|
||||||
|
|
||||||
|
if (modem.decimate[chan] > 1) {
|
||||||
|
|
||||||
|
sample_sum[chan][subchan] += sam;
|
||||||
|
sample_count[chan][subchan]++;
|
||||||
|
if (sample_count[chan][subchan] >= modem.decimate[chan]) {
|
||||||
|
demod_afsk_process_sample (chan, subchan, sample_sum[chan][subchan] / modem.decimate[chan], D);
|
||||||
|
sample_sum[chan][subchan] = 0;
|
||||||
|
sample_count[chan][subchan] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
demod_afsk_process_sample (chan, subchan, sam, D);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
#define ZEROSTUFF 1
|
||||||
|
|
||||||
|
|
||||||
|
#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<UPSAMPLE; k++) {
|
||||||
|
demod_9600_process_sample (chan, 0, D);
|
||||||
|
}
|
||||||
|
demod_9600_process_sample (chan, sam*UPSAMPLE, D);
|
||||||
|
#else
|
||||||
|
/* Linear interpolation. */
|
||||||
|
static int prev_sam;
|
||||||
|
switch (UPSAMPLE) {
|
||||||
|
case 1:
|
||||||
|
demod_9600_process_sample (chan, sam);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
demod_9600_process_sample (chan, (prev_sam + sam) / 2, D);
|
||||||
|
demod_9600_process_sample (chan, sam, D);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
demod_9600_process_sample (chan, (2 * prev_sam + sam) / 3, D);
|
||||||
|
demod_9600_process_sample (chan, (prev_sam + 2 * sam) / 3, D);
|
||||||
|
demod_9600_process_sample (chan, sam, D);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
demod_9600_process_sample (chan, (3 * prev_sam + sam) / 4, D);
|
||||||
|
demod_9600_process_sample (chan, (prev_sam + sam) / 2, D);
|
||||||
|
demod_9600_process_sample (chan, (prev_sam + 3 * sam) / 4, D);
|
||||||
|
demod_9600_process_sample (chan, sam, D);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert (0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prev_sam = sam;
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
} /* end demod_process_sample */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: fsk_demod_print_agc
|
||||||
|
*
|
||||||
|
* Purpose: Print information about input signal amplitude.
|
||||||
|
* This will be useful for adjusting transmitter audio levels.
|
||||||
|
* We also want to avoid having an input level so high
|
||||||
|
* that the A/D converter "clips" the signal.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Inputs: chan - Audio channel. 0 for left, 1 for right.
|
||||||
|
*
|
||||||
|
* Returns: None
|
||||||
|
*
|
||||||
|
* Descripion: Not sure what to use for final form.
|
||||||
|
* For now display the AGC peaks for both tones.
|
||||||
|
* This will be called at the end of a frame.
|
||||||
|
*
|
||||||
|
* Future: Come up with a sensible scale and add command line option.
|
||||||
|
* Probably makes more sense to return a single number
|
||||||
|
* and let the caller print it.
|
||||||
|
* Just an experiment for now.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
void demod_print_agc (int chan, int subchan)
|
||||||
|
{
|
||||||
|
|
||||||
|
struct demodulator_state_s *D;
|
||||||
|
|
||||||
|
|
||||||
|
assert (chan >= 0 && chan < MAX_CHANS);
|
||||||
|
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
|
||||||
|
|
||||||
|
D = &demodulator_state[chan][subchan];
|
||||||
|
|
||||||
|
dw_printf ("%d\n", (int)((D->lev_last_peak + D->lev_prev_peak)*50));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//dw_printf ("Peak= %.2f, %.2f Ave= %.2f, %.2f AGC M= %.2f / %.2f S= %.2f / %.2f\n",
|
||||||
|
// D->lev_last_peak, D->lev_prev_peak, D->lev_last_ave, D->lev_prev_ave,
|
||||||
|
// D->m_peak, D->m_valley, D->s_peak, D->s_valley);
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Resulting scale is 0 to almost 100. */
|
||||||
|
/* Cranking up the input level produces no more than 97 or 98. */
|
||||||
|
/* We currently produce a message when this goes over 90. */
|
||||||
|
|
||||||
|
int demod_get_audio_level (int chan, int subchan)
|
||||||
|
{
|
||||||
|
struct demodulator_state_s *D;
|
||||||
|
|
||||||
|
|
||||||
|
assert (chan >= 0 && chan < MAX_CHANS);
|
||||||
|
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
|
||||||
|
|
||||||
|
D = &demodulator_state[chan][subchan];
|
||||||
|
|
||||||
|
return ( (int) ((D->lev_last_peak + D->lev_prev_peak) * 50 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* end demod.c */
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
|
||||||
|
/* demod.h */
|
||||||
|
|
||||||
|
#include "audio.h" /* for struct audio_s */
|
||||||
|
|
||||||
|
|
||||||
|
int demod_init (struct audio_s *pa);
|
||||||
|
|
||||||
|
int demod_get_sample (void);
|
||||||
|
|
||||||
|
void demod_process_sample (int chan, int subchan, int sam);
|
||||||
|
|
||||||
|
void demod_print_agc (int chan, int subchan);
|
||||||
|
|
||||||
|
int demod_get_audio_level (int chan, int subchan);
|
|
@ -0,0 +1,463 @@
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011,2012,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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
// #define DEBUG5 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 <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "tune.h"
|
||||||
|
#include "fsk_demod_state.h"
|
||||||
|
#include "hdlc_rec.h"
|
||||||
|
#include "demod_9600.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
#include "dsp.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* Add sample to buffer and shift the rest down. */
|
||||||
|
|
||||||
|
__attribute__((hot))
|
||||||
|
static inline void push_sample (float val, float *buff, int size)
|
||||||
|
{
|
||||||
|
int j;
|
||||||
|
|
||||||
|
// TODO: memmove any faster?
|
||||||
|
for (j = size - 1; j >= 1; j--) {
|
||||||
|
buff[j] = buff[j-1];
|
||||||
|
}
|
||||||
|
buff[0] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* FIR filter kernel. */
|
||||||
|
|
||||||
|
__attribute__((hot))
|
||||||
|
static inline float convolve (const float *data, const float *filter, int filter_size)
|
||||||
|
{
|
||||||
|
float sum = 0;
|
||||||
|
int j;
|
||||||
|
|
||||||
|
for (j=0; j<filter_size; j++) {
|
||||||
|
sum += filter[j] * data[j];
|
||||||
|
}
|
||||||
|
return (sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Automatic gain control. */
|
||||||
|
/* Result should settle down to 1 unit peak to peak. i.e. -0.5 to +0.5 */
|
||||||
|
|
||||||
|
__attribute__((hot))
|
||||||
|
static inline float agc (float in, float fast_attack, float slow_decay, float *ppeak, float *pvalley)
|
||||||
|
{
|
||||||
|
if (in >= *ppeak) {
|
||||||
|
*ppeak = in * fast_attack + *ppeak * (1. - fast_attack);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
*ppeak = in * slow_decay + *ppeak * (1. - slow_decay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in <= *pvalley) {
|
||||||
|
*pvalley = in * fast_attack + *pvalley * (1. - fast_attack);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
*pvalley = in * slow_decay + *pvalley * (1. - slow_decay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*ppeak > *pvalley) {
|
||||||
|
return ((in - 0.5 * (*ppeak + *pvalley)) / (*ppeak - *pvalley));
|
||||||
|
}
|
||||||
|
return (0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: demod_9600_init
|
||||||
|
*
|
||||||
|
* Purpose: Initialize the 9600 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;
|
||||||
|
|
||||||
|
memset (D, 0, sizeof(struct demodulator_state_s));
|
||||||
|
|
||||||
|
//dw_printf ("demod_9600_init(rate=%d, baud=%d, D ptr)\n", samples_per_sec, baud);
|
||||||
|
|
||||||
|
D->pll_step_per_sample =
|
||||||
|
(int) round(TICKS_PER_PLL_CYCLE * (double) baud / (double)samples_per_sec);
|
||||||
|
|
||||||
|
D->filter_len_bits = 72 * 9600.0 / (44100.0 * 2.0);
|
||||||
|
D->lp_filter_size = (int) (( D->filter_len_bits * (float)samples_per_sec / baud) + 0.5);
|
||||||
|
#if TUNE_LP_FILTER_SIZE
|
||||||
|
D->lp_filter_size = TUNE_LP_FILTER_SIZE;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
D->lpf_baud = 0.59;
|
||||||
|
#ifdef TUNE_LPF_BAUD
|
||||||
|
D->lpf_baud = TUNE_LPF_BAUD;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
D->agc_fast_attack = 0.080;
|
||||||
|
#ifdef TUNE_AGC_FAST
|
||||||
|
D->agc_fast_attack = TUNE_AGC_FAST;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
D->agc_slow_decay = 0.00012;
|
||||||
|
#ifdef TUNE_AGC_SLOW
|
||||||
|
D->agc_slow_decay = TUNE_AGC_SLOW;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
D->pll_locked_inertia = 0.88;
|
||||||
|
D->pll_searching_inertia = 0.67;
|
||||||
|
|
||||||
|
#if defined(TUNE_PLL_LOCKED) && defined(TUNE_PLL_SEARCHING)
|
||||||
|
D->pll_locked_inertia = TUNE_PLL_LOCKED;
|
||||||
|
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, BP_WINDOW_HAMMING);
|
||||||
|
|
||||||
|
} /* 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
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* TODO: This works in a simulated environment but it has not yet
|
||||||
|
* been successfully tested for interoperability with
|
||||||
|
* other systems over the air.
|
||||||
|
* That's why it is not mentioned in documentation.
|
||||||
|
*
|
||||||
|
* The signal from the radio speaker does NOT have
|
||||||
|
* enough bandwidth and the waveform is hopelessly distorted.
|
||||||
|
* It will be necessary to obtain a signal right after
|
||||||
|
* the discriminator of the receiver.
|
||||||
|
* It will probably also be necessary to tap directly into
|
||||||
|
* the modulator, bypassing the microphone amplifier.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
__attribute__((hot))
|
||||||
|
void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D)
|
||||||
|
{
|
||||||
|
|
||||||
|
float fsam;
|
||||||
|
float abs_fsam;
|
||||||
|
float amp;
|
||||||
|
float demod_out;
|
||||||
|
|
||||||
|
#if DEBUG5
|
||||||
|
static FILE *demod_log_fp = NULL;
|
||||||
|
static int seq = 0; /* for log file name */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int j;
|
||||||
|
int subchan = 0;
|
||||||
|
int demod_data; /* Still scrambled. */
|
||||||
|
static int descram; /* Data bit de-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, range -1.0 to +1.0. */
|
||||||
|
|
||||||
|
fsam = sam / 32768.0;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 = 2.0 * agc (amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley));
|
||||||
|
|
||||||
|
//dw_printf ("peak=%.2f valley=%.2f amp=%.2f norm=%.2f\n", D->m_peak, D->m_valley, amp, norm);
|
||||||
|
|
||||||
|
/* Throw in a little Hysteresis??? */
|
||||||
|
/* (Not to be confused with Hysteria.) */
|
||||||
|
|
||||||
|
if (demod_out > 0.01) {
|
||||||
|
demod_data = 1;
|
||||||
|
}
|
||||||
|
else if (demod_out < -0.01) {
|
||||||
|
demod_data = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
demod_data = D->prev_demod_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Next, 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.
|
||||||
|
*
|
||||||
|
* This was optimized for 1200 baud AFSK. There might be some opportunity
|
||||||
|
* for improvement here.
|
||||||
|
*/
|
||||||
|
D->prev_d_c_pll = D->data_clock_pll;
|
||||||
|
D->data_clock_pll += D->pll_step_per_sample;
|
||||||
|
|
||||||
|
if (D->data_clock_pll < 0 && D->prev_d_c_pll > 0) {
|
||||||
|
|
||||||
|
/* Overflow. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* At this point, we need to descramble the data as
|
||||||
|
* in hardware based designs by G3RUH and K9NG.
|
||||||
|
*
|
||||||
|
* http://www.amsat.org/amsat/articles/g3ruh/109/fig03.gif
|
||||||
|
*/
|
||||||
|
|
||||||
|
//assert (modem.modem_type[chan] == SCRAMBLE);
|
||||||
|
|
||||||
|
//if (modem.modem_type[chan] == SCRAMBLE) {
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: This needs to be rearranged to allow attempted "fixing"
|
||||||
|
// of corrupted bits later. We need to store the original
|
||||||
|
// received bits and do the descrambling after attempted
|
||||||
|
// repairs. However, we also need to descramble now to
|
||||||
|
// detect the flag sequences.
|
||||||
|
|
||||||
|
|
||||||
|
descram = descramble (demod_data, &(D->lfsr));
|
||||||
|
#if SLICENDICE
|
||||||
|
// TODO: Needs more thought.
|
||||||
|
// Does it even make sense to remember demod_out in this case?
|
||||||
|
// We would need to do the re-thresholding before descrambling.
|
||||||
|
//hdlc_rec_bit_sam (chan, subchan, descram, descram ? 1.0 : -1.0);
|
||||||
|
#else
|
||||||
|
|
||||||
|
// TODO: raw received bit and true later.
|
||||||
|
|
||||||
|
hdlc_rec_bit (chan, subchan, descram, 0, D->lfsr);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//D->prev_descram = descram;
|
||||||
|
//}
|
||||||
|
//else {
|
||||||
|
/* Baseband signal for completeness - not in common use. */
|
||||||
|
#if SLICENDICE
|
||||||
|
//hdlc_rec_bit_sam (chan, subchan, demod_data, demod_data ? 1.0 : -1.0);
|
||||||
|
#else
|
||||||
|
//hdlc_rec_bit (chan, subchan, demod_data);
|
||||||
|
#endif
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (demod_data != D->prev_demod_data) {
|
||||||
|
|
||||||
|
// Note: Test for this demodulator, not overall for channel.
|
||||||
|
|
||||||
|
if (hdlc_rec_data_detect_1 (chan, subchan)) {
|
||||||
|
D->data_clock_pll = (int)(D->data_clock_pll * D->pll_locked_inertia);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
D->data_clock_pll = (int)(D->data_clock_pll * D->pll_searching_inertia);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG5
|
||||||
|
|
||||||
|
//if (chan == 0) {
|
||||||
|
if (hdlc_rec_data_detect_1 (chan,subchan)) {
|
||||||
|
|
||||||
|
char fname[30];
|
||||||
|
|
||||||
|
|
||||||
|
if (demod_log_fp == NULL) {
|
||||||
|
seq++;
|
||||||
|
sprintf (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.5 * fsam + 3.5,
|
||||||
|
0.5 * D->m_peak + 3.5,
|
||||||
|
0.5 * D->m_valley + 3.5,
|
||||||
|
0.5 * 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->prev_demod_data = demod_data;
|
||||||
|
|
||||||
|
} /* end demod_9600_process_sample */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* end demod_9600.c */
|
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
|
||||||
|
/* demod_9600.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);
|
||||||
|
}
|
|
@ -0,0 +1,977 @@
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011,2012,2013,2014 John Langner, WB2OSZ
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
// #define DEBUG1 1 /* display debugging info */
|
||||||
|
|
||||||
|
// #define DEBUG3 1 /* print carrier detect changes. */
|
||||||
|
|
||||||
|
// #define DEBUG4 1 /* capture 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 <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "audio.h"
|
||||||
|
//#include "fsk_demod.h"
|
||||||
|
//#include "gen_tone.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))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Quick approximation to sqrt(x*x+y*y) */
|
||||||
|
/* No benefit for regular PC. */
|
||||||
|
/* Should help with microcomputer platform. */
|
||||||
|
|
||||||
|
|
||||||
|
__attribute__((hot))
|
||||||
|
static inline float z (float x, float y)
|
||||||
|
{
|
||||||
|
x = fabsf(x);
|
||||||
|
y = fabsf(y);
|
||||||
|
|
||||||
|
if (x > y) {
|
||||||
|
return (x * .941246 + y * .41);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return (y * .941246 + x * .41);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add sample to buffer and shift the rest down. */
|
||||||
|
|
||||||
|
__attribute__((hot))
|
||||||
|
static inline void push_sample (float val, float *buff, int size)
|
||||||
|
{
|
||||||
|
int j;
|
||||||
|
|
||||||
|
// TODO: memmove any faster?
|
||||||
|
for (j = size - 1; j >= 1; j--) {
|
||||||
|
buff[j] = buff[j-1];
|
||||||
|
}
|
||||||
|
buff[0] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* FIR filter kernel. */
|
||||||
|
|
||||||
|
__attribute__((hot))
|
||||||
|
static inline float convolve (const float *data, const float *filter, int filter_size)
|
||||||
|
{
|
||||||
|
float sum = 0;
|
||||||
|
int j;
|
||||||
|
|
||||||
|
for (j=0; j<filter_size; j++) {
|
||||||
|
sum += filter[j] * data[j];
|
||||||
|
}
|
||||||
|
return (sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Automatic gain control. */
|
||||||
|
/* Result should settle down to 1 unit peak to peak. i.e. -0.5 to +0.5 */
|
||||||
|
|
||||||
|
__attribute__((hot))
|
||||||
|
static inline float agc (float in, float fast_attack, float slow_decay, float *ppeak, float *pvalley)
|
||||||
|
{
|
||||||
|
if (in >= *ppeak) {
|
||||||
|
*ppeak = in * fast_attack + *ppeak * (1. - fast_attack);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
*ppeak = in * slow_decay + *ppeak * (1. - slow_decay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in <= *pvalley) {
|
||||||
|
*pvalley = in * fast_attack + *pvalley * (1. - fast_attack);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
*pvalley = in * slow_decay + *pvalley * (1. - slow_decay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*ppeak > *pvalley) {
|
||||||
|
return ((in - 0.5 * (*ppeak + *pvalley)) / (*ppeak - *pvalley));
|
||||||
|
}
|
||||||
|
return (0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* 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));
|
||||||
|
|
||||||
|
#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 (toupper(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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile == 'a' || profile == 'A' || profile == 'f' || profile == '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->filter_len_bits = 1.415; /* 52 @ 44100, 1200 */
|
||||||
|
D->bp_window = BP_WINDOW_TRUNCATED;
|
||||||
|
D->lpf_use_fir = 0;
|
||||||
|
D->lpf_iir = 0.195;
|
||||||
|
D->lpf_baud = 0;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
else if (profile == 'b' || profile == 'B') {
|
||||||
|
|
||||||
|
/* Original bandpass. Use FIR lowpass instead. */
|
||||||
|
|
||||||
|
D->filter_len_bits = 1.415; /* 52 @ 44100, 1200 */
|
||||||
|
D->bp_window = BP_WINDOW_TRUNCATED;
|
||||||
|
D->lpf_use_fir = 1;
|
||||||
|
D->lpf_iir = 0;
|
||||||
|
D->lpf_baud = 1.09;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
else if (profile == 'c' || profile == 'C') {
|
||||||
|
|
||||||
|
/* Cosine window, 76 taps for bandpass, FIR lowpass. */
|
||||||
|
|
||||||
|
D->filter_len_bits = 2.068; /* 76 @ 44100, 1200 */
|
||||||
|
D->bp_window = BP_WINDOW_COSINE;
|
||||||
|
D->lpf_use_fir = 1;
|
||||||
|
D->lpf_iir = 0;
|
||||||
|
D->lpf_baud = 1.09;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
else if (profile == 'd' || profile == 'D') {
|
||||||
|
|
||||||
|
/* Prefilter, Cosine window, FIR lowpass. Tweeked for 300 baud. */
|
||||||
|
|
||||||
|
D->use_prefilter = 1; /* first, a bandpass filter. */
|
||||||
|
D->prefilter_baud = 0.87; /* Cosine window. */
|
||||||
|
D->filter_len_bits = 1.857; /* 91 @ 44100/3, 300 */
|
||||||
|
D->bp_window = BP_WINDOW_COSINE;
|
||||||
|
D->lpf_use_fir = 1;
|
||||||
|
D->lpf_iir = 0;
|
||||||
|
D->lpf_baud = 1.10;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Invalid filter profile = %c\n", profile);
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#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));
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* My initial guess at length of filter was about one bit time.
|
||||||
|
* By trial and error, the optimal value was found to somewhat longer.
|
||||||
|
* This was optimized for 44,100 sample rate, 1200 baud, 1200/2200 Hz.
|
||||||
|
* More experimentation is needed for other situations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
D->ms_filter_size = (int) round( D->filter_len_bits * (float)samples_per_sec / (float)baud );
|
||||||
|
|
||||||
|
/* Experiment with other sizes. */
|
||||||
|
|
||||||
|
#if defined(TUNE_MS_FILTER_SIZE)
|
||||||
|
D->ms_filter_size = TUNE_MS_FILTER_SIZE;
|
||||||
|
#endif
|
||||||
|
D->lp_filter_size = D->ms_filter_size;
|
||||||
|
|
||||||
|
assert (D->ms_filter_size >= 4);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For narrow AFSK (e.g. 200 Hz shift), it might be beneficial to
|
||||||
|
* have a bandpass filter before the mark/space detector.
|
||||||
|
* For now, make it the same number of taps for simplicity.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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->ms_filter_size, BP_WINDOW_HAMMING);
|
||||||
|
//gen_bandpass (f1, f2, D->pre_filter, D->ms_filter_size, BP_WINDOW_BLACKMAN);
|
||||||
|
gen_bandpass (f1, f2, D->pre_filter, D->ms_filter_size, BP_WINDOW_COSINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
for (j=0; j<D->ms_filter_size; j++) {
|
||||||
|
float am;
|
||||||
|
float center;
|
||||||
|
float shape = 1; /* 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.5 * (D->ms_filter_size - 1);
|
||||||
|
am = ((float)(j - center) / (float)samples_per_sec) * ((float)mark_freq) * (2 * M_PI);
|
||||||
|
|
||||||
|
shape = window (D->bp_window, D->ms_filter_size, j);
|
||||||
|
|
||||||
|
D->m_sin_table[j] = sin(am) * shape;
|
||||||
|
D->m_cos_table[j] = cos(am) * shape;
|
||||||
|
|
||||||
|
#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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG1
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
|
||||||
|
dw_printf ("Space\n");
|
||||||
|
dw_printf (" j shape S sin S cos\n");
|
||||||
|
#endif
|
||||||
|
for (j=0; j<D->ms_filter_size; j++) {
|
||||||
|
float as;
|
||||||
|
float center;
|
||||||
|
float shape = 1;
|
||||||
|
|
||||||
|
center = 0.5 * (D->ms_filter_size - 1);
|
||||||
|
as = ((float)(j - center) / (float)samples_per_sec) * ((float)space_freq) * (2 * M_PI);
|
||||||
|
|
||||||
|
shape = window (D->bp_window, D->ms_filter_size, j);
|
||||||
|
|
||||||
|
D->s_sin_table[j] = sin(as) * shape;
|
||||||
|
D->s_cos_table[j] = cos(as) * shape;
|
||||||
|
|
||||||
|
#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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Do we want to normalize for unity gain? */
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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, BP_WINDOW_TRUNCATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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; j<D->ms_filter_size; j++) {
|
||||||
|
dc_bias += D->m_sin_table[j];
|
||||||
|
}
|
||||||
|
for (j=0; j<D->ms_filter_size; j++) {
|
||||||
|
D->m_sin_table[j] -= dc_bias / D->ms_filter_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
dc_bias = 0;
|
||||||
|
for (j=0; j<D->ms_filter_size; j++) {
|
||||||
|
dc_bias += D->m_cos_table[j];
|
||||||
|
}
|
||||||
|
for (j=0; j<D->ms_filter_size; j++) {
|
||||||
|
D->m_cos_table[j] -= dc_bias / D->ms_filter_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
dc_bias = 0;
|
||||||
|
for (j=0; j<D->ms_filter_size; j++) {
|
||||||
|
dc_bias += D->s_sin_table[j];
|
||||||
|
}
|
||||||
|
for (j=0; j<D->ms_filter_size; j++) {
|
||||||
|
D->s_sin_table[j] -= dc_bias / D->ms_filter_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
dc_bias = 0;
|
||||||
|
for (j=0; j<D->ms_filter_size; j++) {
|
||||||
|
dc_bias += D->s_cos_table[j];
|
||||||
|
}
|
||||||
|
for (j=0; j<D->ms_filter_size; j++) {
|
||||||
|
D->s_cos_table[j] -= dc_bias / D->ms_filter_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} /* fsk_gen_filter */
|
||||||
|
|
||||||
|
|
||||||
|
#if GEN_FFF
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Properties of the radio channels.
|
||||||
|
|
||||||
|
static struct audio_s modem;
|
||||||
|
|
||||||
|
|
||||||
|
// Filters will be stored here.
|
||||||
|
|
||||||
|
static struct demodulator_state_s ds;
|
||||||
|
|
||||||
|
|
||||||
|
#define SPARSE 3
|
||||||
|
|
||||||
|
|
||||||
|
static void emit_macro (char *name, int size, float *coeff)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
dw_printf ("#define %s(x) \\\n", name);
|
||||||
|
|
||||||
|
for (i=SPARSE/2; i<size; i+=SPARSE) {
|
||||||
|
dw_printf ("\t%c (%.6f * x[%d]) \\\n", (i==0 ? ' ' : '+'), coeff[i], i);
|
||||||
|
}
|
||||||
|
dw_printf ("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main ()
|
||||||
|
{
|
||||||
|
//int n;
|
||||||
|
char fff_profile;
|
||||||
|
|
||||||
|
fff_profile = 'F';
|
||||||
|
|
||||||
|
memset (&modem, 0, sizeof(modem));
|
||||||
|
memset (&ds, 0, sizeof(ds));
|
||||||
|
|
||||||
|
modem.num_channels = 1;
|
||||||
|
modem.samples_per_sec = DEFAULT_SAMPLES_PER_SEC;
|
||||||
|
modem.mark_freq[0] = DEFAULT_MARK_FREQ;
|
||||||
|
modem.space_freq[0] = DEFAULT_SPACE_FREQ;
|
||||||
|
modem.baud[0] = DEFAULT_BAUD;
|
||||||
|
modem.num_subchan[0] = 1;
|
||||||
|
|
||||||
|
|
||||||
|
demod_afsk_init (modem.samples_per_sec, modem.baud[0],
|
||||||
|
modem.mark_freq[0], modem.space_freq[0], fff_profile, &ds);
|
||||||
|
|
||||||
|
printf ("/* This is an automatically generated file. Do not edit. */\n");
|
||||||
|
printf ("\n");
|
||||||
|
printf ("#define FFF_SAMPLES_PER_SEC %d\n", modem.samples_per_sec);
|
||||||
|
printf ("#define FFF_BAUD %d\n", modem.baud[0]);
|
||||||
|
printf ("#define FFF_MARK_FREQ %d\n", modem.mark_freq[0]);
|
||||||
|
printf ("#define FFF_SPACE_FREQ %d\n", modem.space_freq[0]);
|
||||||
|
printf ("#define FFF_PROFILE '%c'\n", fff_profile);
|
||||||
|
printf ("\n");
|
||||||
|
|
||||||
|
emit_macro ("CALC_M_SUM1", ds.ms_filter_size, ds.m_sin_table);
|
||||||
|
emit_macro ("CALC_M_SUM2", ds.ms_filter_size, ds.m_cos_table);
|
||||||
|
emit_macro ("CALC_S_SUM1", ds.ms_filter_size, ds.s_sin_table);
|
||||||
|
emit_macro ("CALC_S_SUM2", ds.ms_filter_size, ds.s_cos_table);
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef GEN_FFF
|
||||||
|
|
||||||
|
/* Optimization for slow processors. */
|
||||||
|
|
||||||
|
#include "fsk_fast_filter.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: demod_afsk_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_afsk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D)
|
||||||
|
{
|
||||||
|
float fsam, abs_fsam;
|
||||||
|
float m_sum1, m_sum2, s_sum1, s_sum2;
|
||||||
|
float m_amp, s_amp;
|
||||||
|
float m_norm, s_norm;
|
||||||
|
float demod_out;
|
||||||
|
#if DEBUG4
|
||||||
|
static FILE *demod_log_fp = NULL;
|
||||||
|
static int seq = 0; /* for log file name */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int j;
|
||||||
|
int demod_data;
|
||||||
|
|
||||||
|
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? Can we do better than shifting each time?
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Scale to nice number, TODO: range -1.0 to +1.0, not 2. */
|
||||||
|
|
||||||
|
fsam = sam / 16384.0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Accumulate measure of the input signal level.
|
||||||
|
*/
|
||||||
|
abs_fsam = fsam >= 0 ? fsam : -fsam;
|
||||||
|
|
||||||
|
// TODO: move to common code
|
||||||
|
|
||||||
|
if (abs_fsam > D->lev_peak_acc) {
|
||||||
|
D->lev_peak_acc = abs_fsam;
|
||||||
|
}
|
||||||
|
D->lev_sum_acc += abs_fsam;
|
||||||
|
|
||||||
|
D->lev_count++;
|
||||||
|
if (D->lev_count >= D->lev_period) {
|
||||||
|
D->lev_prev_peak = D->lev_last_peak;
|
||||||
|
D->lev_last_peak = D->lev_peak_acc;
|
||||||
|
D->lev_peak_acc = 0;
|
||||||
|
|
||||||
|
D->lev_prev_ave = D->lev_last_ave;
|
||||||
|
D->lev_last_ave = D->lev_sum_acc / D->lev_count;
|
||||||
|
D->lev_sum_acc = 0;
|
||||||
|
|
||||||
|
D->lev_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Optional bandpass filter before the mark/space discriminator.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (D->use_prefilter) {
|
||||||
|
float cleaner;
|
||||||
|
|
||||||
|
push_sample (fsam, D->raw_cb, D->ms_filter_size);
|
||||||
|
cleaner = convolve (D->raw_cb, D->pre_filter, D->ms_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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: How do we test for profile F here?
|
||||||
|
|
||||||
|
if (0) {
|
||||||
|
//if (toupper(modem.profiles[chan][subchan]) == 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.0 - D->lpf_iir) * D->m_amp_prev;
|
||||||
|
D->m_amp_prev = m_amp;
|
||||||
|
|
||||||
|
s_amp = D->lpf_iir * s_amp + (1.0 - D->lpf_iir) * D->s_amp_prev;
|
||||||
|
D->s_amp_prev = s_amp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Which tone is stronger?
|
||||||
|
*
|
||||||
|
* 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 have different amounts of DC bias.
|
||||||
|
*
|
||||||
|
* Try to compensate for this by normalizing them separately with automatic gain
|
||||||
|
* control (AGC). 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Fast attack and slow decay. */
|
||||||
|
/* Numbers were obtained by trial and error from actual */
|
||||||
|
/* recorded less-than-optimal signals. */
|
||||||
|
|
||||||
|
/* See agc.c and 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));
|
||||||
|
|
||||||
|
/* 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->prev_demod_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Finally, 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.
|
||||||
|
*
|
||||||
|
* 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->prev_d_c_pll = D->data_clock_pll;
|
||||||
|
D->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->data_clock_pll < 0 && D->prev_d_c_pll > 0) {
|
||||||
|
|
||||||
|
/* Overflow. */
|
||||||
|
#if SLICENDICE
|
||||||
|
hdlc_rec_bit_sam (chan, subchan, demod_data, demod_out);
|
||||||
|
#else
|
||||||
|
hdlc_rec_bit (chan, subchan, demod_data, 0, -1);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (demod_data != D->prev_demod_data) {
|
||||||
|
|
||||||
|
// Note: Test for this demodulator, not overall for channel.
|
||||||
|
|
||||||
|
if (hdlc_rec_data_detect_1 (chan, subchan)) {
|
||||||
|
D->data_clock_pll = (int)(D->data_clock_pll * D->pll_locked_inertia);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
D->data_clock_pll = (int)(D->data_clock_pll * D->pll_searching_inertia);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG4
|
||||||
|
|
||||||
|
if (chan == 0) {
|
||||||
|
if (hdlc_rec_data_detect_1 (chan, subchan)) {
|
||||||
|
char fname[30];
|
||||||
|
|
||||||
|
|
||||||
|
if (demod_log_fp == NULL) {
|
||||||
|
seq++;
|
||||||
|
sprintf (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
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remember demodulator output so we can compare next time.
|
||||||
|
*/
|
||||||
|
D->prev_demod_data = demod_data;
|
||||||
|
|
||||||
|
|
||||||
|
} /* end demod_afsk_process_sample */
|
||||||
|
|
||||||
|
#endif /* GEN_FFF */
|
||||||
|
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: fsk_demod_print_agc
|
||||||
|
*
|
||||||
|
* Purpose: Print information about input signal amplitude.
|
||||||
|
* This will be useful for adjusting transmitter audio levels.
|
||||||
|
* We also want to avoid having an input level so high
|
||||||
|
* that the A/D converter "clips" the signal.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Inputs: chan - Audio channel. 0 for left, 1 for right.
|
||||||
|
*
|
||||||
|
* Returns: None
|
||||||
|
*
|
||||||
|
* Descripion: Not sure what to use for final form.
|
||||||
|
* For now display the AGC peaks for both tones.
|
||||||
|
* This will be called at the end of a frame.
|
||||||
|
*
|
||||||
|
* Future: Come up with a sensible scale and add command line option.
|
||||||
|
* Probably makes more sense to return a single number
|
||||||
|
* and let the caller print it.
|
||||||
|
* Just an experiment for now.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
void fsk_demod_print_agc (int chan, int subchan)
|
||||||
|
{
|
||||||
|
|
||||||
|
struct demodulator_state_s *D;
|
||||||
|
|
||||||
|
|
||||||
|
assert (chan >= 0 && chan < MAX_CHANS);
|
||||||
|
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
|
||||||
|
|
||||||
|
D = &demodulator_state[chan][subchan];
|
||||||
|
|
||||||
|
dw_printf ("%d\n", (int)((D->lev_last_peak + D->lev_prev_peak)*50));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//dw_printf ("Peak= %.2f, %.2f Ave= %.2f, %.2f AGC M= %.2f / %.2f S= %.2f / %.2f\n",
|
||||||
|
// D->lev_last_peak, D->lev_prev_peak, D->lev_last_ave, D->lev_prev_ave,
|
||||||
|
// D->m_peak, D->m_valley, D->s_peak, D->s_valley);
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Resulting scale is 0 to almost 100. */
|
||||||
|
/* Cranking up the input level produces no more than 97 or 98. */
|
||||||
|
/* We currently produce a message when this goes over 90. */
|
||||||
|
|
||||||
|
int fsk_demod_get_audio_level (int chan, int subchan)
|
||||||
|
{
|
||||||
|
struct demodulator_state_s *D;
|
||||||
|
|
||||||
|
|
||||||
|
assert (chan >= 0 && chan < MAX_CHANS);
|
||||||
|
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
|
||||||
|
|
||||||
|
D = &demodulator_state[chan][subchan];
|
||||||
|
|
||||||
|
return ( (int) ((D->lev_last_peak + D->lev_prev_peak) * 50 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* 0 */
|
||||||
|
|
||||||
|
/* end demod_afsk.c */
|
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
/* 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);
|
|
@ -0,0 +1,772 @@
|
||||||
|
//
|
||||||
|
// 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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: digipeater.c
|
||||||
|
*
|
||||||
|
* Purpose: Act as an APRS digital repeater.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Description: Decide whether the specified packet should
|
||||||
|
* be digipeated and make necessary modifications.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* References: APRS Protocol Reference, document version 1.0.1
|
||||||
|
*
|
||||||
|
* http://www.aprs.org/doc/APRS101.PDF
|
||||||
|
*
|
||||||
|
* APRS SPEC Addendum 1.1
|
||||||
|
*
|
||||||
|
* http://www.aprs.org/aprs11.html
|
||||||
|
*
|
||||||
|
* APRS SPEC Addendum 1.2
|
||||||
|
*
|
||||||
|
* http://www.aprs.org/aprs12.html
|
||||||
|
*
|
||||||
|
* "The New n-N Paradigm"
|
||||||
|
*
|
||||||
|
* http://www.aprs.org/fix14439.html
|
||||||
|
*
|
||||||
|
* Preemptive Digipeating (new in version 0.8)
|
||||||
|
*
|
||||||
|
* http://www.aprs.org/aprs12/preemptive-digipeating.txt
|
||||||
|
*
|
||||||
|
*------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#define DIGIPEATER_C
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
//#include <ctype.h> /* for isdigit */
|
||||||
|
#include "regex.h"
|
||||||
|
#include <sys/unistd.h>
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "ax25_pad.h"
|
||||||
|
#include "digipeater.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
#include "dedupe.h"
|
||||||
|
#include "tq.h"
|
||||||
|
|
||||||
|
|
||||||
|
static packet_t digipeat_match (packet_t pp, char *mycall_rec, char *mycall_xmit,
|
||||||
|
regex_t *uidigi, regex_t *uitrace, int to_chan, enum preempt_e preempt);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set by digipeater_init and used later.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
static struct digi_config_s my_config;
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: digipeater_init
|
||||||
|
*
|
||||||
|
* Purpose: Initialize with stuff from configuration file.
|
||||||
|
*
|
||||||
|
* Input: p_digi_config - Address of structure with all the
|
||||||
|
* necessary configuration details.
|
||||||
|
*
|
||||||
|
* Outputs: Make local copy for later use.
|
||||||
|
*
|
||||||
|
* Description: Called once at application startup time.
|
||||||
|
*
|
||||||
|
*------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
void digipeater_init (struct digi_config_s *p_digi_config)
|
||||||
|
{
|
||||||
|
memcpy (&my_config, p_digi_config, sizeof(my_config));
|
||||||
|
|
||||||
|
dedupe_init (p_digi_config->dedupe_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: digipeater
|
||||||
|
*
|
||||||
|
* Purpose: Re-transmit packet if it matches the rules.
|
||||||
|
*
|
||||||
|
* Inputs: chan - Radio channel where it was received.
|
||||||
|
*
|
||||||
|
* pp - Packet object.
|
||||||
|
*
|
||||||
|
* Returns: None.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void digipeater (int from_chan, packet_t pp)
|
||||||
|
{
|
||||||
|
int to_chan;
|
||||||
|
packet_t result;
|
||||||
|
|
||||||
|
|
||||||
|
// dw_printf ("digipeater()\n");
|
||||||
|
|
||||||
|
assert (from_chan >= 0 && from_chan < my_config.num_chans);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* First pass: Look at packets being digipeated to same channel.
|
||||||
|
*
|
||||||
|
* We want these to get out quickly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (to_chan=0; to_chan<my_config.num_chans; to_chan++) {
|
||||||
|
if (my_config.enabled[from_chan][to_chan]) {
|
||||||
|
if (to_chan == from_chan) {
|
||||||
|
result = digipeat_match (pp, my_config.mycall[from_chan], my_config.mycall[to_chan],
|
||||||
|
&my_config.alias[from_chan][to_chan], &my_config.wide[from_chan][to_chan],
|
||||||
|
to_chan, my_config.preempt[from_chan][to_chan]);
|
||||||
|
if (result != NULL) {
|
||||||
|
dedupe_remember (pp, to_chan);
|
||||||
|
tq_append (to_chan, TQ_PRIO_0_HI, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Second pass: Look at packets being digipeated to different channel.
|
||||||
|
*
|
||||||
|
* These are lower priority
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (to_chan=0; to_chan<my_config.num_chans; to_chan++) {
|
||||||
|
if (my_config.enabled[from_chan][to_chan]) {
|
||||||
|
if (to_chan != from_chan) {
|
||||||
|
result = digipeat_match (pp, my_config.mycall[from_chan], my_config.mycall[to_chan],
|
||||||
|
&my_config.alias[from_chan][to_chan], &my_config.wide[from_chan][to_chan],
|
||||||
|
to_chan, my_config.preempt[from_chan][to_chan]);
|
||||||
|
if (result != NULL) {
|
||||||
|
dedupe_remember (pp, to_chan);
|
||||||
|
tq_append (to_chan, TQ_PRIO_1_LO, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* end digipeater */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: digipeat_match
|
||||||
|
*
|
||||||
|
* Purpose: A simple digipeater for APRS.
|
||||||
|
*
|
||||||
|
* 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_rec - Call of my station, with optional SSID,
|
||||||
|
* associated with the radio channel where the
|
||||||
|
* packet was received. Could be the same as
|
||||||
|
* mycall_rec or different.
|
||||||
|
*
|
||||||
|
* alias - Compiled pattern for my station aliases or
|
||||||
|
* "trapping" (repeating only once).
|
||||||
|
*
|
||||||
|
* wide - Compiled pattern for normal WIDEn-n digipeating.
|
||||||
|
*
|
||||||
|
* to_chan - Channel number that we are transmitting to.
|
||||||
|
* This is needed to maintain a history for
|
||||||
|
* removing duplicates during specified time period.
|
||||||
|
*
|
||||||
|
preempt - Option for "preemptive" digipeating.
|
||||||
|
*
|
||||||
|
* Returns: Packet object for transmission or NULL.
|
||||||
|
*
|
||||||
|
* Description: The packet will be digipeated if the next unused digipeater
|
||||||
|
* field matches one of the following:
|
||||||
|
*
|
||||||
|
* - mycall_rec
|
||||||
|
* - udigi list (only once)
|
||||||
|
* - wide list (usual wideN-N rules)
|
||||||
|
*
|
||||||
|
*------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
static char *dest_ssid_path[16] = {
|
||||||
|
"", /* Use VIA path */
|
||||||
|
"WIDE1-1",
|
||||||
|
"WIDE2-2",
|
||||||
|
"WIDE3-3",
|
||||||
|
"WIDE4-4",
|
||||||
|
"WIDE5-5",
|
||||||
|
"WIDE6-6",
|
||||||
|
"WIDE7-7",
|
||||||
|
"WIDE1-1", /* North */
|
||||||
|
"WIDE1-1", /* South */
|
||||||
|
"WIDE1-1", /* East */
|
||||||
|
"WIDE1-1", /* West */
|
||||||
|
"WIDE2-2", /* North */
|
||||||
|
"WIDE2-2", /* South */
|
||||||
|
"WIDE2-2", /* East */
|
||||||
|
"WIDE2-2" }; /* West */
|
||||||
|
|
||||||
|
|
||||||
|
static packet_t digipeat_match (packet_t pp, char *mycall_rec, char *mycall_xmit,
|
||||||
|
regex_t *alias, regex_t *wide, int to_chan, enum preempt_e preempt)
|
||||||
|
{
|
||||||
|
int ssid;
|
||||||
|
int r;
|
||||||
|
char repeater[AX25_MAX_ADDR_LEN];
|
||||||
|
packet_t result = NULL;
|
||||||
|
int err;
|
||||||
|
char err_msg[100];
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The spec says:
|
||||||
|
*
|
||||||
|
* The SSID in the Destination Address field of all packets is coded to specify
|
||||||
|
* the APRS digipeater path.
|
||||||
|
* If the Destination Address SSID is –0, the packet follows the standard AX.25
|
||||||
|
* digipeater (“VIA”) path contained in the Digipeater Addresses field of the
|
||||||
|
* AX.25 frame.
|
||||||
|
* If the Destination Address SSID is non-zero, the packet follows one of 15
|
||||||
|
* generic APRS digipeater paths.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* What if this is non-zero but there is also a digipeater path?
|
||||||
|
* I will ignore this if there is an explicit path.
|
||||||
|
*
|
||||||
|
* Note that this modifies the input. But only once!
|
||||||
|
* Otherwise we don't want to modify the input because this could be called multiple times.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (ax25_get_num_repeaters(pp) == 0 && (ssid = ax25_get_ssid(pp, AX25_DESTINATION)) > 0) {
|
||||||
|
ax25_set_addr(pp, AX25_REPEATER_1, dest_ssid_path[ssid]);
|
||||||
|
ax25_set_ssid(pp, AX25_DESTINATION, 0);
|
||||||
|
/* Continue with general case, below. */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
ax25_get_addr_with_ssid(pp, r, repeater);
|
||||||
|
ssid = ax25_get_ssid(pp, r);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("First unused digipeater is %s, ssid=%d\n", repeater, ssid);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* First check for explicit use of my call.
|
||||||
|
* In this case, we don't check the history so it would be possible
|
||||||
|
* to have a loop (of limited size) if someone constructed the digipeater paths
|
||||||
|
* correctly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (strcmp(repeater, mycall_rec) == 0) {
|
||||||
|
result = ax25_dup (pp);
|
||||||
|
/* If using multiple radio channels, they */
|
||||||
|
/* could have different calls. */
|
||||||
|
ax25_set_addr (result, r, mycall_xmit);
|
||||||
|
ax25_set_h (result, r);
|
||||||
|
return (result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Next try to avoid retransmitting redundant information.
|
||||||
|
* Duplicates are detected by comparing only:
|
||||||
|
* - source
|
||||||
|
* - destination
|
||||||
|
* - info part
|
||||||
|
* - but none of the digipeaters
|
||||||
|
* A history is kept for some amount of time, typically 30 seconds.
|
||||||
|
* For efficiency, only a checksum, rather than the complete fields
|
||||||
|
* might be kept but the result is the same.
|
||||||
|
* Packets transmitted recently will not be transmitted again during
|
||||||
|
* the specified time period.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
if (dedupe_check(pp, to_chan)) {
|
||||||
|
//#if DEBUG
|
||||||
|
/* Might be useful if people are wondering why */
|
||||||
|
/* some are not repeated. Might cause confusion. */
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("Digipeater: Drop redundant packet.\n");
|
||||||
|
//#endif
|
||||||
|
assert (result == NULL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For the alias pattern, we unconditionally digipeat it once.
|
||||||
|
* i.e. Just replace it with MYCALL don't even look at the ssid.
|
||||||
|
*/
|
||||||
|
err = regexec(alias,repeater,0,NULL,0);
|
||||||
|
if (err == 0) {
|
||||||
|
result = ax25_dup (pp);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If preemptive digipeating is enabled, try matching my call
|
||||||
|
* and aliases against all remaining unused digipeaters.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
if (preempt != PREEMPT_OFF) {
|
||||||
|
int r2;
|
||||||
|
|
||||||
|
for (r2 = r+1; r2 < ax25_get_num_addr(pp); r2++) {
|
||||||
|
char repeater2[AX25_MAX_ADDR_LEN];
|
||||||
|
|
||||||
|
ax25_get_addr_with_ssid(pp, r2, repeater2);
|
||||||
|
|
||||||
|
//text_color_set (DW_COLOR_DEBUG);
|
||||||
|
//dw_printf ("test match %d %s\n", r2, repeater2);
|
||||||
|
|
||||||
|
if (strcmp(repeater2, mycall_rec) == 0 ||
|
||||||
|
regexec(alias,repeater2,0,NULL,0) == 0) {
|
||||||
|
|
||||||
|
result = ax25_dup (pp);
|
||||||
|
ax25_set_addr (result, r2, mycall_xmit);
|
||||||
|
ax25_set_h (result, r2);
|
||||||
|
|
||||||
|
switch (preempt) {
|
||||||
|
case PREEMPT_DROP: /* remove all prior */
|
||||||
|
while (r2 > AX25_REPEATER_1) {
|
||||||
|
ax25_remove_addr (result, r2-1);
|
||||||
|
r2--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PREEMPT_MARK:
|
||||||
|
r2--;
|
||||||
|
while (r2 >= AX25_REPEATER_1 && ax25_get_h(result,r2) == 0) {
|
||||||
|
ax25_set_h (result, r2);
|
||||||
|
r2--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PREEMPT_TRACE: /* remove prior unused */
|
||||||
|
default:
|
||||||
|
while (r2 > AX25_REPEATER_1 && ax25_get_h(result,r2-1) == 0) {
|
||||||
|
ax25_remove_addr (result, r2-1);
|
||||||
|
r2--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For the wide pattern, we check the ssid and decrement it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
err = regexec(wide,repeater,0,NULL,0);
|
||||||
|
if (err == 0) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If ssid == 1, we simply replace the repeater with my call and
|
||||||
|
* mark it as being used.
|
||||||
|
*
|
||||||
|
* Otherwise, if ssid in range of 2 to 7,
|
||||||
|
* Decrement y and don't mark repeater as being used.
|
||||||
|
* Insert own call ahead of this one for tracing if we don't already have the
|
||||||
|
* maximum number of repeaters.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (ssid == 1) {
|
||||||
|
result = ax25_dup (pp);
|
||||||
|
ax25_set_addr (result, r, mycall_xmit);
|
||||||
|
ax25_set_h (result, r);
|
||||||
|
return (result);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ssid >= 2 && ssid <= 7) {
|
||||||
|
result = ax25_dup (pp);
|
||||||
|
ax25_set_ssid(result, r, ssid-1); // should be at least 1
|
||||||
|
|
||||||
|
if (ax25_get_num_repeaters(pp) < AX25_MAX_REPEATERS) {
|
||||||
|
ax25_insert_addr (result, r, mycall_xmit);
|
||||||
|
ax25_set_h (result, r);
|
||||||
|
}
|
||||||
|
return (result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (err != REG_NOMATCH) {
|
||||||
|
regerror(err, wide, 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.
|
||||||
|
*/
|
||||||
|
assert (result == NULL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: main
|
||||||
|
*
|
||||||
|
* Purpose: Standalone test case for this funtionality.
|
||||||
|
*
|
||||||
|
* Usage: make -f Makefile.<platform> dtest
|
||||||
|
* ./dtest
|
||||||
|
*
|
||||||
|
*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#if TEST
|
||||||
|
|
||||||
|
static char mycall[] = "WB2OSZ-9";
|
||||||
|
|
||||||
|
static regex_t alias_re;
|
||||||
|
|
||||||
|
static regex_t wide_re;
|
||||||
|
|
||||||
|
static int failed;
|
||||||
|
|
||||||
|
static enum preempt_e preempt = PREEMPT_OFF;
|
||||||
|
|
||||||
|
|
||||||
|
static void test (char *in, char *out)
|
||||||
|
{
|
||||||
|
packet_t pp, result;
|
||||||
|
//int should_repeat;
|
||||||
|
char rec[256];
|
||||||
|
char xmit[256];
|
||||||
|
unsigned char *pinfo;
|
||||||
|
int info_len;
|
||||||
|
unsigned char frame[AX25_MAX_PACKET_LEN];
|
||||||
|
int frame_len;
|
||||||
|
|
||||||
|
dw_printf ("\n");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* As an extra test, change text to internal format back to
|
||||||
|
* text again to make sure it comes out the same.
|
||||||
|
*/
|
||||||
|
pp = ax25_from_text (in, 1);
|
||||||
|
assert (pp != NULL);
|
||||||
|
|
||||||
|
ax25_format_addrs (pp, rec);
|
||||||
|
info_len = ax25_get_info (pp, &pinfo);
|
||||||
|
strcat (rec, (char*)pinfo);
|
||||||
|
|
||||||
|
if (strcmp(in, rec) != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Text/internal/text error %s -> %s\n", in, rec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Just for more fun, write as the frame format, read it back
|
||||||
|
* again, and make sure it is still the same.
|
||||||
|
*/
|
||||||
|
|
||||||
|
frame_len = ax25_pack (pp, frame);
|
||||||
|
ax25_delete (pp);
|
||||||
|
|
||||||
|
pp = ax25_from_frame (frame, frame_len, 50);
|
||||||
|
ax25_format_addrs (pp, rec);
|
||||||
|
info_len = ax25_get_info (pp, &pinfo);
|
||||||
|
strcat (rec, (char*)pinfo);
|
||||||
|
|
||||||
|
if (strcmp(in, rec) != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("internal/frame/internal/text error %s -> %s\n", in, rec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On with the digipeater test.
|
||||||
|
*/
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_REC);
|
||||||
|
dw_printf ("Rec\t%s\n", rec);
|
||||||
|
|
||||||
|
result = digipeat_match (pp, mycall, mycall, &alias_re, &wide_re, 0, preempt);
|
||||||
|
|
||||||
|
if (result != NULL) {
|
||||||
|
|
||||||
|
dedupe_remember (result, 0);
|
||||||
|
ax25_format_addrs (result, xmit);
|
||||||
|
info_len = ax25_get_info (result, &pinfo);
|
||||||
|
strcat (xmit, (char*)pinfo);
|
||||||
|
ax25_delete (result);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
strcpy (xmit, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_XMIT);
|
||||||
|
dw_printf ("Xmit\t%s\n", xmit);
|
||||||
|
|
||||||
|
if (strcmp(xmit, out) == 0) {
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("OK\n");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Expect\t%s\n", out);
|
||||||
|
failed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
dw_printf ("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main (int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int e;
|
||||||
|
failed = 0;
|
||||||
|
char message[256];
|
||||||
|
|
||||||
|
dedupe_init (4);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compile the patterns.
|
||||||
|
*/
|
||||||
|
e = regcomp (&alias_re, "^WIDE[4-7]-[1-7]|CITYD$", REG_EXTENDED|REG_NOSUB);
|
||||||
|
if (e != 0) {
|
||||||
|
regerror (e, &alias_re, message, sizeof(message));
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("\n%s\n\n", message);
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
e = regcomp (&wide_re, "^WIDE[1-7]-[1-7]$|^TRACE[1-7]-[1-7]$|^MA[1-7]-[1-7]$", REG_EXTENDED|REG_NOSUB);
|
||||||
|
if (e != 0) {
|
||||||
|
regerror (e, &wide_re, message, sizeof(message));
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("\n%s\n\n", message);
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Let's start with the most basic cases.
|
||||||
|
*/
|
||||||
|
|
||||||
|
test ( "W1ABC>TEST01,TRACE3-3:",
|
||||||
|
"W1ABC>TEST01,WB2OSZ-9*,TRACE3-2:");
|
||||||
|
|
||||||
|
test ( "W1ABC>TEST02,WIDE3-3:",
|
||||||
|
"W1ABC>TEST02,WB2OSZ-9*,WIDE3-2:");
|
||||||
|
|
||||||
|
test ( "W1ABC>TEST03,WIDE3-2:",
|
||||||
|
"W1ABC>TEST03,WB2OSZ-9*,WIDE3-1:");
|
||||||
|
|
||||||
|
test ( "W1ABC>TEST04,WIDE3-1:",
|
||||||
|
"W1ABC>TEST04,WB2OSZ-9*:");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Look at edge case of maximum number of digipeaters.
|
||||||
|
*/
|
||||||
|
test ( "W1ABC>TEST11,R1,R2,R3,R4,R5,R6*,WIDE3-3:",
|
||||||
|
"W1ABC>TEST11,R1,R2,R3,R4,R5,R6,WB2OSZ-9*,WIDE3-2:");
|
||||||
|
|
||||||
|
test ( "W1ABC>TEST12,R1,R2,R3,R4,R5,R6,R7*,WIDE3-3:",
|
||||||
|
"W1ABC>TEST12,R1,R2,R3,R4,R5,R6,R7*,WIDE3-2:");
|
||||||
|
|
||||||
|
test ( "W1ABC>TEST13,R1,R2,R3,R4,R5,R6,R7*,WIDE3-1:",
|
||||||
|
"W1ABC>TEST13,R1,R2,R3,R4,R5,R6,R7,WB2OSZ-9*:");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "Trap" large values of "N" by repeating only once.
|
||||||
|
*/
|
||||||
|
test ( "W1ABC>TEST21,WIDE4-4:",
|
||||||
|
"W1ABC>TEST21,WB2OSZ-9*:");
|
||||||
|
|
||||||
|
test ( "W1ABC>TEST22,WIDE7-7:",
|
||||||
|
"W1ABC>TEST22,WB2OSZ-9*:");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Only values in range of 1 thru 7 are valid.
|
||||||
|
*/
|
||||||
|
test ( "W1ABC>TEST31,WIDE0-4:",
|
||||||
|
"");
|
||||||
|
|
||||||
|
test ( "W1ABC>TEST32,WIDE8-4:",
|
||||||
|
"");
|
||||||
|
|
||||||
|
test ( "W1ABC>TEST33,WIDE2:",
|
||||||
|
"");
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* and a few cases actually heard.
|
||||||
|
*/
|
||||||
|
|
||||||
|
test ( "WA1ENO>FN42ND,W1MV-1*,WIDE3-2:",
|
||||||
|
"WA1ENO>FN42ND,W1MV-1,WB2OSZ-9*,WIDE3-1:");
|
||||||
|
|
||||||
|
test ( "W1ON-3>BEACON:",
|
||||||
|
"");
|
||||||
|
|
||||||
|
test ( "W1CMD-9>TQ3Y8P,N1RCW-2,W1CLA-1,N8VIM,WIDE2*:",
|
||||||
|
"");
|
||||||
|
|
||||||
|
test ( "W1CLA-1>APX192,W1GLO-1,WIDE2*:",
|
||||||
|
"");
|
||||||
|
|
||||||
|
test ( "AC1U-9>T2TX4S,AC1U,WIDE1,N8VIM*,WIDE2-1:",
|
||||||
|
"AC1U-9>T2TX4S,AC1U,WIDE1,N8VIM,WB2OSZ-9*:");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Someone is still using the old style and will probably be disappointed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
test ( "K1CPD-1>T2SR5R,RELAY*,WIDE,WIDE,SGATE,WIDE:",
|
||||||
|
"");
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change destination SSID to normal digipeater if none specified.
|
||||||
|
*/
|
||||||
|
test ( "W1ABC>TEST-3:",
|
||||||
|
"W1ABC>TEST,WB2OSZ-9*,WIDE3-2:");
|
||||||
|
|
||||||
|
test ( "W1DEF>TEST-3,WIDE2-2:",
|
||||||
|
"W1DEF>TEST-3,WB2OSZ-9*,WIDE2-1:");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Drop duplicates within specified time interval.
|
||||||
|
* Only the first 1 of 3 should be retransmitted.
|
||||||
|
*/
|
||||||
|
|
||||||
|
test ( "W1XYZ>TEST,R1*,WIDE3-2:info1",
|
||||||
|
"W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info1");
|
||||||
|
|
||||||
|
test ( "W1XYZ>TEST,R2*,WIDE3-2:info1",
|
||||||
|
"");
|
||||||
|
|
||||||
|
test ( "W1XYZ>TEST,R3*,WIDE3-2:info1",
|
||||||
|
"");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allow same thing after adequate time.
|
||||||
|
*/
|
||||||
|
SLEEP_SEC (5);
|
||||||
|
|
||||||
|
test ( "W1XYZ>TEST,R3*,WIDE3-2:info1",
|
||||||
|
"W1XYZ>TEST,R3,WB2OSZ-9*,WIDE3-1:info1");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Although source and destination match, the info field is different.
|
||||||
|
*/
|
||||||
|
|
||||||
|
test ( "W1XYZ>TEST,R1*,WIDE3-2:info4",
|
||||||
|
"W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info4");
|
||||||
|
|
||||||
|
test ( "W1XYZ>TEST,R1*,WIDE3-2:info5",
|
||||||
|
"W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info5");
|
||||||
|
|
||||||
|
test ( "W1XYZ>TEST,R1*,WIDE3-2:info6",
|
||||||
|
"W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info6");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* New in version 0.8.
|
||||||
|
* "Preemptive" digipeating looks ahead beyond the first unused digipeater.
|
||||||
|
*/
|
||||||
|
|
||||||
|
test ( "W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD,CITYE:off",
|
||||||
|
"");
|
||||||
|
|
||||||
|
preempt = PREEMPT_DROP;
|
||||||
|
|
||||||
|
test ( "W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD,CITYE:drop",
|
||||||
|
"W1ABC>TEST11,WB2OSZ-9*,CITYE:drop");
|
||||||
|
|
||||||
|
preempt = PREEMPT_MARK;
|
||||||
|
|
||||||
|
test ( "W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD,CITYE:mark1",
|
||||||
|
"W1ABC>TEST11,CITYA,CITYB,CITYC,WB2OSZ-9*,CITYE:mark1");
|
||||||
|
|
||||||
|
test ( "W1ABC>TEST11,CITYA*,CITYB,CITYC,WB2OSZ-9,CITYE:mark2",
|
||||||
|
"W1ABC>TEST11,CITYA,CITYB,CITYC,WB2OSZ-9*,CITYE:mark2");
|
||||||
|
|
||||||
|
preempt = PREEMPT_TRACE;
|
||||||
|
|
||||||
|
test ( "W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD,CITYE:trace1",
|
||||||
|
"W1ABC>TEST11,CITYA,WB2OSZ-9*,CITYE:trace1");
|
||||||
|
|
||||||
|
test ( "W1ABC>TEST11,CITYA*,CITYB,CITYC,CITYD:trace2",
|
||||||
|
"W1ABC>TEST11,CITYA,WB2OSZ-9*:trace2");
|
||||||
|
|
||||||
|
test ( "W1ABC>TEST11,CITYB,CITYC,CITYD:trace3",
|
||||||
|
"W1ABC>TEST11,WB2OSZ-9*:trace3");
|
||||||
|
|
||||||
|
test ( "W1ABC>TEST11,CITYA*,CITYW,CITYX,CITYY,CITYZ:nomatch",
|
||||||
|
"");
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Did I miss any cases?
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (failed == 0) {
|
||||||
|
dw_printf ("SUCCESS -- All digipeater tests passed.\n");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("ERROR - %d digipeater tests failed.\n", failed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ( failed != 0 );
|
||||||
|
|
||||||
|
} /* end main */
|
||||||
|
|
||||||
|
#endif /* if TEST */
|
||||||
|
|
||||||
|
/* end digipeater.c */
|
|
@ -0,0 +1,62 @@
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef DIGIPEATER_H
|
||||||
|
#define DIGIPEATER_H 1
|
||||||
|
|
||||||
|
#include "regex.h"
|
||||||
|
|
||||||
|
#include "direwolf.h" /* for MAX_CHANS */
|
||||||
|
#include "ax25_pad.h" /* for packet_t */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Information required for digipeating.
|
||||||
|
*
|
||||||
|
* The configuration file reader fills in this information
|
||||||
|
* and it is passed to digipeater_init at application start up time.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
struct digi_config_s {
|
||||||
|
|
||||||
|
int num_chans;
|
||||||
|
|
||||||
|
char mycall[MAX_CHANS][AX25_MAX_ADDR_LEN]; /* Call associated */
|
||||||
|
/* with each of the radio channels. */
|
||||||
|
/* Could be the same or different. */
|
||||||
|
|
||||||
|
int dedupe_time; /* Don't digipeat duplicate packets */
|
||||||
|
/* within this number of seconds. */
|
||||||
|
|
||||||
|
#define DEFAULT_DEDUPE 30
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Rules for each of the [from_chan][to_chan] combinations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
regex_t alias[MAX_CHANS][MAX_CHANS];
|
||||||
|
|
||||||
|
regex_t wide[MAX_CHANS][MAX_CHANS];
|
||||||
|
|
||||||
|
int enabled[MAX_CHANS][MAX_CHANS];
|
||||||
|
|
||||||
|
enum preempt_e { PREEMPT_OFF, PREEMPT_DROP, PREEMPT_MARK, PREEMPT_TRACE } preempt[MAX_CHANS][MAX_CHANS];
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Call once at application start up time.
|
||||||
|
*/
|
||||||
|
|
||||||
|
extern void digipeater_init (struct digi_config_s *p_digi_config);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Call this for each packet received.
|
||||||
|
* Suitable packets will be queued for transmission.
|
||||||
|
*/
|
||||||
|
|
||||||
|
extern void digipeater (int from_chan, packet_t pp);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* end digipeater.h */
|
||||||
|
|
|
@ -0,0 +1,885 @@
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011, 2012, 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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Module: direwolf.c
|
||||||
|
*
|
||||||
|
* Purpose: Main program for "Dire Wolf" which includes:
|
||||||
|
*
|
||||||
|
* AFSK modem using the "sound card."
|
||||||
|
* AX.25 encoder/decoder.
|
||||||
|
* APRS data encoder / decoder.
|
||||||
|
* APRS digipeater.
|
||||||
|
* KISS TNC emulator.
|
||||||
|
* APRStt (touch tone input) gateway
|
||||||
|
* Internet Gateway (IGate)
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*---------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/soundcard.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#define DIREWOLF_C 1
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "version.h"
|
||||||
|
#include "audio.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "multi_modem.h"
|
||||||
|
#include "demod.h"
|
||||||
|
#include "hdlc_rec.h"
|
||||||
|
#include "hdlc_rec2.h"
|
||||||
|
#include "ax25_pad.h"
|
||||||
|
#include "decode_aprs.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
#include "server.h"
|
||||||
|
#include "kiss.h"
|
||||||
|
#include "kissnet.h"
|
||||||
|
#include "gen_tone.h"
|
||||||
|
#include "digipeater.h"
|
||||||
|
#include "tq.h"
|
||||||
|
#include "xmit.h"
|
||||||
|
#include "ptt.h"
|
||||||
|
#include "beacon.h"
|
||||||
|
#include "ax25_pad.h"
|
||||||
|
#include "redecode.h"
|
||||||
|
#include "dtmf.h"
|
||||||
|
#include "aprs_tt.h"
|
||||||
|
#include "tt_user.h"
|
||||||
|
#include "igate.h"
|
||||||
|
#include "symbols.h"
|
||||||
|
#include "dwgps.h"
|
||||||
|
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
static BOOL cleanup_win (int);
|
||||||
|
#else
|
||||||
|
static void cleanup_linux (int);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void usage (char **argv);
|
||||||
|
|
||||||
|
#if __SSE__
|
||||||
|
|
||||||
|
static void __cpuid(int cpuinfo[4], int infotype){
|
||||||
|
__asm__ __volatile__ (
|
||||||
|
"cpuid":
|
||||||
|
"=a" (cpuinfo[0]),
|
||||||
|
"=b" (cpuinfo[1]),
|
||||||
|
"=c" (cpuinfo[2]),
|
||||||
|
"=d" (cpuinfo[3]) :
|
||||||
|
"a" (infotype)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: main
|
||||||
|
*
|
||||||
|
* Purpose: Main program for packet radio virtual TNC.
|
||||||
|
*
|
||||||
|
* Inputs: Command line arguments.
|
||||||
|
* See usage message for details.
|
||||||
|
*
|
||||||
|
* Outputs: Decoded information is written to stdout.
|
||||||
|
*
|
||||||
|
* A socket and pseudo terminal are created for
|
||||||
|
* for communication with other applications.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
static struct audio_s modem;
|
||||||
|
|
||||||
|
static int d_u_opt = 0; /* "-d u" command line option. */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int main (int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
int eof;
|
||||||
|
int j;
|
||||||
|
char config_file[100];
|
||||||
|
int xmit_calibrate_option = 0;
|
||||||
|
int enable_pseudo_terminal = 0;
|
||||||
|
struct digi_config_s digi_config;
|
||||||
|
struct tt_config_s tt_config;
|
||||||
|
struct igate_config_s igate_config;
|
||||||
|
struct misc_config_s misc_config;
|
||||||
|
int r_opt = 0, n_opt = 0, b_opt = 0, B_opt = 0, D_opt = 0; /* Command line options. */
|
||||||
|
char input_file[80];
|
||||||
|
|
||||||
|
int t_opt = 1; /* Text color option. */
|
||||||
|
|
||||||
|
|
||||||
|
#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);
|
||||||
|
|
||||||
|
#elif __CYGWIN__
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Without this, the ISO Latin 1 characters are displayed as gray boxes.
|
||||||
|
*/
|
||||||
|
//setenv ("LANG", "C.ISO-8859-1", 1);
|
||||||
|
#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
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pre-scan the command line options for the text color option.
|
||||||
|
* We need to set this before any text output.
|
||||||
|
*/
|
||||||
|
|
||||||
|
t_opt = 1; /* 1 = normal, 0 = no text colors. */
|
||||||
|
for (j=1; j<argc-1; j++) {
|
||||||
|
if (strcmp(argv[j], "-t") == 0) {
|
||||||
|
t_opt = atoi (argv[j+1]);
|
||||||
|
//dw_printf ("DEBUG: text color option = %d.\n", t_opt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
text_color_init(t_opt);
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
//dw_printf ("Dire Wolf version %d.%d (%s) Beta Test 2\n", MAJOR_VERSION, MINOR_VERSION, __DATE__);
|
||||||
|
//dw_printf ("Dire Wolf version %d.%d (%s) Development version\n", MAJOR_VERSION, MINOR_VERSION, __DATE__);
|
||||||
|
|
||||||
|
// Note "a" for fix with beacon sent to IGate Server.
|
||||||
|
|
||||||
|
dw_printf ("Dire Wolf version %d.%da\n", MAJOR_VERSION, MINOR_VERSION);
|
||||||
|
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
SetConsoleCtrlHandler (cleanup_win, TRUE);
|
||||||
|
#else
|
||||||
|
setlinebuf (stdout);
|
||||||
|
signal (SIGINT, cleanup_linux);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Starting with version 0.9, the prebuilt Windows version
|
||||||
|
* requires a minimum of a Pentium 3 or equivalent so we can
|
||||||
|
* use the SSE instructions.
|
||||||
|
* Try to warn anyone using a CPU from the previous
|
||||||
|
* century rather than just dying for no apparent reason.
|
||||||
|
*
|
||||||
|
* Now, where can I find a Pentium 2 or earlier to test this?
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if __SSE__
|
||||||
|
int cpuinfo[4];
|
||||||
|
__cpuid (cpuinfo, 0);
|
||||||
|
if (cpuinfo[0] >= 1) {
|
||||||
|
__cpuid (cpuinfo, 1);
|
||||||
|
//dw_printf ("debug: cpuinfo = %x, %x, %x, %x\n", cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]);
|
||||||
|
if ( ! ( cpuinfo[3] & (1 << 25))) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("------------------------------------------------------------------\n");
|
||||||
|
dw_printf ("This version requires a minimum of a Pentium 3 or equivalent.\n");
|
||||||
|
dw_printf ("If you are seeing this message, you are probably using a computer\n");
|
||||||
|
dw_printf ("from the previous century. See comments in Makefile.win for\n");
|
||||||
|
dw_printf ("information on how you can recompile it for use with your antique.\n");
|
||||||
|
dw_printf ("------------------------------------------------------------------\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This has not been very well tested in 64 bit mode.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
if (sizeof(int) != 4 || sizeof(long) != 4 || sizeof(char *) != 4) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("------------------------------------------------------------------\n");
|
||||||
|
dw_printf ("This might not work properly when compiled for a 64 bit target.\n");
|
||||||
|
dw_printf ("It is recommended that you rebuild it with gcc -m32 option.\n");
|
||||||
|
dw_printf ("------------------------------------------------------------------\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Default location of configuration file is current directory.
|
||||||
|
* Can be overridden by -c command line option.
|
||||||
|
* TODO: Automatically search other places.
|
||||||
|
*/
|
||||||
|
|
||||||
|
strcpy (config_file, "direwolf.conf");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Look at command line options.
|
||||||
|
* So far, the only one is the configuration file location.
|
||||||
|
*/
|
||||||
|
|
||||||
|
strcpy (input_file, "");
|
||||||
|
while (1) {
|
||||||
|
int this_option_optind = optind ? optind : 1;
|
||||||
|
int option_index = 0;
|
||||||
|
int c;
|
||||||
|
static struct option long_options[] = {
|
||||||
|
{"future1", 1, 0, 0},
|
||||||
|
{"future2", 0, 0, 0},
|
||||||
|
{"future3", 1, 0, 'c'},
|
||||||
|
{0, 0, 0, 0}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ':' following option character means arg is required. */
|
||||||
|
|
||||||
|
c = getopt_long(argc, argv, "B:D:c:pxr:b:n:d:t:U",
|
||||||
|
long_options, &option_index);
|
||||||
|
if (c == -1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
|
||||||
|
case 0: /* possible future use */
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf("option %s", long_options[option_index].name);
|
||||||
|
if (optarg) {
|
||||||
|
dw_printf(" with arg %s", optarg);
|
||||||
|
}
|
||||||
|
dw_printf("\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'c': /* -c for configuration file name */
|
||||||
|
|
||||||
|
strcpy (config_file, optarg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
#else
|
||||||
|
case 'p': /* -p enable pseudo terminal */
|
||||||
|
|
||||||
|
/* We want this to be off by default because it hangs */
|
||||||
|
/* eventually when nothing is reading from other side. */
|
||||||
|
|
||||||
|
enable_pseudo_terminal = 1;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case 'B': /* -B baud rate and modem properties. */
|
||||||
|
|
||||||
|
B_opt = atoi(optarg);
|
||||||
|
if (B_opt < 100 || B_opt > 10000) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Use a more reasonable data baud rate in range of 100 - 10000.\n");
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'D': /* -D decrease AFSK demodulator sample rate */
|
||||||
|
|
||||||
|
D_opt = atoi(optarg);
|
||||||
|
if (D_opt < 1 || D_opt > 8) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Crazy value of -D. \n");
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'x': /* -x for transmit calibration tones. */
|
||||||
|
|
||||||
|
xmit_calibrate_option = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'r': /* -r audio samples/sec. e.g. 44100 */
|
||||||
|
|
||||||
|
r_opt = atoi(optarg);
|
||||||
|
if (r_opt < MIN_SAMPLES_PER_SEC || r_opt > MAX_SAMPLES_PER_SEC)
|
||||||
|
{
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf("-r option, audio samples/sec, is out of range.\n");
|
||||||
|
r_opt = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'n': /* -n number of audio channels. 1 or 2. */
|
||||||
|
|
||||||
|
n_opt = atoi(optarg);
|
||||||
|
if (n_opt < 1 || n_opt > MAX_CHANS)
|
||||||
|
{
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf("-n option, number of audio channels, is out of range.\n");
|
||||||
|
n_opt = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'b': /* -b bits per sample. 8 or 16. */
|
||||||
|
|
||||||
|
b_opt = atoi(optarg);
|
||||||
|
if (b_opt != 8 && b_opt != 16)
|
||||||
|
{
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf("-b option, bits per sample, must be 8 or 16.\n");
|
||||||
|
b_opt = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '?':
|
||||||
|
|
||||||
|
/* Unknown option message was already printed. */
|
||||||
|
usage (argv);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'd': /* Set debug option. */
|
||||||
|
|
||||||
|
switch (optarg[0]) {
|
||||||
|
case 'a': server_set_debug(1); break;
|
||||||
|
case 'k': kiss_serial_set_debug (1); break;
|
||||||
|
case 'n': kiss_net_set_debug (1); break;
|
||||||
|
case 'u': d_u_opt = 1; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 't': /* Was handled earlier. */
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'U': /* Print UTF-8 test and exit. */
|
||||||
|
|
||||||
|
dw_printf ("\n UTF-8 test string: ma%c%cana %c%c F%c%c%c%ce\n\n",
|
||||||
|
0xc3, 0xb1,
|
||||||
|
0xc2, 0xb0,
|
||||||
|
0xc3, 0xbc, 0xc3, 0x9f);
|
||||||
|
|
||||||
|
exit (0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
/* Should not be here. */
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf("?? getopt returned character code 0%o ??\n", c);
|
||||||
|
usage (argv);
|
||||||
|
}
|
||||||
|
} /* end while(1) for options */
|
||||||
|
|
||||||
|
if (optind < argc)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (optind < argc - 1)
|
||||||
|
{
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Warning: File(s) beyond the first are ignored.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
strcpy (input_file, argv[optind]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get all types of configuration settings from configuration file.
|
||||||
|
*
|
||||||
|
* Possibly override some by command line options.
|
||||||
|
*/
|
||||||
|
|
||||||
|
symbols_init ();
|
||||||
|
|
||||||
|
config_init (config_file, &modem, &digi_config, &tt_config, &igate_config, &misc_config);
|
||||||
|
|
||||||
|
if (r_opt != 0) {
|
||||||
|
modem.samples_per_sec = r_opt;
|
||||||
|
}
|
||||||
|
if (n_opt != 0) {
|
||||||
|
modem.num_channels = n_opt;
|
||||||
|
}
|
||||||
|
if (b_opt != 0) {
|
||||||
|
modem.bits_per_sample = b_opt;
|
||||||
|
}
|
||||||
|
if (B_opt != 0) {
|
||||||
|
modem.baud[0] = B_opt;
|
||||||
|
|
||||||
|
if (modem.baud[0] < 600) {
|
||||||
|
modem.modem_type[0] = AFSK;
|
||||||
|
modem.mark_freq[0] = 1600;
|
||||||
|
modem.space_freq[0] = 1800;
|
||||||
|
modem.decimate[0] = 3;
|
||||||
|
}
|
||||||
|
else if (modem.baud[0] > 2400) {
|
||||||
|
modem.modem_type[0] = SCRAMBLE;
|
||||||
|
modem.mark_freq[0] = 0;
|
||||||
|
modem.space_freq[0] = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
modem.modem_type[0] = AFSK;
|
||||||
|
modem.mark_freq[0] = 1200;
|
||||||
|
modem.space_freq[0] = 2200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (D_opt != 0) {
|
||||||
|
// Don't document. This will change.
|
||||||
|
modem.decimate[0] = D_opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
misc_config.enable_kiss_pt = enable_pseudo_terminal;
|
||||||
|
|
||||||
|
if (strlen(input_file) > 0) {
|
||||||
|
strcpy (modem.adevice_in, input_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Open the audio source
|
||||||
|
* - soundcard
|
||||||
|
* - stdin
|
||||||
|
* - UDP
|
||||||
|
* Files not supported at this time.
|
||||||
|
* Can always "cat" the file and pipe it into stdin.
|
||||||
|
*/
|
||||||
|
|
||||||
|
err = audio_open (&modem);
|
||||||
|
if (err < 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Pointless to continue without audio device.\n");
|
||||||
|
SLEEP_SEC(5);
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize the AFSK demodulator and HDLC decoder.
|
||||||
|
*/
|
||||||
|
multi_modem_init (&modem);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize the touch tone decoder & APRStt gateway.
|
||||||
|
*/
|
||||||
|
dtmf_init (modem.samples_per_sec);
|
||||||
|
aprs_tt_init (&tt_config);
|
||||||
|
tt_user_init (&tt_config);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Should there be an option for audio output level?
|
||||||
|
* Note: This is not the same as a volume control you would see on the screen.
|
||||||
|
* It is the range of the digital sound representation.
|
||||||
|
*/
|
||||||
|
gen_tone_init (&modem, 100);
|
||||||
|
|
||||||
|
assert (modem.bits_per_sample == 8 || modem.bits_per_sample == 16);
|
||||||
|
assert (modem.num_channels == 1 || modem.num_channels == 2);
|
||||||
|
assert (modem.samples_per_sec >= MIN_SAMPLES_PER_SEC && modem.samples_per_sec <= MAX_SAMPLES_PER_SEC);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize the transmit queue.
|
||||||
|
*/
|
||||||
|
|
||||||
|
xmit_init (&modem);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If -x option specified, transmit alternating tones for transmitter
|
||||||
|
* audio level adjustment, up to 1 minute then quit.
|
||||||
|
* TODO: enhance for more than one channel.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (xmit_calibrate_option) {
|
||||||
|
|
||||||
|
int max_duration = 60; /* seconds */
|
||||||
|
int n = modem.baud[0] * max_duration;
|
||||||
|
int chan = 0;
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("\nSending transmit calibration tones. Press control-C to terminate.\n");
|
||||||
|
|
||||||
|
ptt_set (chan, 1);
|
||||||
|
while (n-- > 0) {
|
||||||
|
|
||||||
|
tone_gen_put_bit (chan, n & 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
ptt_set (chan, 0);
|
||||||
|
exit (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize the digipeater and IGate functions.
|
||||||
|
*/
|
||||||
|
digipeater_init (&digi_config);
|
||||||
|
igate_init (&igate_config, &digi_config);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Provide the AGW & KISS socket interfaces for use by a client application.
|
||||||
|
*/
|
||||||
|
server_init (&misc_config);
|
||||||
|
kissnet_init (&misc_config);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a pseudo terminal and KISS TNC emulator.
|
||||||
|
*/
|
||||||
|
kiss_init (&misc_config);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create thread for trying to salvage frames with bad FCS.
|
||||||
|
*/
|
||||||
|
redecode_init ();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Enable beaconing.
|
||||||
|
*/
|
||||||
|
beacon_init (&misc_config, &digi_config);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get sound samples and decode them.
|
||||||
|
* Use hot attribute for all functions called for every audio sample.
|
||||||
|
* TODO: separate function with __attribute__((hot))
|
||||||
|
*/
|
||||||
|
eof = 0;
|
||||||
|
while ( ! eof)
|
||||||
|
{
|
||||||
|
|
||||||
|
int audio_sample;
|
||||||
|
int c;
|
||||||
|
char tt;
|
||||||
|
|
||||||
|
for (c=0; c<modem.num_channels; c++)
|
||||||
|
{
|
||||||
|
audio_sample = demod_get_sample ();
|
||||||
|
|
||||||
|
if (audio_sample >= 256 * 256)
|
||||||
|
eof = 1;
|
||||||
|
|
||||||
|
multi_modem_process_sample(c,audio_sample);
|
||||||
|
|
||||||
|
|
||||||
|
/* Previously, the DTMF decoder was always active. */
|
||||||
|
/* It took very little CPU time and the thinking was that an */
|
||||||
|
/* attached application might be interested in this even when */
|
||||||
|
/* the APRStt gateway was not being used. */
|
||||||
|
/* Unfortunately it resulted in too many false detections of */
|
||||||
|
/* touch tones when hearing other types of digital communications */
|
||||||
|
/* on HF. Starting in version 1.0, the DTMF decoder is active */
|
||||||
|
/* only when the APRStt gateway is configured. */
|
||||||
|
|
||||||
|
if (tt_config.obj_xmit_header[0] != '\0') {
|
||||||
|
tt = dtmf_sample (c, audio_sample/16384.);
|
||||||
|
if (tt != ' ') {
|
||||||
|
aprs_tt_button (c, tt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When a complete frame is accumulated, */
|
||||||
|
/* process_rec_frame, below, is called. */
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
exit (EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: app_process_rec_frame
|
||||||
|
*
|
||||||
|
* Purpose: This is called when we receive a frame with a valid
|
||||||
|
* FCS and acceptable size.
|
||||||
|
*
|
||||||
|
* Inputs: chan - Audio channel number, 0 or 1.
|
||||||
|
* subchan - Which modem caught it.
|
||||||
|
* Special case -1 for APRStt gateway.
|
||||||
|
* pp - Packet handle.
|
||||||
|
* alevel - Audio level, range of 0 - 100.
|
||||||
|
* (Special case, use negative to skip
|
||||||
|
* display of audio level line.
|
||||||
|
* Use -2 to indicate DTMF message.)
|
||||||
|
* retries - Level of bit correction used.
|
||||||
|
* spectrum - Display of how well multiple decoders did.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Description: Print decoded packet.
|
||||||
|
* Optionally send to another application.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
void app_process_rec_packet (int chan, int subchan, packet_t pp, int alevel, retry_t retries, char *spectrum)
|
||||||
|
{
|
||||||
|
|
||||||
|
char stemp[500];
|
||||||
|
unsigned char *pinfo;
|
||||||
|
int info_len;
|
||||||
|
char heard[AX25_MAX_ADDR_LEN];
|
||||||
|
//int j;
|
||||||
|
int h;
|
||||||
|
|
||||||
|
assert (chan >= 0 && chan < MAX_CHANS);
|
||||||
|
assert (subchan >= -1 && subchan < MAX_SUBCHANS);
|
||||||
|
|
||||||
|
|
||||||
|
ax25_format_addrs (pp, stemp);
|
||||||
|
|
||||||
|
info_len = ax25_get_info (pp, &pinfo);
|
||||||
|
|
||||||
|
/* Print so we can see what is going on. */
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
strcpy (heard, "");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
h = ax25_get_heard(pp);
|
||||||
|
ax25_get_addr_with_ssid(pp, h, heard);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alevel >= 0) {
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("\n");
|
||||||
|
|
||||||
|
if (h != -1 && h != AX25_SOURCE) {
|
||||||
|
dw_printf ("Digipeater ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* As suggested by KJ4ERJ, if we are receiving from */
|
||||||
|
/* WIDEn-0, it is quite likely (but not guaranteed), that */
|
||||||
|
/* we are actually hearing the preceding station in the path. */
|
||||||
|
|
||||||
|
if (h >= AX25_REPEATER_2 &&
|
||||||
|
strncmp(heard, "WIDE", 4) == 0 &&
|
||||||
|
isdigit(heard[4]) &&
|
||||||
|
heard[5] == '\0') {
|
||||||
|
|
||||||
|
char probably_really[AX25_MAX_ADDR_LEN];
|
||||||
|
|
||||||
|
ax25_get_addr_with_ssid(pp, h-1, probably_really);
|
||||||
|
dw_printf ("%s (probably %s) audio level = %d [%s] %s\n", heard, probably_really, alevel, retry_text[(int)retries], spectrum);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dw_printf ("%s audio level = %d [%s] %s\n", heard, alevel, retry_text[(int)retries], spectrum);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cranking up the input currently produces */
|
||||||
|
/* no more than 97. Issue a warning before we */
|
||||||
|
/* reach this saturation point. */
|
||||||
|
|
||||||
|
if (alevel > 90) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Audio input level is too high. Reduce so most stations are around 50.\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display non-APRS packets in a different color.
|
||||||
|
|
||||||
|
// Display subchannel only when multiple modems configured for channel.
|
||||||
|
|
||||||
|
// -1 for APRStt DTMF decoder.
|
||||||
|
|
||||||
|
if (subchan == -1) {
|
||||||
|
text_color_set(DW_COLOR_REC);
|
||||||
|
dw_printf ("[%d.dtmf] ", chan);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (ax25_is_aprs(pp)) {
|
||||||
|
text_color_set(DW_COLOR_REC);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
}
|
||||||
|
if (modem.num_subchan[chan] > 1) {
|
||||||
|
dw_printf ("[%d.%d] ", chan, subchan);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dw_printf ("[%d] ", chan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dw_printf ("%s", stemp); /* stations followed by : */
|
||||||
|
ax25_safe_print ((char *)pinfo, info_len, 0);
|
||||||
|
dw_printf ("\n");
|
||||||
|
|
||||||
|
// Display in pure ASCII if non-ASCII characters and "-d u" option specified.
|
||||||
|
|
||||||
|
if (d_u_opt) {
|
||||||
|
|
||||||
|
unsigned char *p;
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
for (p = pinfo; *p != '\0'; p++) {
|
||||||
|
if (*p >= 0x80) n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n > 0) {
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
ax25_safe_print ((char *)pinfo, info_len, 1);
|
||||||
|
dw_printf ("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Decode the contents of APRS frames and display in human-readable form. */
|
||||||
|
|
||||||
|
if (ax25_is_aprs(pp)) {
|
||||||
|
decode_aprs (pp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send to another application if connected. */
|
||||||
|
|
||||||
|
int flen;
|
||||||
|
unsigned char fbuf[AX25_MAX_PACKET_LEN];
|
||||||
|
|
||||||
|
flen = ax25_pack(pp, fbuf);
|
||||||
|
|
||||||
|
server_send_rec_packet (chan, pp, fbuf, flen);
|
||||||
|
kissnet_send_rec_packet (chan, fbuf, flen);
|
||||||
|
kiss_send_rec_packet (chan, fbuf, flen);
|
||||||
|
|
||||||
|
/* Send to Internet server if option is enabled. */
|
||||||
|
/* Consider only those with correct CRC. */
|
||||||
|
|
||||||
|
if (ax25_is_aprs(pp) && retries == RETRY_NONE) {
|
||||||
|
igate_send_rec_packet (chan, pp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Note that packet can be modified in place so this is the last thing we should do with it. */
|
||||||
|
/* Again, use only those with correct CRC. */
|
||||||
|
/* We don't want to spread corrupted data! */
|
||||||
|
/* Single bit change appears to be safe from observations so far but be cautious. */
|
||||||
|
|
||||||
|
if (ax25_is_aprs(pp) && retries == RETRY_NONE) {
|
||||||
|
digipeater (chan, pp);
|
||||||
|
}
|
||||||
|
|
||||||
|
ax25_delete (pp);
|
||||||
|
|
||||||
|
} /* end app_process_rec_packet */
|
||||||
|
|
||||||
|
|
||||||
|
/* Process control C and window close events. */
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
|
||||||
|
static BOOL cleanup_win (int ctrltype)
|
||||||
|
{
|
||||||
|
if (ctrltype == CTRL_C_EVENT || ctrltype == CTRL_CLOSE_EVENT) {
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("\nQRT\n");
|
||||||
|
ptt_term ();
|
||||||
|
dwgps_term ();
|
||||||
|
SLEEP_SEC(1);
|
||||||
|
ExitProcess (0);
|
||||||
|
}
|
||||||
|
return (TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
static void cleanup_linux (int x)
|
||||||
|
{
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("\nQRT\n");
|
||||||
|
ptt_term ();
|
||||||
|
dwgps_term ();
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static void usage (char **argv)
|
||||||
|
{
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
|
||||||
|
dw_printf ("\n");
|
||||||
|
dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION);
|
||||||
|
dw_printf ("\n");
|
||||||
|
dw_printf ("Usage: direwolf [options]\n");
|
||||||
|
dw_printf ("Options:\n");
|
||||||
|
dw_printf (" -c fname Configuration file name.\n");
|
||||||
|
|
||||||
|
dw_printf (" -r n Audio sample rate, per sec.\n");
|
||||||
|
dw_printf (" -n n Number of audio channels, 1 or 2.\n");
|
||||||
|
dw_printf (" -b n Bits per audio sample, 8 or 16.\n");
|
||||||
|
dw_printf (" -B n Data rate in bits/sec. Standard values are 300, 1200, 9600.\n");
|
||||||
|
dw_printf (" If < 600, AFSK tones are set to 1600 & 1800.\n");
|
||||||
|
dw_printf (" If > 2400, K9NG/G3RUH style encoding is used.\n");
|
||||||
|
dw_printf (" Otherwise, AFSK tones are set to 1200 & 2200.\n");
|
||||||
|
|
||||||
|
dw_printf (" -d Debug communication with client application, one of\n");
|
||||||
|
dw_printf (" a a = AGWPE network protocol.\n");
|
||||||
|
dw_printf (" k k = KISS serial port.\n");
|
||||||
|
dw_printf (" n n = KISS network.\n");
|
||||||
|
dw_printf (" u u = Display non-ASCII text in hexadecimal.\n");
|
||||||
|
|
||||||
|
dw_printf (" -t n Text colors. 1=normal, 0=disabled.\n");
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
#else
|
||||||
|
dw_printf (" -p Enable pseudo terminal for KISS protocol.\n");
|
||||||
|
#endif
|
||||||
|
dw_printf (" -x Send Xmit level calibration tones.\n");
|
||||||
|
dw_printf (" -U Print UTF-8 test string and exit.\n");
|
||||||
|
dw_printf ("\n");
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
#else
|
||||||
|
dw_printf ("Complete documentation can be found in /usr/local/share/doc/direwolf.\n");
|
||||||
|
#endif
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* end direwolf.c */
|
|
@ -0,0 +1,592 @@
|
||||||
|
#############################################################
|
||||||
|
# #
|
||||||
|
# Configuration file for Dire Wolf #
|
||||||
|
# #
|
||||||
|
#############################################################
|
||||||
|
#
|
||||||
|
# Consult the User Guide for more details on configuration options.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# These are the most likely settings you might change:
|
||||||
|
#
|
||||||
|
# (1) MYCALL - call sign and SSID for your station.
|
||||||
|
#
|
||||||
|
# Look for lines starting with MYCALL and
|
||||||
|
# change NOCALL to your own.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# (2) PBEACON - enable position beaconing.
|
||||||
|
#
|
||||||
|
# Look for lines starting with PBEACON and
|
||||||
|
# modify for your call, location, etc.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# (3) DIGIPEATER - configure digipeating rules.
|
||||||
|
#
|
||||||
|
# Look for lines starting with DIGIPEATER.
|
||||||
|
# Most people will probably use the first example.
|
||||||
|
# Just remove the "#" from the start of the line
|
||||||
|
# to enable it.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# (4) IGSERVER, IGLOGIN - IGate server and login
|
||||||
|
#
|
||||||
|
# Configure an IGate client to relay messages between
|
||||||
|
# radio and internet servers.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# The default location is "direwolf.conf" in the current working directory.
|
||||||
|
# On Linux, the user's home directory will also be searched.
|
||||||
|
# An alternate configuration file location can be specified with the "-c" command line option.
|
||||||
|
#
|
||||||
|
# As you probably guessed by now, # indicates a comment line.
|
||||||
|
#
|
||||||
|
# Remove the # at the beginning of a line if you want to use a sample
|
||||||
|
# configuration that is currently commented out.
|
||||||
|
#
|
||||||
|
# Commands are a keyword followed by parameters.
|
||||||
|
#
|
||||||
|
# Command key words are case insensitive. i.e. upper and lower case are equivalent.
|
||||||
|
#
|
||||||
|
# Command parameters are generally case sensitive. i.e. upper and lower case are different.
|
||||||
|
#
|
||||||
|
# Example: The next two are equivalent
|
||||||
|
#
|
||||||
|
# PTT /dev/ttyS0 RTS
|
||||||
|
# ptt /dev/ttyS0 RTS
|
||||||
|
#
|
||||||
|
# But this not equivalent because device names are case sensitive.
|
||||||
|
#
|
||||||
|
# PTT /dev/TTYs0 RTS
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# #
|
||||||
|
# AUDIO DEVICE PROPERTIES #
|
||||||
|
# #
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
#
|
||||||
|
# Many people will simply use the default sound device.
|
||||||
|
# Some might want to use an alternative device by chosing it here.
|
||||||
|
#
|
||||||
|
# When the Windows version starts up, it displays something like
|
||||||
|
# this with the available sound devices and capabilities:
|
||||||
|
#
|
||||||
|
# Available audio input devices for receive (*=selected):
|
||||||
|
# 0: Microphone (Realtek High Defini
|
||||||
|
# 1: Microphone (Bluetooth SCO Audio
|
||||||
|
# 2: Microphone (Bluetooth AV Audio)
|
||||||
|
# 3: Microphone (USB PnP Sound Devic
|
||||||
|
# Available audio output devices for transmit (*=selected):
|
||||||
|
# 0: Speakers (Realtek High Definiti
|
||||||
|
# 1: Speakers (Bluetooth SCO Audio)
|
||||||
|
# 2: Realtek Digital Output (Realtek
|
||||||
|
# 3: Realtek Digital Output(Optical)
|
||||||
|
# 4: Speakers (Bluetooth AV Audio)
|
||||||
|
# 5: Speakers (USB PnP Sound Device)
|
||||||
|
|
||||||
|
# Example: To use the USB Audio, use a command like this with
|
||||||
|
# the input and output device numbers. (Remove the # comment character.)
|
||||||
|
|
||||||
|
#ADEVICE 3 5
|
||||||
|
|
||||||
|
# The position in the list can change when devices (e.g. USB) are added and removed.
|
||||||
|
# You can also specify devices by using part of the name.
|
||||||
|
# Here is an example of specifying the USB Audio device.
|
||||||
|
# This is case-sensitive. Upper and lower case are not treated the same.
|
||||||
|
|
||||||
|
#ADEVICE USB
|
||||||
|
|
||||||
|
|
||||||
|
# Linux ALSA is complicated. See User Guide for discussion.
|
||||||
|
# To use something other than the default, generally use plughw
|
||||||
|
# and a card number reported by "arecord -l" command. Examples:
|
||||||
|
|
||||||
|
# ADEVICE plughw:CARD=Device,DEV=0
|
||||||
|
# ADEVICE plughw:1,0
|
||||||
|
|
||||||
|
# Starting with version 1.0, you can also use "-" or "stdin" to
|
||||||
|
# pipe stdout from some other application such as a software defined
|
||||||
|
# radio. You can also specify "UDP:" and an optional port for input.
|
||||||
|
# Something different must be specified for output.
|
||||||
|
|
||||||
|
# ADEVICE - plughw:1,0
|
||||||
|
# ADEVICE UDP:7355 default
|
||||||
|
|
||||||
|
#
|
||||||
|
# This is the sound card audio sample rate.
|
||||||
|
# The default is 44100. Other standard values are 22050 or 11025.
|
||||||
|
#
|
||||||
|
# Change this only if your computer can't keep up.
|
||||||
|
# A lower rate means lower CPU demands but performance will be degraded.
|
||||||
|
#
|
||||||
|
|
||||||
|
ARATE 44100
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Number of audio channels. 1 or 2.
|
||||||
|
# If you specify 2, it is possible to attach two different transceivers
|
||||||
|
# and receive from both simultaneously.
|
||||||
|
#
|
||||||
|
|
||||||
|
ACHANNELS 1
|
||||||
|
|
||||||
|
# Use this instead if you want to use two transceivers.
|
||||||
|
|
||||||
|
#ACHANNELS 2
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# #
|
||||||
|
# CHANNEL 0 PROPERTIES #
|
||||||
|
# #
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
CHANNEL 0
|
||||||
|
|
||||||
|
#
|
||||||
|
# The following will apply to the first or only channel.
|
||||||
|
# When two channels are used, this is the left audio channel.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Station identifier for this channel.
|
||||||
|
# Multiple channels can have the same or different names.
|
||||||
|
#
|
||||||
|
# It can be up to 6 letters and digits with an optional ssid.
|
||||||
|
# The APRS specification requires that it be upper case.
|
||||||
|
#
|
||||||
|
# Example (don't use this unless you are me): MYCALL WB2OSZ-5
|
||||||
|
#
|
||||||
|
|
||||||
|
MYCALL NOCALL
|
||||||
|
|
||||||
|
#
|
||||||
|
# VHF FM operation normally uses 1200 baud data with AFSK tones of 1200 and 2200 Hz.
|
||||||
|
#
|
||||||
|
|
||||||
|
MODEM 1200 1200 2200
|
||||||
|
|
||||||
|
#
|
||||||
|
# 200 Hz shift is normally used for 300 baud HF SSB operation.
|
||||||
|
#
|
||||||
|
# Note that if you change the tones here, you will need to adjust
|
||||||
|
# your tuning dial accordingly to get the same transmitted frequencies.
|
||||||
|
#
|
||||||
|
# In the second example, we have 7 demodulators spaced 30 Hz apart
|
||||||
|
# to capture signals that are off frequency.
|
||||||
|
# If you run out of CPU power, drop the audio sample rate down to 22050.
|
||||||
|
|
||||||
|
#MODEM 300 1600 1800
|
||||||
|
#MODEM 300 1600 1800 7 30
|
||||||
|
|
||||||
|
#
|
||||||
|
# 9600 baud doesn't use AFSK so no tones are listed.
|
||||||
|
#
|
||||||
|
|
||||||
|
#MODEM 9600
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# If not using a VOX circuit, the transmitter Push to Talk (PTT)
|
||||||
|
# control is usually wired to a serial port with a suitable interface circuit.
|
||||||
|
# DON'T connect it directly!
|
||||||
|
#
|
||||||
|
# For the PTT command, specify the device and either RTS or DTR.
|
||||||
|
# RTS or DTR may be preceded by "-" to invert the signal.
|
||||||
|
#
|
||||||
|
|
||||||
|
#PTT COM1 RTS
|
||||||
|
#PTT COM1 -DTR
|
||||||
|
#PTT /dev/ttyUSB0 RTS
|
||||||
|
|
||||||
|
#
|
||||||
|
# On Linux, you can also use general purpose I/O pins if
|
||||||
|
# your system is configured for user access to them.
|
||||||
|
# This would apply mostly to microprocessor boards, not a regular PC.
|
||||||
|
# See separate Raspberry Pi document for more details.
|
||||||
|
# The number may be preceded by "-" to invert the signal.
|
||||||
|
#
|
||||||
|
|
||||||
|
#PTT GPIO 25
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# After turning on transmitter, send "flag" characters for
|
||||||
|
# TXDELAY * 10 milliseconds for transmitter to stabilize before
|
||||||
|
# sending data. 300 milliseconds is a good default.
|
||||||
|
#
|
||||||
|
|
||||||
|
TXDELAY 30
|
||||||
|
|
||||||
|
#
|
||||||
|
# Keep transmitting for TXTAIL * 10 milliseconds after sending
|
||||||
|
# the data. This is needed to avoid dropping PTT too soon and
|
||||||
|
# chopping of the end of the data because we don't have
|
||||||
|
# precise control of when the sound will actually get out.
|
||||||
|
#
|
||||||
|
|
||||||
|
TXTAIL 10
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# #
|
||||||
|
# CHANNEL 1 PROPERTIES #
|
||||||
|
# #
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
CHANNEL 1
|
||||||
|
|
||||||
|
#
|
||||||
|
# The following will apply to the second (right) channel if ACHANNELS is 2.
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# The two radio channels can have the same or different station identifiers.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Example (don't use this unless you are me): MYCALL WB2OSZ-5
|
||||||
|
#
|
||||||
|
|
||||||
|
MYCALL NOCALL
|
||||||
|
|
||||||
|
MODEM 1200 1200 2200
|
||||||
|
|
||||||
|
#
|
||||||
|
# For this example, we use the same serial port for both
|
||||||
|
# transmitters. RTS for channel 0 and DTR for channel 1.
|
||||||
|
#
|
||||||
|
|
||||||
|
#PTT COM1 DTR
|
||||||
|
|
||||||
|
TXDELAY 30
|
||||||
|
TXTAIL 10
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# #
|
||||||
|
# VIRTUAL TNC SERVER PROPERTIES #
|
||||||
|
# #
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
#
|
||||||
|
# Dire Wolf acts as a virtual TNC and can communicate with
|
||||||
|
# two different protocols:
|
||||||
|
# - the “AGW TCPIP Socket Interface” - default port 8000
|
||||||
|
# - KISS TNC via serial port
|
||||||
|
# - KISS protocol over TCP socket - default port 8001
|
||||||
|
#
|
||||||
|
# See descriptions of AGWPORT, KISSPORT, and NULLMODEM in the
|
||||||
|
# User Guide for more details.
|
||||||
|
#
|
||||||
|
|
||||||
|
AGWPORT 8000
|
||||||
|
KISSPORT 8001
|
||||||
|
|
||||||
|
#
|
||||||
|
# Some applications are designed to operate with only a physical
|
||||||
|
# TNC attached to a serial port. For these, we provide a virtual serial
|
||||||
|
# port ("pseudo terminal" in Linux) that appears to be connected to a TNC.
|
||||||
|
#
|
||||||
|
# Linux:
|
||||||
|
# Linux applications can often specify "/tmp/kisstnc"
|
||||||
|
# for the serial port name. Behind the scenes, Dire Wolf
|
||||||
|
# creates a pseudo terminal. Unfortunately we can't specify the name
|
||||||
|
# and we wouldn't want to reconfigure the application each time.
|
||||||
|
# To get around this, /tmp/kisstnc is a symbolic link to the
|
||||||
|
# non-constant pseudo terminal name.
|
||||||
|
#
|
||||||
|
# Use the -p command line option to enable this feature.
|
||||||
|
#
|
||||||
|
# Windows:
|
||||||
|
#
|
||||||
|
# Microsoft Windows applications need a serial port
|
||||||
|
# name like COM1, COM2, COM3, or COM4.
|
||||||
|
#
|
||||||
|
# Take a look at the User Guide for instructions to set up
|
||||||
|
# two virtual serial ports named COM3 and COM4 connected by
|
||||||
|
# a null modem.
|
||||||
|
#
|
||||||
|
# Using the default configuration, Dire Wolf will connect to
|
||||||
|
# COM3 and the client application will use COM4.
|
||||||
|
#
|
||||||
|
# Uncomment following line to use this feature.
|
||||||
|
|
||||||
|
#NULLMODEM COM3
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Version 0.6 adds a new feature where it is sometimes possible
|
||||||
|
# to recover frames with a bad FCS. Several levels of effort
|
||||||
|
# are possible.
|
||||||
|
#
|
||||||
|
# 0 [NONE] - Don't try to repair.
|
||||||
|
# 1 [SINGLE] - Attempt to fix single bit error. (default)
|
||||||
|
# 2 [DOUBLE] - Also attempt to fix two adjacent bits.
|
||||||
|
# 3 [TRIPLE] - Also attempt to fix three adjacent bits.
|
||||||
|
# 4 [TWO_SEP] - Also attempt to fix two non-adjacent (separated) bits.
|
||||||
|
#
|
||||||
|
|
||||||
|
FIX_BITS 1
|
||||||
|
|
||||||
|
#
|
||||||
|
#############################################################
|
||||||
|
# #
|
||||||
|
# BEACONING PROPERTIES #
|
||||||
|
# #
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Beaconing is configured with these two commands:
|
||||||
|
#
|
||||||
|
# PBEACON - for a position report (usually yourself)
|
||||||
|
# OBEACON - for an object report (usually some other entity)
|
||||||
|
#
|
||||||
|
# Each has a series of keywords and values for options.
|
||||||
|
# See User Guide for details.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
#
|
||||||
|
# This results in a broadcast once every 10 minutes.
|
||||||
|
# Every half hour, it can travel via two digipeater hops.
|
||||||
|
# The others are kept local.
|
||||||
|
#
|
||||||
|
|
||||||
|
#PBEACON delay=00:15 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" via=WIDE1-1,WIDE2-1
|
||||||
|
#PBEACON delay=10:15 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA"
|
||||||
|
#PBEACON delay=20:15 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Modify this for your particular situation before removing
|
||||||
|
# the # comment character from the beginning of the lines above.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# #
|
||||||
|
# DIGIPEATER PROPERTIES #
|
||||||
|
# #
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
#
|
||||||
|
# Digipeating is activated with commands of the form:
|
||||||
|
#
|
||||||
|
# DIGIPEAT from-chan to-chan aliases wide [ preemptive ]
|
||||||
|
#
|
||||||
|
# where,
|
||||||
|
#
|
||||||
|
# from-chan is the channel where the packet is received.
|
||||||
|
#
|
||||||
|
# to-chan is the channel where the packet is to be re-transmitted.
|
||||||
|
#
|
||||||
|
# aliases is a pattern for digipeating ONCE. Anything matching
|
||||||
|
# this pattern is effectively treated like WIDE1-1.
|
||||||
|
# 'MYCALL' for the receiving channel is an implied
|
||||||
|
# member of this list.
|
||||||
|
#
|
||||||
|
# wide is the pattern for normal WIDEn-N digipeating
|
||||||
|
# where the ssid is decremented.
|
||||||
|
#
|
||||||
|
# preemptive is the "preemptive" digipeating option. See
|
||||||
|
# User Guide for more details.
|
||||||
|
#
|
||||||
|
# Pattern matching uses "extended regular expressions." Rather than listing
|
||||||
|
# all the different possibilities (such as "WIDE3-3,WIDE4-4,WIDE5-5,WIDE6-6,WIDE7-7"),
|
||||||
|
# a pattern can be specified such as "^WIDE[34567]-[1-7]$". This means:
|
||||||
|
#
|
||||||
|
# ^ beginning of call. Without this, leading characters
|
||||||
|
# don't need to match and ZWIDE3-3 would end up matching.
|
||||||
|
#
|
||||||
|
# WIDE is an exact literal match of upper case letters W I D E.
|
||||||
|
#
|
||||||
|
# [34567] means ANY ONE of the characters listed.
|
||||||
|
#
|
||||||
|
# - is an exact literal match of the "-" character (when not
|
||||||
|
# found inside of []).
|
||||||
|
#
|
||||||
|
# [1-7] is an alternative form where we have a range of characters
|
||||||
|
# rather than listing them all individually.
|
||||||
|
#
|
||||||
|
# $ means end of call. Without this, trailing characters don't
|
||||||
|
# need to match. As an example, we would end up matching
|
||||||
|
# WIDE3-15 besides WIDE3-1.
|
||||||
|
#
|
||||||
|
# Google "Extended Regular Expressions" for more information.
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# If the first unused digipeater field, in the received packet,
|
||||||
|
# matches the first pattern, it is treated the same way you
|
||||||
|
# would expect WIDE1-1 to behave.
|
||||||
|
#
|
||||||
|
# The digipeater name is replaced by MYCALL of the destination channel.
|
||||||
|
#
|
||||||
|
# Example: W1ABC>APRS,WIDE7-7
|
||||||
|
# Becomes: W1ABC>APRS,WB2OSZ-5*
|
||||||
|
#
|
||||||
|
# In this example, we trap large values of N as recommended in
|
||||||
|
# http://www.aprs.org/fix14439.html
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# If not caught by the first pattern, see if it matches the second pattern.
|
||||||
|
#
|
||||||
|
# Matches will be processed with the usual WIDEn-N rules.
|
||||||
|
#
|
||||||
|
# If N >= 2, the N value is decremented and MYCALL (of the destination
|
||||||
|
# channel) is inserted if enough room.
|
||||||
|
#
|
||||||
|
# Example: W1ABC>APRS,WIDE2-2
|
||||||
|
# Becomes: W1ABC>APRS,WB2OSZ-5*,WIDE2-1
|
||||||
|
#
|
||||||
|
# If N = 1, we don't want to keep WIDEn-0 in the digipeater list so
|
||||||
|
# the station is replaced by MYCALL.
|
||||||
|
#
|
||||||
|
# Example: W1ABC>APRS,W9XYZ*,WIDE2-1
|
||||||
|
# Becomes: W1ABC>APRS,W9XYZ,WB2OSZ-5*
|
||||||
|
#
|
||||||
|
|
||||||
|
#-------------------------------------------------------
|
||||||
|
# ---------- Example 1: Typical digipeater ----------
|
||||||
|
#-------------------------------------------------------
|
||||||
|
|
||||||
|
#
|
||||||
|
# For most common situations, use something like this by removing
|
||||||
|
# the "#" from the beginning of the line.
|
||||||
|
# To disable digipeating, put # at the beginning of the line.
|
||||||
|
#
|
||||||
|
|
||||||
|
# DIGIPEAT 0 0 ^WIDE[3-7]-[1-7]$|^TEST$ ^WIDE[12]-[12]$ TRACE
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# #
|
||||||
|
# INTERNET GATEWAY #
|
||||||
|
# #
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
# First you need to specify the name of a Tier 2 server.
|
||||||
|
# The current preferred way is to use one of these regional rotate addresses:
|
||||||
|
|
||||||
|
# noam.aprs2.net - for North America
|
||||||
|
# soam.aprs2.net - for South America
|
||||||
|
# euro.aprs2.net - for Europe and Africa
|
||||||
|
# asia.aprs2.net - for Asia
|
||||||
|
# aunz.aprs2.net - for Oceania
|
||||||
|
|
||||||
|
#IGSERVER noam.aprs2.net
|
||||||
|
|
||||||
|
# You also need to specify your login name and passcode.
|
||||||
|
# Contact the author if you can't figure out how to generate the passcode.
|
||||||
|
|
||||||
|
#IGLOGIN WB2OSZ-5 123456
|
||||||
|
|
||||||
|
# That's all you need for a receive only IGate which relays
|
||||||
|
# messages from the local radio channel to the global servers.
|
||||||
|
|
||||||
|
# Some might want to send an IGate client position directly to a server
|
||||||
|
# without sending it over the air and relying on someone else to
|
||||||
|
# forward it to an IGate server. This is done by using sendto=IG rather
|
||||||
|
# than a radio channel number. Overlay R for receive only, T for two way.
|
||||||
|
|
||||||
|
#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=R lat=42^37.14N long=071^20.83W
|
||||||
|
#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=T lat=42^37.14N long=071^20.83W
|
||||||
|
|
||||||
|
|
||||||
|
# To relay messages from the Internet to radio, you need to add
|
||||||
|
# one more option with the transmit channel number and a VIA path.
|
||||||
|
|
||||||
|
#IGTXVIA 0 WIDE1-1
|
||||||
|
|
||||||
|
# You might want to apply a filter for what packets will be obtained from the server.
|
||||||
|
# Read about filters here: http://www.aprs2.net/wiki/pmwiki.php/Main/FilterGuide
|
||||||
|
# Example:
|
||||||
|
|
||||||
|
#IGFILTER m/50
|
||||||
|
|
||||||
|
# Finally, we don’t want to flood the radio channel.
|
||||||
|
# The IGate function will limit the number of packets transmitted
|
||||||
|
# during 1 minute and 5 minute intervals. If a limit would
|
||||||
|
# be exceeded, the packet is dropped and message is displayed in red.
|
||||||
|
|
||||||
|
IGTXLIMIT 6 10
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################
|
||||||
|
# #
|
||||||
|
# APRStt GATEWAY #
|
||||||
|
# #
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
#
|
||||||
|
# Dire Wolf can receive DTMF (commonly known as Touch Tone)
|
||||||
|
# messages and convert them to packet objects.
|
||||||
|
#
|
||||||
|
# See "APRStt-Implementation-Notes" document for details.
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# Sample gateway configuration based on:
|
||||||
|
#
|
||||||
|
# http://www.aprs.org/aprstt/aprstt-coding24.txt
|
||||||
|
# http://www.aprs.org/aprs-jamboree-2013.html
|
||||||
|
#
|
||||||
|
|
||||||
|
# Define specific points.
|
||||||
|
|
||||||
|
TTPOINT B01 37^55.37N 81^7.86W
|
||||||
|
TTPOINT B7495088 42.605237 -71.34456
|
||||||
|
TTPOINT B934 42.605237 -71.34456
|
||||||
|
|
||||||
|
TTPOINT B901 42.661279 -71.364452
|
||||||
|
TTPOINT B902 42.660411 -71.364419
|
||||||
|
TTPOINT B903 42.659046 -71.364452
|
||||||
|
TTPOINT B904 42.657578 -71.364602
|
||||||
|
|
||||||
|
|
||||||
|
# For location at given bearing and distance from starting point.
|
||||||
|
|
||||||
|
TTVECTOR B5bbbddd 37^55.37N 81^7.86W 0.01 mi
|
||||||
|
|
||||||
|
# For location specified by x, y coordinates.
|
||||||
|
|
||||||
|
TTGRID Byyyxxx 37^50.00N 81^00.00W 37^59.99N 81^09.99W
|
||||||
|
|
||||||
|
# UTM location for Lowell-Dracut-Tyngsborough State Forest.
|
||||||
|
|
||||||
|
TTUTM B6xxxyyy 19T 10 300000 4720000
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Location for the corral.
|
||||||
|
|
||||||
|
TTCORRAL 37^55.50N 81^7.00W 0^0.02N
|
||||||
|
|
||||||
|
# Compact messages - Fixed locations xx and object yyy where
|
||||||
|
# Object numbers 100 – 199 = bicycle
|
||||||
|
# Object numbers 200 – 299 = fire truck
|
||||||
|
# Others = dog
|
||||||
|
|
||||||
|
TTMACRO xx1yy B9xx*AB166*AA2B4C5B3B0A1yy
|
||||||
|
TTMACRO xx2yy B9xx*AB170*AA3C4C7C3B0A2yy
|
||||||
|
TTMACRO xxyyy B9xx*AB180*AA3A6C4A0Ayyy
|
||||||
|
|
||||||
|
TTMACRO z Cz
|
||||||
|
|
||||||
|
# Transmit object reports on channel 0 with this header.
|
||||||
|
|
||||||
|
#TTOBJ 0 WB2OSZ-5>APDW10
|
||||||
|
|
||||||
|
# Advertise gateway position with beacon.
|
||||||
|
|
||||||
|
# OBEACON DELAY=0:15 EVERY=10:00 VIA=WIDE1-1 OBJNAME=WB2OSZ-tt SYMBOL=APRStt LAT=42^37.14N LONG=71^20.83W COMMENT="APRStt Gateway"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Exec=lxterminal -t "Dire Wolf" -e "/usr/local/bin/direwolf"
|
||||||
|
Name=Dire Wolf
|
||||||
|
Comment=APRS Soundcard TNC
|
||||||
|
Icon=/usr/share/direwolf/dw-icon.png
|
||||||
|
Path=/home/pi
|
||||||
|
#Terminal=true
|
||||||
|
Categories=HamRadio
|
||||||
|
Keywords=Ham Radio;APRS;Soundcard TNC;KISS;AGWPE;AX.25
|
|
@ -0,0 +1,39 @@
|
||||||
|
|
||||||
|
#ifndef DIREWOLF_H
|
||||||
|
#define DIREWOLF_H 1
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Maximum number of radio channels.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define MAX_CHANS 2
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Maximum number of modems per channel.
|
||||||
|
* I called them "subchannels" (in the code) because
|
||||||
|
* it is short and unambiguous.
|
||||||
|
* Nothing magic about the number. Could be larger
|
||||||
|
* but CPU demands might be overwhelming.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define MAX_SUBCHANS 9
|
||||||
|
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
#include <windows.h>
|
||||||
|
#define SLEEP_SEC(n) Sleep((n)*1000)
|
||||||
|
#define SLEEP_MS(n) Sleep(n)
|
||||||
|
#else
|
||||||
|
#define SLEEP_SEC(n) sleep(n)
|
||||||
|
#define SLEEP_MS(n) usleep((n)*1000)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
#define PTW32_STATIC_LIB
|
||||||
|
#include "pthreads/pthread.h"
|
||||||
|
#else
|
||||||
|
#include <pthread.h>
|
||||||
|
#endif
|
|
@ -0,0 +1,248 @@
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011,2012,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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: dsp.c
|
||||||
|
*
|
||||||
|
* Purpose: Generate the filters used by the demodulators.
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "audio.h"
|
||||||
|
#include "fsk_demod_state.h"
|
||||||
|
#include "fsk_gen_filter.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
#include "dsp.h"
|
||||||
|
|
||||||
|
|
||||||
|
//#include "fsk_demod_agc.h" /* for M_FILTER_SIZE, etc. */
|
||||||
|
|
||||||
|
#define MIN(a,b) ((a)<(b)?(a):(b))
|
||||||
|
#define MAX(a,b) ((a)>(b)?(a):(b))
|
||||||
|
|
||||||
|
|
||||||
|
// Don't remove this. It serves as a reminder that an experiment is underway.
|
||||||
|
|
||||||
|
#if defined(TUNE_MS_FILTER_SIZE) || defined(TUNE_AGC_FAST) || defined(TUNE_LPF_BAUD) || defined(TUNE_PLL_LOCKED) || defined(TUNE_PROFILE)
|
||||||
|
#define DEBUG1 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: window
|
||||||
|
*
|
||||||
|
* Purpose: Filter window shape functions.
|
||||||
|
*
|
||||||
|
* Inputs: type - BP_WINDOW_HAMMING, etc.
|
||||||
|
* size - Number of filter taps.
|
||||||
|
* j - Index in range of 0 to size-1.
|
||||||
|
*
|
||||||
|
* Returns: Multiplier for the window shape.
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
float window (bp_window_t type, int size, int j)
|
||||||
|
{
|
||||||
|
float center;
|
||||||
|
float w;
|
||||||
|
|
||||||
|
center = 0.5 * (size - 1);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
|
||||||
|
case BP_WINDOW_COSINE:
|
||||||
|
w = cos((j - center) / size * M_PI);
|
||||||
|
//w = sin(j * M_PI / (size - 1));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BP_WINDOW_HAMMING:
|
||||||
|
w = 0.53836 - 0.46164 * cos((j * 2 * M_PI) / (size - 1));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BP_WINDOW_BLACKMAN:
|
||||||
|
w = 0.42659 - 0.49656 * cos((j * 2 * M_PI) / (size - 1))
|
||||||
|
+ 0.076849 * cos((j * 4 * M_PI) / (size - 1));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BP_WINDOW_FLATTOP:
|
||||||
|
w = 1.0 - 1.93 * cos((j * 2 * M_PI) / (size - 1))
|
||||||
|
+ 1.29 * cos((j * 4 * M_PI) / (size - 1))
|
||||||
|
- 0.388 * cos((j * 6 * M_PI) / (size - 1))
|
||||||
|
+ 0.028 * cos((j * 8 * M_PI) / (size - 1));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BP_WINDOW_TRUNCATED:
|
||||||
|
default:
|
||||||
|
w = 1.0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return (w);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: gen_lowpass
|
||||||
|
*
|
||||||
|
* Purpose: Generate low pass filter kernel.
|
||||||
|
*
|
||||||
|
* Inputs: fc - Cutoff frequency as fraction of sampling frequency.
|
||||||
|
* filter_size - Number of filter taps.
|
||||||
|
* wtype - Window type, BP_WINDOW_HAMMING, etc.
|
||||||
|
*
|
||||||
|
* Outputs: lp_filter
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype)
|
||||||
|
{
|
||||||
|
int j;
|
||||||
|
float lp_sum;
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG1
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
|
||||||
|
dw_printf ("Lowpass, size=%d, fc=%.2f\n", filter_size, fc);
|
||||||
|
dw_printf (" j shape sinc final\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
assert (filter_size >= 3 && filter_size <= MAX_FILTER_SIZE);
|
||||||
|
|
||||||
|
for (j=0; j<filter_size; j++) {
|
||||||
|
float center;
|
||||||
|
float sinc;
|
||||||
|
float shape;
|
||||||
|
|
||||||
|
center = 0.5 * (filter_size - 1);
|
||||||
|
|
||||||
|
if (j - center == 0) {
|
||||||
|
sinc = 2 * fc;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sinc = sin(2 * M_PI * fc * (j-center)) / (M_PI*(j-center));
|
||||||
|
}
|
||||||
|
|
||||||
|
shape = window (wtype, filter_size, j);
|
||||||
|
lp_filter[j] = sinc * shape;
|
||||||
|
|
||||||
|
#if DEBUG1
|
||||||
|
dw_printf ("%6d %6.2f %6.3f %6.3f\n", j, shape, sinc, lp_filter[j] ) ;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Normalize lowpass for unity gain.
|
||||||
|
*/
|
||||||
|
lp_sum = 0;
|
||||||
|
for (j=0; j<filter_size; j++) {
|
||||||
|
lp_sum += lp_filter[j];
|
||||||
|
}
|
||||||
|
for (j=0; j<filter_size; j++) {
|
||||||
|
lp_filter[j] = lp_filter[j] / lp_sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: gen_bandpass
|
||||||
|
*
|
||||||
|
* Purpose: Generate band pass filter kernel.
|
||||||
|
*
|
||||||
|
* Inputs: f1 - Lower cutoff frequency as fraction of sampling frequency.
|
||||||
|
* f2 - Upper cutoff frequency...
|
||||||
|
* filter_size - Number of filter taps.
|
||||||
|
* wtype - Window type, BP_WINDOW_HAMMING, etc.
|
||||||
|
*
|
||||||
|
* Outputs: bp_filter
|
||||||
|
*
|
||||||
|
* Reference: http://www.labbookpages.co.uk/audio/firWindowing.html
|
||||||
|
*
|
||||||
|
* Does it need to be an odd length?
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_window_t wtype)
|
||||||
|
{
|
||||||
|
int j;
|
||||||
|
float bp_sum;
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG1
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
|
||||||
|
dw_printf ("Bandpass, size=%d\n", filter_size);
|
||||||
|
dw_printf (" j shape sinc final\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
assert (filter_size >= 3 && filter_size <= MAX_FILTER_SIZE);
|
||||||
|
|
||||||
|
for (j=0; j<filter_size; j++) {
|
||||||
|
float center;
|
||||||
|
float sinc;
|
||||||
|
float shape;
|
||||||
|
|
||||||
|
center = 0.5 * (filter_size - 1);
|
||||||
|
|
||||||
|
if (j - center == 0) {
|
||||||
|
sinc = 2 * (f2 - f1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sinc = sin(2 * M_PI * f2 * (j-center)) / (M_PI*(j-center))
|
||||||
|
- sin(2 * M_PI * f1 * (j-center)) / (M_PI*(j-center));
|
||||||
|
}
|
||||||
|
|
||||||
|
shape = window (wtype, filter_size, j);
|
||||||
|
bp_filter[j] = sinc * shape;
|
||||||
|
|
||||||
|
#if DEBUG1
|
||||||
|
dw_printf ("%6d %6.2f %6.3f %6.3f\n", j, shape, sinc, bp_filter[j] ) ;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Normalize bandpass for unity gain.
|
||||||
|
*/
|
||||||
|
bp_sum = 0;
|
||||||
|
for (j=0; j<filter_size; j++) {
|
||||||
|
bp_sum += bp_filter[j];
|
||||||
|
}
|
||||||
|
for (j=0; j<filter_size; j++) {
|
||||||
|
bp_filter[j] = bp_filter[j] / bp_sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* end dsp.c */
|
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
/* dsp.h */
|
||||||
|
|
||||||
|
// TODO: put prefixes on these names.
|
||||||
|
|
||||||
|
float window (bp_window_t type, int size, int j);
|
||||||
|
|
||||||
|
void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype);
|
||||||
|
|
||||||
|
void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_window_t wtype);
|
|
@ -0,0 +1,411 @@
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Module: dtmf.c
|
||||||
|
*
|
||||||
|
* Purpose: Decoder for DTMF, commonly known as "touch tones."
|
||||||
|
*
|
||||||
|
* Description: This uses the Goertzel Algorithm for tone detection.
|
||||||
|
*
|
||||||
|
* References: http://eetimes.com/design/embedded/4024443/The-Goertzel-Algorithm
|
||||||
|
* http://www.ti.com/ww/cn/uprogram/share/ppt/c5000/17dtmf_v13.ppt
|
||||||
|
*
|
||||||
|
*---------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "dtmf.h"
|
||||||
|
|
||||||
|
|
||||||
|
// Define for unit test.
|
||||||
|
//#define DTMF_TEST 1
|
||||||
|
|
||||||
|
|
||||||
|
#if DTMF_TEST
|
||||||
|
#define TIMEOUT_SEC 1 /* short for unit test below. */
|
||||||
|
#define DEBUG 1
|
||||||
|
#else
|
||||||
|
#define TIMEOUT_SEC 5 /* for normal operation. */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#define NUM_TONES 8
|
||||||
|
static int const dtmf_tones[NUM_TONES] = { 697, 770, 852, 941, 1209, 1336, 1477, 1633 };
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Current state of the decoding.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
int sample_rate; /* Samples per sec. Typ. 44100, 8000, etc. */
|
||||||
|
int block_size; /* Number of samples to process in one block. */
|
||||||
|
float coef[NUM_TONES];
|
||||||
|
|
||||||
|
struct { /* Separate for each audio channel. */
|
||||||
|
|
||||||
|
int n; /* Samples processed in this block. */
|
||||||
|
float Q1[NUM_TONES];
|
||||||
|
float Q2[NUM_TONES];
|
||||||
|
char prev_dec;
|
||||||
|
char debounced;
|
||||||
|
char prev_debounced;
|
||||||
|
int timeout;
|
||||||
|
} C[MAX_CHANS];
|
||||||
|
} D;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: dtmf_init
|
||||||
|
*
|
||||||
|
* Purpose: Initialize the DTMF decoder.
|
||||||
|
* This should be called once at application start up time.
|
||||||
|
*
|
||||||
|
* Inputs: sample_rate - Audio sample frequency, typically
|
||||||
|
* 44100, 22050, 8000, etc.
|
||||||
|
*
|
||||||
|
* Returns: None.
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
void dtmf_init (int sample_rate)
|
||||||
|
{
|
||||||
|
int j; /* Loop over all tones frequencies. */
|
||||||
|
int c; /* Loop over all audio channels. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Processing block size.
|
||||||
|
* Larger = narrower bandwidth, slower response.
|
||||||
|
*/
|
||||||
|
D.sample_rate = sample_rate;
|
||||||
|
D.block_size = (205 * sample_rate) / 8000;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
dw_printf (" freq k coef \n");
|
||||||
|
#endif
|
||||||
|
for (j=0; j<NUM_TONES; j++) {
|
||||||
|
float k;
|
||||||
|
|
||||||
|
|
||||||
|
// Why do some insist on rounding k to the nearest integer?
|
||||||
|
// That would move the filter center frequency away from ideal.
|
||||||
|
// What is to be gained?
|
||||||
|
// More consistent results for all the tones when k is not rounded off.
|
||||||
|
|
||||||
|
k = D.block_size * (float)(dtmf_tones[j]) / (float)(D.sample_rate);
|
||||||
|
|
||||||
|
D.coef[j] = 2 * cos(2 * M_PI * (float)k / (float)(D.block_size));
|
||||||
|
|
||||||
|
assert (D.coef[j] > 0 && D.coef[j] < 2.0);
|
||||||
|
#if DEBUG
|
||||||
|
dw_printf ("%8d %5.1f %8.5f \n", dtmf_tones[j], k, D.coef[j]);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
for (c=0; c<MAX_CHANS; c++) {
|
||||||
|
D.C[c].n = 0;
|
||||||
|
for (j=0; j<NUM_TONES; j++) {
|
||||||
|
D.C[c].Q1[j] = 0;
|
||||||
|
D.C[c].Q2[j] = 0;
|
||||||
|
}
|
||||||
|
D.C[c].prev_dec = ' ';
|
||||||
|
D.C[c].debounced = ' ';
|
||||||
|
D.C[c].prev_debounced = ' ';
|
||||||
|
D.C[c].timeout = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: dtmf_sample
|
||||||
|
*
|
||||||
|
* Purpose: Process one audio sample from the sound input source.
|
||||||
|
*
|
||||||
|
* Inputs: c - Audio channel number.
|
||||||
|
* This can process multiple channels in parallel.
|
||||||
|
* input - Audio sample.
|
||||||
|
*
|
||||||
|
* Returns: 0123456789ABCD*# for a button push.
|
||||||
|
* . for nothing happening during sample interval.
|
||||||
|
* $ after several seconds of inactivity.
|
||||||
|
* space between sample intervals.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
__attribute__((hot))
|
||||||
|
char dtmf_sample (int c, float input)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
float Q0;
|
||||||
|
float output[NUM_TONES];
|
||||||
|
char decoded;
|
||||||
|
char ret;
|
||||||
|
static const char rc2char[16] = { '1', '2', '3', 'A',
|
||||||
|
'4', '5', '6', 'B',
|
||||||
|
'7', '8', '9', 'C',
|
||||||
|
'*', '0', '#', 'D' };
|
||||||
|
for (i=0; i<NUM_TONES; i++) {
|
||||||
|
Q0 = input + D.C[c].Q1[i] * D.coef[i] - D.C[c].Q2[i];
|
||||||
|
D.C[c].Q2[i] = D.C[c].Q1[i];
|
||||||
|
D.C[c].Q1[i] = Q0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Is it time to process the block?
|
||||||
|
*/
|
||||||
|
D.C[c].n++;
|
||||||
|
if (D.C[c].n == D.block_size) {
|
||||||
|
int row, col;
|
||||||
|
|
||||||
|
for (i=0; i<NUM_TONES; i++) {
|
||||||
|
output[i] = sqrt(D.C[c].Q1[i] * D.C[c].Q1[i] + D.C[c].Q2[i] * D.C[c].Q2[i] - D.C[c].Q1[i] * D.C[c].Q2[i] * D.coef[i]);
|
||||||
|
D.C[c].Q1[i] = 0;
|
||||||
|
D.C[c].Q2[i] = 0;
|
||||||
|
}
|
||||||
|
D.C[c].n = 0;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The input signal can vary over a couple orders of
|
||||||
|
* magnitude so we can't set some absolute threshold.
|
||||||
|
*
|
||||||
|
* See if one tone is stronger than the sum of the
|
||||||
|
* others in the same group multiplied by some factor.
|
||||||
|
*
|
||||||
|
* For perfect synthetic signals this needs to be in
|
||||||
|
* the range of about 1.33 (very senstive) to 2.15 (very fussy).
|
||||||
|
*
|
||||||
|
* Too low will cause false triggers on random noise.
|
||||||
|
* Too high will won't decode less than perfect signals.
|
||||||
|
*
|
||||||
|
* Use the mid point 1.74 as our initial guess.
|
||||||
|
* It might need some fine tuning for imperfect real world signals.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#define THRESHOLD 1.74
|
||||||
|
|
||||||
|
if (output[0] > THRESHOLD * ( output[1] + output[2] + output[3])) row = 0;
|
||||||
|
else if (output[1] > THRESHOLD * (output[0] + output[2] + output[3])) row = 1;
|
||||||
|
else if (output[2] > THRESHOLD * (output[0] + output[1] + output[3])) row = 2;
|
||||||
|
else if (output[3] > THRESHOLD * (output[0] + output[1] + output[2] )) row = 3;
|
||||||
|
else row = -1;
|
||||||
|
|
||||||
|
if (output[4] > THRESHOLD * ( output[5] + output[6] + output[7])) col = 0;
|
||||||
|
else if (output[5] > THRESHOLD * (output[4] + output[6] + output[7])) col = 1;
|
||||||
|
else if (output[6] > THRESHOLD * (output[4] + output[5] + output[7])) col = 2;
|
||||||
|
else if (output[7] > THRESHOLD * (output[4] + output[5] + output[6] )) col = 3;
|
||||||
|
else col = -1;
|
||||||
|
|
||||||
|
for (i=0; i<NUM_TONES; i++) {
|
||||||
|
#if DEBUG
|
||||||
|
dw_printf ("%5.0f ", output[i]);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
if (row >= 0 && col >= 0) {
|
||||||
|
decoded = rc2char[row*4+col];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
decoded = '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consider valid only if we get same twice in a row.
|
||||||
|
|
||||||
|
if (decoded == D.C[c].prev_dec) {
|
||||||
|
D.C[c].debounced = decoded;
|
||||||
|
/* Reset timeout timer. */
|
||||||
|
if (decoded != ' ') {
|
||||||
|
D.C[c].timeout = ((TIMEOUT_SEC) * D.sample_rate) / D.block_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
D.C[c].prev_dec = decoded;
|
||||||
|
|
||||||
|
// Return only new button pushes.
|
||||||
|
// Also report timeout after period of inactivity.
|
||||||
|
|
||||||
|
ret = '.';
|
||||||
|
if (D.C[c].debounced != D.C[c].prev_debounced) {
|
||||||
|
if (D.C[c].debounced != ' ') {
|
||||||
|
ret = D.C[c].debounced;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ret == '.') {
|
||||||
|
if (D.C[c].timeout > 0) {
|
||||||
|
D.C[c].timeout--;
|
||||||
|
if (D.C[c].timeout == 0) {
|
||||||
|
ret = '$';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
D.C[c].prev_debounced = D.C[c].debounced;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
dw_printf (" dec=%c, deb=%c, ret=%c \n",
|
||||||
|
decoded, D.C[c].debounced, ret);
|
||||||
|
#endif
|
||||||
|
return (ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: main
|
||||||
|
*
|
||||||
|
* Purpose: Unit test for functions above.
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
#if DTMF_TEST
|
||||||
|
|
||||||
|
push_button (char button, int ms)
|
||||||
|
{
|
||||||
|
static float phasea = 0;
|
||||||
|
static float phaseb = 0;
|
||||||
|
float fa, fb;
|
||||||
|
int i;
|
||||||
|
float input;
|
||||||
|
char x;
|
||||||
|
static char result[100];
|
||||||
|
static int result_len = 0;
|
||||||
|
|
||||||
|
|
||||||
|
switch (button) {
|
||||||
|
case '1': fa = dtmf_tones[0]; fb = dtmf_tones[4]; break;
|
||||||
|
case '2': fa = dtmf_tones[0]; fb = dtmf_tones[5]; break;
|
||||||
|
case '3': fa = dtmf_tones[0]; fb = dtmf_tones[6]; break;
|
||||||
|
case 'A': fa = dtmf_tones[0]; fb = dtmf_tones[7]; break;
|
||||||
|
case '4': fa = dtmf_tones[1]; fb = dtmf_tones[4]; break;
|
||||||
|
case '5': fa = dtmf_tones[1]; fb = dtmf_tones[5]; break;
|
||||||
|
case '6': fa = dtmf_tones[1]; fb = dtmf_tones[6]; break;
|
||||||
|
case 'B': fa = dtmf_tones[1]; fb = dtmf_tones[7]; break;
|
||||||
|
case '7': fa = dtmf_tones[2]; fb = dtmf_tones[4]; break;
|
||||||
|
case '8': fa = dtmf_tones[2]; fb = dtmf_tones[5]; break;
|
||||||
|
case '9': fa = dtmf_tones[2]; fb = dtmf_tones[6]; break;
|
||||||
|
case 'C': fa = dtmf_tones[2]; fb = dtmf_tones[7]; break;
|
||||||
|
case '*': fa = dtmf_tones[3]; fb = dtmf_tones[4]; break;
|
||||||
|
case '0': fa = dtmf_tones[3]; fb = dtmf_tones[5]; break;
|
||||||
|
case '#': fa = dtmf_tones[3]; fb = dtmf_tones[6]; break;
|
||||||
|
case 'D': fa = dtmf_tones[3]; fb = dtmf_tones[7]; break;
|
||||||
|
case '?':
|
||||||
|
|
||||||
|
if (strcmp(result, "123A456B789C*0#D123$789$") == 0) {
|
||||||
|
dw_printf ("\nSuccess!\n");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dw_printf ("\n *** TEST FAILED ***\n");
|
||||||
|
dw_printf ("\"%s\"\n", result);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: fa = 0; fb = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < (ms*D.sample_rate)/1000; i++) {
|
||||||
|
|
||||||
|
input = sin(phasea) + sin(phaseb);
|
||||||
|
phasea += 2 * M_PI * fa / D.sample_rate;
|
||||||
|
phaseb += 2 * M_PI * fb / D.sample_rate;
|
||||||
|
|
||||||
|
/* Make sure it is insensitive to signal amplitude. */
|
||||||
|
|
||||||
|
x = dtmf_sample (0, input);
|
||||||
|
//x = dtmf_sample (0, input * 1000);
|
||||||
|
//x = dtmf_sample (0, input * 0.001);
|
||||||
|
|
||||||
|
if (x != ' ' && x != '.') {
|
||||||
|
result[result_len] = x;
|
||||||
|
result_len++;
|
||||||
|
result[result_len] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main ()
|
||||||
|
{
|
||||||
|
|
||||||
|
dtmf_init(44100);
|
||||||
|
|
||||||
|
dw_printf ("\nFirst, check all button tone pairs. \n\n");
|
||||||
|
/* Max auto dialing rate is 10 per second. */
|
||||||
|
|
||||||
|
push_button ('1', 50); push_button (' ', 50);
|
||||||
|
push_button ('2', 50); push_button (' ', 50);
|
||||||
|
push_button ('3', 50); push_button (' ', 50);
|
||||||
|
push_button ('A', 50); push_button (' ', 50);
|
||||||
|
|
||||||
|
push_button ('4', 50); push_button (' ', 50);
|
||||||
|
push_button ('5', 50); push_button (' ', 50);
|
||||||
|
push_button ('6', 50); push_button (' ', 50);
|
||||||
|
push_button ('B', 50); push_button (' ', 50);
|
||||||
|
|
||||||
|
push_button ('7', 50); push_button (' ', 50);
|
||||||
|
push_button ('8', 50); push_button (' ', 50);
|
||||||
|
push_button ('9', 50); push_button (' ', 50);
|
||||||
|
push_button ('C', 50); push_button (' ', 50);
|
||||||
|
|
||||||
|
push_button ('*', 50); push_button (' ', 50);
|
||||||
|
push_button ('0', 50); push_button (' ', 50);
|
||||||
|
push_button ('#', 50); push_button (' ', 50);
|
||||||
|
push_button ('D', 50); push_button (' ', 50);
|
||||||
|
|
||||||
|
dw_printf ("\nShould reject very short pulses.\n\n");
|
||||||
|
|
||||||
|
push_button ('1', 20); push_button (' ', 50);
|
||||||
|
push_button ('1', 20); push_button (' ', 50);
|
||||||
|
push_button ('1', 20); push_button (' ', 50);
|
||||||
|
push_button ('1', 20); push_button (' ', 50);
|
||||||
|
push_button ('1', 20); push_button (' ', 50);
|
||||||
|
|
||||||
|
dw_printf ("\nTest timeout after inactivity.\n\n");
|
||||||
|
/* For this test we use 1 second. */
|
||||||
|
/* In practice, it will probably more like 10 or 20. */
|
||||||
|
|
||||||
|
push_button ('1', 250); push_button (' ', 500);
|
||||||
|
push_button ('2', 250); push_button (' ', 500);
|
||||||
|
push_button ('3', 250); push_button (' ', 1200);
|
||||||
|
|
||||||
|
push_button ('7', 250); push_button (' ', 500);
|
||||||
|
push_button ('8', 250); push_button (' ', 500);
|
||||||
|
push_button ('9', 250); push_button (' ', 1200);
|
||||||
|
|
||||||
|
/* Check for expected results. */
|
||||||
|
|
||||||
|
push_button ('?', 0);
|
||||||
|
|
||||||
|
} /* end main */
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* end dtmf.c */
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
/* dtmf.h */
|
||||||
|
|
||||||
|
|
||||||
|
void dtmf_init (int sample_rate);
|
||||||
|
|
||||||
|
char dtmf_sample (int c, float input);
|
||||||
|
|
||||||
|
|
||||||
|
/* end dtmf.h */
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 361 KiB |
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
|
@ -0,0 +1 @@
|
||||||
|
MAINICON ICON "dw-icon.ico"
|
|
@ -0,0 +1,77 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
#
|
||||||
|
# Run this from crontab periodically to start up
|
||||||
|
# Dire Wolf automatically.
|
||||||
|
#
|
||||||
|
# I prefer this method instead of putting something
|
||||||
|
# in ~/.config/autostart. That would start an application
|
||||||
|
# only when the desktop first starts up.
|
||||||
|
#
|
||||||
|
# This method will restart the application if it
|
||||||
|
# crashes or stops for any other reason.
|
||||||
|
#
|
||||||
|
# This script has some specifics the Raspberry Pi.
|
||||||
|
# Some adjustments might be needed for other Linux variations.
|
||||||
|
#
|
||||||
|
# First wait a little while in case we just rebooted
|
||||||
|
# and the desktop hasn't started up yet.
|
||||||
|
#
|
||||||
|
|
||||||
|
sleep 30
|
||||||
|
|
||||||
|
#
|
||||||
|
# Nothing to do if it is already running.
|
||||||
|
#
|
||||||
|
|
||||||
|
a=`ps -ef | grep direwolf | grep -v grep`
|
||||||
|
if [ "$a" != "" ]
|
||||||
|
then
|
||||||
|
#date >> /tmp/dw-start.log
|
||||||
|
#echo "Already running." >> /tmp/dw-start.log
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
#
|
||||||
|
# In my case, the Raspberry Pi is not connected to a monitor.
|
||||||
|
# I access it remotely using VNC as described here:
|
||||||
|
# http://learn.adafruit.com/adafruit-raspberry-pi-lesson-7-remote-control-with-vnc
|
||||||
|
#
|
||||||
|
# If VNC server is running, use its display number.
|
||||||
|
# Otherwise default to :0.
|
||||||
|
#
|
||||||
|
|
||||||
|
date >> /tmp/dw-start.log
|
||||||
|
|
||||||
|
export DISPLAY=":0"
|
||||||
|
|
||||||
|
v=`ps -ef | grep Xtightvnc | grep -v grep`
|
||||||
|
if [ "$v" != "" ]
|
||||||
|
then
|
||||||
|
d=`echo "$v" | sed 's/.*tightvnc *\(:[0-9]\).*/\1/'`
|
||||||
|
export DISPLAY="$d"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "DISPLAY=$DISPLAY" >> /tmp/dw-start.log
|
||||||
|
|
||||||
|
echo "Start up application." >> /tmp/dw-start.log
|
||||||
|
|
||||||
|
#
|
||||||
|
# Adjust for your particular situation: gnome-terminal, xterm, etc.
|
||||||
|
#
|
||||||
|
|
||||||
|
if [ -x /usr/bin/lxterminal ]
|
||||||
|
then
|
||||||
|
/usr/bin/lxterminal -t "Dire Wolf" -e "/usr/local/bin/direwolf" &
|
||||||
|
elif [ -x /usr/bin/xterm ]
|
||||||
|
then
|
||||||
|
/usr/bin/xterm -bg white -fg black -e /usr/local/bin/direwolf &
|
||||||
|
elif [ -x /usr/bin/x-terminal-emulator ]
|
||||||
|
then
|
||||||
|
/usr/bin/x-terminal-emulator -e /usr/local/bin/direwolf &
|
||||||
|
else
|
||||||
|
echo "Did not find an X terminal emulator."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "-----------------------" >> /tmp/dw-start.log
|
||||||
|
|
|
@ -0,0 +1,327 @@
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Module: dwgps.c
|
||||||
|
*
|
||||||
|
* Purpose: Interface to location data, i.e. GPS receiver.
|
||||||
|
*
|
||||||
|
* Description: Tracker beacons need to know the current location.
|
||||||
|
* At this time, I can't think of any other reason why
|
||||||
|
* we would need this information.
|
||||||
|
*
|
||||||
|
* For Linux, we use gpsd and libgps.
|
||||||
|
* This has the extra benefit that the system clock can
|
||||||
|
* be set from the GPS signal.
|
||||||
|
*
|
||||||
|
* Not yet implemented for Windows. Not sure how yet.
|
||||||
|
* The Windows location API is new in Windows 7.
|
||||||
|
* At the end of 2013, about 1/3 of Windows users are
|
||||||
|
* still using XP so that still needs to be supported.
|
||||||
|
*
|
||||||
|
* Reference:
|
||||||
|
*
|
||||||
|
*---------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#if TEST
|
||||||
|
#define ENABLE_GPS 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#if ENABLE_GPS
|
||||||
|
#include <gps.h>
|
||||||
|
|
||||||
|
#if GPSD_API_MAJOR_VERSION != 5
|
||||||
|
#error libgps API version might be incompatible.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
#include "dwgps.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* Was init successful? */
|
||||||
|
|
||||||
|
static enum { INIT_NOT_YET, INIT_SUCCESS, INIT_FAILED } init_status = INIT_NOT_YET;
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#if ENABLE_GPS
|
||||||
|
|
||||||
|
static struct gps_data_t gpsdata;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: dwgps_init
|
||||||
|
*
|
||||||
|
* Purpose: Intialize the GPS interface.
|
||||||
|
*
|
||||||
|
* Inputs: none.
|
||||||
|
*
|
||||||
|
* Returns: 0 = success
|
||||||
|
* -1 = failure
|
||||||
|
*
|
||||||
|
* Description: For Linux, this maps into gps_open.
|
||||||
|
* Not yet implemented for Windows.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
int dwgps_init (void)
|
||||||
|
{
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("GPS interface not yet available in Windows version.\n");
|
||||||
|
init_status = INIT_FAILED;
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
#elif ENABLE_GPS
|
||||||
|
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = gps_open (GPSD_SHARED_MEMORY, NULL, &gpsdata);
|
||||||
|
if (err != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Unable to connect to GPS receiver.\n");
|
||||||
|
if (err == NL_NOHOST) {
|
||||||
|
dw_printf ("Shared memory interface is not enabled in libgps.\n");
|
||||||
|
dw_printf ("Download the gpsd source and build with 'shm_export=True' option.\n");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dw_printf ("%s\n", gps_errstr(errno));
|
||||||
|
}
|
||||||
|
init_status = INIT_FAILED;
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
init_status = INIT_SUCCESS;
|
||||||
|
return (0);
|
||||||
|
#else
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("GPS interface not enabled in this version.\n");
|
||||||
|
dw_printf ("See documentation on how to rebuild with ENABLE_GPS.\n");
|
||||||
|
init_status = INIT_FAILED;
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} /* end dwgps_init */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: dwgps_read
|
||||||
|
*
|
||||||
|
* Purpose: Obtain current location from GPS receiver.
|
||||||
|
*
|
||||||
|
* Outputs: *plat - Latitude.
|
||||||
|
* *plon - Longitude.
|
||||||
|
* *pspeed - Speed, knots.
|
||||||
|
* *pcourse - Course over ground, degrees.
|
||||||
|
* *palt - Altitude, meters.
|
||||||
|
*
|
||||||
|
* Returns: -1 = error
|
||||||
|
* 0 = data not available (no fix)
|
||||||
|
* 2 = 2D fix, lat/lon, speed, and course are set.
|
||||||
|
* 3 - 3D fix, altitude is also set.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
int dwgps_read (double *plat, double *plon, float *pspeed, float *pcourse, float *palt)
|
||||||
|
{
|
||||||
|
#if __WIN32__
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Internal error, dwgps_read, shouldn't be here.\n");
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
#elif ENABLE_GPS
|
||||||
|
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (init_status != INIT_SUCCESS) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Internal error, dwgps_read without successful init.\n");
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
err = gps_read (&gpsdata);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
dw_printf ("gps_read returns %d bytes\n", err);
|
||||||
|
#endif
|
||||||
|
if (err > 0) {
|
||||||
|
if (gpsdata.status >= STATUS_FIX && gpsdata.fix.mode >= MODE_2D) {
|
||||||
|
|
||||||
|
*plat = gpsdata.fix.latitude;
|
||||||
|
*plon = gpsdata.fix.longitude;
|
||||||
|
*pcourse = gpsdata.fix.track;
|
||||||
|
*pspeed = MPS_TO_KNOTS * gpsdata.fix.speed; /* libgps uses meters/sec */
|
||||||
|
|
||||||
|
if (gpsdata.fix.mode >= MODE_3D) {
|
||||||
|
*palt = gpsdata.fix.altitude;
|
||||||
|
return (3);
|
||||||
|
}
|
||||||
|
return (2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No fix. Probably temporary condition. */
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
else if (err == 0) {
|
||||||
|
/* No data available */
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* More serious error. */
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Internal error, dwgps_read, shouldn't be here.\n");
|
||||||
|
return (-1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} /* end dwgps_read */
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: dwgps_term
|
||||||
|
*
|
||||||
|
* Purpose: Shut down GPS interface before exiting from application.
|
||||||
|
*
|
||||||
|
* Inputs: none.
|
||||||
|
*
|
||||||
|
* Returns: none.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
void dwgps_term (void) {
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
|
||||||
|
#elif ENABLE_GPS
|
||||||
|
|
||||||
|
if (init_status == INIT_SUCCESS) {
|
||||||
|
gps_close (&gpsdata);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} /* end dwgps_term */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: main
|
||||||
|
*
|
||||||
|
* Purpose: Simple unit test for other functions in this file.
|
||||||
|
*
|
||||||
|
* Description: Compile with -DTEST option.
|
||||||
|
*
|
||||||
|
* gcc -DTEST dwgps.c textcolor.c -lgps
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#if TEST
|
||||||
|
|
||||||
|
int main (int argc, char *argv[])
|
||||||
|
{
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
|
||||||
|
printf ("Not in win32 version yet.\n");
|
||||||
|
|
||||||
|
#elif ENABLE_GPS
|
||||||
|
int err;
|
||||||
|
int fix;
|
||||||
|
double lat;
|
||||||
|
double lon;
|
||||||
|
float speed;
|
||||||
|
float course;
|
||||||
|
float alt;
|
||||||
|
|
||||||
|
err = dwgps_init ();
|
||||||
|
|
||||||
|
if (err != 0) exit(1);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
fix = dwgps_read (&lat, &lon, &speed, &course, &alt)
;
|
||||||
|
switch (fix) {
|
||||||
|
case 3:
|
||||||
|
case 2:
|
||||||
|
dw_printf ("%.6f %.6f", lat, lon);
|
||||||
|
dw_printf (" %.1f knots %.0f degrees", speed, course);
|
||||||
|
if (fix==3) dw_printf (" altitude = %.1f meters", alt);
|
||||||
|
dw_printf ("\n");
|
||||||
|
break;
|
||||||
|
case 0:
|
||||||
|
dw_printf ("location currently not available.\n");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
dw_printf ("ERROR getting GPS information.\n");
|
||||||
|
}
|
||||||
|
sleep (3);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
printf ("Test: Shouldn't be here.\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} /* end main */
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* end dwgps.c */
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
/* dwgps.h */
|
||||||
|
|
||||||
|
|
||||||
|
int dwgps_init (void);
|
||||||
|
|
||||||
|
int dwgps_read (double *plat, double *plon, float *pspeed, float *pcourse, float *palt);
|
||||||
|
|
||||||
|
void dwgps_term (void);
|
||||||
|
|
||||||
|
|
||||||
|
/* end dwgps.h */
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,797 @@
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Module: encode_aprs.c
|
||||||
|
*
|
||||||
|
* Purpose: Construct APRS packets from components.
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
*
|
||||||
|
* References: APRS Protocol Reference.
|
||||||
|
*
|
||||||
|
* Frequency spec.
|
||||||
|
* http://www.aprs.org/info/freqspec.txt
|
||||||
|
*
|
||||||
|
*---------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "encode_aprs.h"
|
||||||
|
#include "latlong.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: set_norm_position
|
||||||
|
*
|
||||||
|
* Purpose: Fill in the human-readable latitude, longitude,
|
||||||
|
* symbol part which is common to multiple data formats.
|
||||||
|
*
|
||||||
|
* Inputs: symtab - Symbol table id or overlay.
|
||||||
|
* symbol - Symbol id.
|
||||||
|
* dlat - Latitude.
|
||||||
|
* dlong - Longitude.
|
||||||
|
*
|
||||||
|
* Outputs: presult - Stored here.
|
||||||
|
*
|
||||||
|
* Returns: Number of characters in result.
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/* Position & symbol fields common to several message formats. */
|
||||||
|
|
||||||
|
typedef struct position_s {
|
||||||
|
char lat[8];
|
||||||
|
char sym_table_id; /* / \ 0-9 A-Z */
|
||||||
|
char lon[9];
|
||||||
|
char symbol_code;
|
||||||
|
} position_t;
|
||||||
|
|
||||||
|
|
||||||
|
static int set_norm_position (char symtab, char symbol, double dlat, double dlong, position_t *presult)
|
||||||
|
{
|
||||||
|
|
||||||
|
latitude_to_str (dlat, 0, presult->lat);
|
||||||
|
|
||||||
|
if (symtab != '/' && symtab != '\\' && ! isdigit(symtab) && ! isupper(symtab)) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Symbol table identifier is not one of / \\ 0-9 A-Z\n");
|
||||||
|
}
|
||||||
|
presult->sym_table_id = symtab;
|
||||||
|
|
||||||
|
longitude_to_str (dlong, 0, presult->lon);
|
||||||
|
|
||||||
|
if (symbol < '!' || symbol > '~') {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Symbol code is not in range of ! to ~\n");
|
||||||
|
}
|
||||||
|
presult->symbol_code = symbol;
|
||||||
|
|
||||||
|
return (sizeof(position_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: set_comp_position
|
||||||
|
*
|
||||||
|
* Purpose: Fill in the compressed latitude, longitude,
|
||||||
|
* symbol part which is common to multiple data formats.
|
||||||
|
*
|
||||||
|
* Inputs: symtab - Symbol table id or overlay.
|
||||||
|
* symbol - Symbol id.
|
||||||
|
* dlat - Latitude.
|
||||||
|
* dlong - Longitude.
|
||||||
|
*
|
||||||
|
* power - Watts.
|
||||||
|
* height - Feet.
|
||||||
|
* gain - dBi.
|
||||||
|
*
|
||||||
|
* course - Degress, 1 - 360. 0 means none or unknown.
|
||||||
|
* speed - knots.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Outputs: presult - Stored here.
|
||||||
|
*
|
||||||
|
* Returns: Number of characters in result.
|
||||||
|
*
|
||||||
|
* Description: The cst field can have only one of
|
||||||
|
*
|
||||||
|
* course/speed - takes priority (this implementation)
|
||||||
|
* radio range - calculated from PHG
|
||||||
|
* altitude - not implemented yet.
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/* Compressed position & symbol fields common to several message formats. */
|
||||||
|
|
||||||
|
typedef struct compressed_position_s {
|
||||||
|
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." */
|
||||||
|
|
||||||
|
char y[4]; /* Compressed Latitude. */
|
||||||
|
char x[4]; /* Compressed Longitude. */
|
||||||
|
char symbol_code;
|
||||||
|
char c; /* Course/speed or radio range or altitude. */
|
||||||
|
char s;
|
||||||
|
char t ; /* Compression type. */
|
||||||
|
} compressed_position_t;
|
||||||
|
|
||||||
|
|
||||||
|
static int set_comp_position (char symtab, char symbol, double dlat, double dlong,
|
||||||
|
int power, int height, int gain,
|
||||||
|
int course, int speed,
|
||||||
|
compressed_position_t *presult)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (symtab != '/' && symtab != '\\' && ! isdigit(symtab) && ! isupper(symtab)) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Symbol table identifier is not one of / \\ 0-9 A-Z\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In compressed format, the characters a-j are used for a numeric overlay.
|
||||||
|
* This allows the receiver to distinguish between compressed and normal formats.
|
||||||
|
*/
|
||||||
|
if (isdigit(symtab)) {
|
||||||
|
symtab = symtab - '0' + 'a';
|
||||||
|
}
|
||||||
|
presult->sym_table_id = symtab;
|
||||||
|
|
||||||
|
latitude_to_comp_str (dlat, presult->y);
|
||||||
|
longitude_to_comp_str (dlong, presult->x);
|
||||||
|
|
||||||
|
if (symbol < '!' || symbol > '~') {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Symbol code is not in range of ! to ~\n");
|
||||||
|
}
|
||||||
|
presult->symbol_code = symbol;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The cst field is complicated.
|
||||||
|
*
|
||||||
|
* When c is ' ', the cst field is not used.
|
||||||
|
*
|
||||||
|
* When the t byte has a certain pattern, c & s represent altitude.
|
||||||
|
*
|
||||||
|
* Otherwise, c & s can be either course/speed or radio range.
|
||||||
|
*
|
||||||
|
* When c is in range of '!' to 'z',
|
||||||
|
*
|
||||||
|
* ('!' - 33) * 4 = 0 degrees.
|
||||||
|
* ...
|
||||||
|
* ('z' - 33) * 4 = 356 degrees.
|
||||||
|
*
|
||||||
|
* In this case, s represents speed ...
|
||||||
|
*
|
||||||
|
* When c is '{', s is range ...
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (course || speed) {
|
||||||
|
int c;
|
||||||
|
int s;
|
||||||
|
|
||||||
|
c = (course + 1) / 4;
|
||||||
|
if (c < 0) c += 90;
|
||||||
|
if (c >= 90) c -= 90;
|
||||||
|
presult->c = c + '!';
|
||||||
|
|
||||||
|
s = (int)round(log(speed+1.0) / log(1.08));
|
||||||
|
presult->s = s + '!';
|
||||||
|
|
||||||
|
presult->t = 0x26 + '!'; /* current, other tracker. */
|
||||||
|
}
|
||||||
|
else if (power || height || gain) {
|
||||||
|
int s;
|
||||||
|
float range;
|
||||||
|
|
||||||
|
presult->c = '{'; /* radio range. */
|
||||||
|
|
||||||
|
if (power == 0) power = 10;
|
||||||
|
if (height == 0) height = 20;
|
||||||
|
if (gain == 0) gain = 3;
|
||||||
|
|
||||||
|
// from protocol reference page 29.
|
||||||
|
range = sqrt(2.0*height * sqrt((power/10.0) * (gain/2.0)));
|
||||||
|
|
||||||
|
s = (int)round(log(range/2.) / log(1.08));
|
||||||
|
if (s < 0) s = 0;
|
||||||
|
if (s > 93) s = 93;
|
||||||
|
|
||||||
|
presult->s = s + '!';
|
||||||
|
|
||||||
|
presult->t = 0x26 + '!'; /* current, other tracker. */
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
presult->c = ' '; /* cst field not used. */
|
||||||
|
presult->s = ' ';
|
||||||
|
presult->t = '!'; /* avoid space. */
|
||||||
|
}
|
||||||
|
return (sizeof(compressed_position_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: phg_data_extension
|
||||||
|
*
|
||||||
|
* Purpose: Fill in parts of the power/height/gain data extension.
|
||||||
|
*
|
||||||
|
* Inputs: power - Watts.
|
||||||
|
* height - Feet.
|
||||||
|
* gain - dB. Not clear if it is dBi or dBd.
|
||||||
|
* dir - Directivity: N, NE, etc., omni.
|
||||||
|
*
|
||||||
|
* Outputs: presult - Stored here.
|
||||||
|
*
|
||||||
|
* Returns: Number of characters in result.
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct phg_s {
|
||||||
|
char P;
|
||||||
|
char H;
|
||||||
|
char G;
|
||||||
|
char p;
|
||||||
|
char h;
|
||||||
|
char g;
|
||||||
|
char d;
|
||||||
|
} phg_t;
|
||||||
|
|
||||||
|
|
||||||
|
static int phg_data_extension (int power, int height, int gain, char *dir, char *presult)
|
||||||
|
{
|
||||||
|
phg_t *r = (phg_t*)presult;
|
||||||
|
int x;
|
||||||
|
|
||||||
|
r->P = 'P';
|
||||||
|
r->H = 'H';
|
||||||
|
r->G = 'G';
|
||||||
|
|
||||||
|
x = (int)round(sqrt((float)power)) + '0';
|
||||||
|
if (x < '0') x = '0';
|
||||||
|
else if (x > '9') x = '9';
|
||||||
|
r->p = x;
|
||||||
|
|
||||||
|
x = (int)round(log2(height/10.0)) + '0';
|
||||||
|
if (x < '0') x = '0';
|
||||||
|
/* Result can go beyond '9'. */
|
||||||
|
r->h = x;
|
||||||
|
|
||||||
|
x = gain + '0';
|
||||||
|
if (x < '0') x = '0';
|
||||||
|
else if (x > '9') x = '0';
|
||||||
|
r->g = x;
|
||||||
|
|
||||||
|
r->d = '0';
|
||||||
|
if (dir != NULL) {
|
||||||
|
if (strcasecmp(dir,"NE") == 0) r->d = '1';
|
||||||
|
if (strcasecmp(dir,"E") == 0) r->d = '2';
|
||||||
|
if (strcasecmp(dir,"SE") == 0) r->d = '3';
|
||||||
|
if (strcasecmp(dir,"S") == 0) r->d = '4';
|
||||||
|
if (strcasecmp(dir,"SW") == 0) r->d = '5';
|
||||||
|
if (strcasecmp(dir,"W") == 0) r->d = '6';
|
||||||
|
if (strcasecmp(dir,"NW") == 0) r->d = '7';
|
||||||
|
if (strcasecmp(dir,"N") == 0) r->d = '8';
|
||||||
|
}
|
||||||
|
return (sizeof(phg_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: cse_spd_data_extension
|
||||||
|
*
|
||||||
|
* Purpose: Fill in parts of the course & speed data extension.
|
||||||
|
*
|
||||||
|
* Inputs: course - Degress, 1 - 360.
|
||||||
|
* speed - knots.
|
||||||
|
*
|
||||||
|
* Outputs: presult - Stored here.
|
||||||
|
*
|
||||||
|
* Returns: Number of characters in result.
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct cs_s {
|
||||||
|
char cse[3];
|
||||||
|
char slash;
|
||||||
|
char spd[3];
|
||||||
|
} cs_t;
|
||||||
|
|
||||||
|
|
||||||
|
static int cse_spd_data_extension (int course, int speed, char *presult)
|
||||||
|
{
|
||||||
|
cs_t *r = (cs_t*)presult;
|
||||||
|
char stemp[8];
|
||||||
|
int x;
|
||||||
|
|
||||||
|
x = course;
|
||||||
|
if (x < 0) x = 0;
|
||||||
|
if (x > 360) x = 360;
|
||||||
|
sprintf (stemp, "%03d", x);
|
||||||
|
memcpy (r->cse, stemp, 3);
|
||||||
|
|
||||||
|
r->slash = '/';
|
||||||
|
|
||||||
|
x = speed;
|
||||||
|
if (x < 0) x = 0;
|
||||||
|
if (x > 999) x = 999;
|
||||||
|
sprintf (stemp, "%03d", x);
|
||||||
|
memcpy (r->spd, stemp, 3);
|
||||||
|
|
||||||
|
return (sizeof(cs_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: frequency_spec
|
||||||
|
*
|
||||||
|
* Purpose: Put frequency specification in beginning of comment field.
|
||||||
|
*
|
||||||
|
* Inputs: freq - MHz.
|
||||||
|
* tone - Hz.
|
||||||
|
* offset - MHz.
|
||||||
|
*
|
||||||
|
* Outputs: presult - Stored here.
|
||||||
|
*
|
||||||
|
* Returns: Number of characters in result.
|
||||||
|
*
|
||||||
|
* Description: There are several valid variations.
|
||||||
|
*
|
||||||
|
* The frequency could be missing here if it is in the
|
||||||
|
* object name. In this case we could have tone & offset.
|
||||||
|
*
|
||||||
|
* Offset must always be preceded by tone.
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct freq_s {
|
||||||
|
char f[7]; /* format 999.999 */
|
||||||
|
char mhz[3];
|
||||||
|
char space;
|
||||||
|
} freq_t;
|
||||||
|
|
||||||
|
typedef struct to_s {
|
||||||
|
char T;
|
||||||
|
char ttt[3]; /* format 999 (drop fraction) or 'off'. */
|
||||||
|
char space1;
|
||||||
|
char oooo[4]; /* leading sign, 3 digits, tens of KHz. */
|
||||||
|
char space2;
|
||||||
|
} to_t;
|
||||||
|
|
||||||
|
|
||||||
|
static int frequency_spec (float freq, float tone, float offset, char *presult)
|
||||||
|
{
|
||||||
|
int result_len = 0;
|
||||||
|
|
||||||
|
if (freq != 0) {
|
||||||
|
freq_t *f = (freq_t*)presult;
|
||||||
|
char stemp[12];
|
||||||
|
|
||||||
|
/* Should use letters for > 999.999. */
|
||||||
|
sprintf (stemp, "%07.3f", freq);
|
||||||
|
memcpy (f->f, stemp, 7);
|
||||||
|
memcpy (f->mhz, "MHz", 3);
|
||||||
|
f->space = ' ';
|
||||||
|
result_len = sizeof (freq_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tone != 0 || offset != 0) {
|
||||||
|
to_t *to = (to_t*)(presult + result_len);
|
||||||
|
char stemp[12];
|
||||||
|
|
||||||
|
to->T = 'T';
|
||||||
|
if (tone == 0) {
|
||||||
|
memcpy(to->ttt, "off", 3);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sprintf (stemp, "%03d", (int)tone);
|
||||||
|
memcpy (to->ttt, stemp, 3);
|
||||||
|
}
|
||||||
|
to->space1 = ' ';
|
||||||
|
sprintf (stemp, "%+04d", (int)round(offset * 100));
|
||||||
|
memcpy (to->oooo, stemp, 4);
|
||||||
|
to->space2 = ' ';
|
||||||
|
|
||||||
|
result_len += sizeof (to_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (result_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: encode_position
|
||||||
|
*
|
||||||
|
* Purpose: Construct info part for position report format.
|
||||||
|
*
|
||||||
|
* Inputs: compressed - Send in compressed form?
|
||||||
|
* lat - Latitude.
|
||||||
|
* lon - Longitude.
|
||||||
|
* symtab - Symbol table id or overlay.
|
||||||
|
* symbol - Symbol id.
|
||||||
|
*
|
||||||
|
* power - Watts.
|
||||||
|
* height - Feet.
|
||||||
|
* gain - dB. Not clear if it is dBi or dBd.
|
||||||
|
* dir - Directivity: N, NE, etc., omni.
|
||||||
|
*
|
||||||
|
* course - Degress, 1 - 360. 0 means none or unknown.
|
||||||
|
* speed - knots.
|
||||||
|
*
|
||||||
|
* freq - MHz.
|
||||||
|
* tone - Hz.
|
||||||
|
* offset - MHz.
|
||||||
|
*
|
||||||
|
* comment - Additional comment text.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Outputs: presult - Stored here. Should be at least ??? bytes.
|
||||||
|
*
|
||||||
|
* Returns: Number of characters in result.
|
||||||
|
*
|
||||||
|
* Description: There can be a single optional "data extension"
|
||||||
|
* following the position so there is a choice
|
||||||
|
* between:
|
||||||
|
* Power/height/gain/directivity or
|
||||||
|
* Course/speed.
|
||||||
|
*
|
||||||
|
* Afer that,
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
typedef struct aprs_ll_pos_s {
|
||||||
|
char dti; /* ! or = */
|
||||||
|
position_t pos;
|
||||||
|
/* Comment up to 43 characters. */
|
||||||
|
/* Start of comment could be data extension(s). */
|
||||||
|
} aprs_ll_pos_t;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct aprs_compressed_pos_s {
|
||||||
|
char dti; /* ! or = */
|
||||||
|
compressed_position_t cpos;
|
||||||
|
/* Comment up to 40 characters. */
|
||||||
|
/* No data extension allowed for compressed location. */
|
||||||
|
} aprs_compressed_pos_t;
|
||||||
|
|
||||||
|
|
||||||
|
int encode_position (int compressed, double lat, double lon,
|
||||||
|
char symtab, char symbol,
|
||||||
|
int power, int height, int gain, char *dir,
|
||||||
|
int course, int speed,
|
||||||
|
float freq, float tone, float offset,
|
||||||
|
char *comment,
|
||||||
|
char *presult)
|
||||||
|
{
|
||||||
|
int result_len = 0;
|
||||||
|
|
||||||
|
if (compressed) {
|
||||||
|
aprs_compressed_pos_t *p = (aprs_compressed_pos_t *)presult;
|
||||||
|
|
||||||
|
p->dti = '!';
|
||||||
|
set_comp_position (symtab, symbol, lat, lon,
|
||||||
|
power, height, gain,
|
||||||
|
course, speed,
|
||||||
|
&(p->cpos));
|
||||||
|
result_len = 1 + sizeof (p->cpos);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
aprs_ll_pos_t *p = (aprs_ll_pos_t *)presult;
|
||||||
|
|
||||||
|
p->dti = '!';
|
||||||
|
set_norm_position (symtab, symbol, lat, lon, &(p->pos));
|
||||||
|
result_len = 1 + sizeof (p->pos);
|
||||||
|
|
||||||
|
/* Optional data extension. (singular) */
|
||||||
|
/* Can't have both course/speed and PHG. Former gets priority. */
|
||||||
|
|
||||||
|
if (course || speed) {
|
||||||
|
result_len += cse_spd_data_extension (course, speed, presult + result_len);
|
||||||
|
}
|
||||||
|
else if (power || height || gain) {
|
||||||
|
result_len += phg_data_extension (power, height, gain, dir, presult + result_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optional frequency spec. */
|
||||||
|
|
||||||
|
if (freq != 0 || tone != 0 || offset != 0) {
|
||||||
|
result_len += frequency_spec (freq, tone, offset, presult + result_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
presult[result_len] = '\0';
|
||||||
|
|
||||||
|
/* Finally, comment text. */
|
||||||
|
|
||||||
|
if (comment != NULL) {
|
||||||
|
strcat (presult, comment);
|
||||||
|
result_len += strlen(comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (result_len);
|
||||||
|
|
||||||
|
} /* end encode_position */
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: encode_object
|
||||||
|
*
|
||||||
|
* Purpose: Construct info part for object report format.
|
||||||
|
*
|
||||||
|
* Inputs: name - Name, up to 9 characters.
|
||||||
|
* compressed - Send in compressed form?
|
||||||
|
* thyme - Time stamp or 0 for none.
|
||||||
|
* lat - Latitude.
|
||||||
|
* lon - Longitude.
|
||||||
|
* symtab - Symbol table id or overlay.
|
||||||
|
* symbol - Symbol id.
|
||||||
|
*
|
||||||
|
* power - Watts.
|
||||||
|
* height - Feet.
|
||||||
|
* gain - dB. Not clear if it is dBi or dBd.
|
||||||
|
* dir - Direction: N, NE, etc., omni.
|
||||||
|
*
|
||||||
|
* course - Degress, 1 - 360. 0 means none or unknown.
|
||||||
|
* speed - knots.
|
||||||
|
*
|
||||||
|
* freq - MHz.
|
||||||
|
* tone - Hz.
|
||||||
|
* offset - MHz.
|
||||||
|
*
|
||||||
|
* comment - Additional comment text.
|
||||||
|
*
|
||||||
|
* Outputs: presult - Stored here. Should be at least ??? bytes.
|
||||||
|
*
|
||||||
|
* Returns: Number of characters in result.
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
typedef struct aprs_object_s {
|
||||||
|
struct {
|
||||||
|
char dti; /* ; */
|
||||||
|
char name[9];
|
||||||
|
char live_killed; /* * for live or _ for killed */
|
||||||
|
char time_stamp[7];
|
||||||
|
} o;
|
||||||
|
union {
|
||||||
|
position_t pos; /* Up to 43 char comment. First 7 bytes could be data extension. */
|
||||||
|
compressed_position_t cpos; /* Up to 40 char comment. No PHG data extension in this case. */
|
||||||
|
} u;
|
||||||
|
} aprs_object_t;
|
||||||
|
|
||||||
|
int encode_object (char *name, int compressed, time_t thyme, double lat, double lon,
|
||||||
|
char symtab, char symbol,
|
||||||
|
int power, int height, int gain, char *dir,
|
||||||
|
int course, int speed,
|
||||||
|
float freq, float tone, float offset, char *comment,
|
||||||
|
char *presult)
|
||||||
|
{
|
||||||
|
aprs_object_t *p = (aprs_object_t *) presult;
|
||||||
|
int result_len = 0;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
|
||||||
|
p->o.dti = ';';
|
||||||
|
|
||||||
|
memset (p->o.name, ' ', sizeof(p->o.name));
|
||||||
|
n = strlen(name);
|
||||||
|
if (n > sizeof(p->o.name)) n = sizeof(p->o.name);
|
||||||
|
memcpy (p->o.name, name, n);
|
||||||
|
|
||||||
|
p->o.live_killed = '*';
|
||||||
|
|
||||||
|
if (thyme != 0) {
|
||||||
|
struct tm tm;
|
||||||
|
|
||||||
|
#define XMIT_UTC 1
|
||||||
|
#if XMIT_UTC
|
||||||
|
gmtime_r (&thyme, &tm);
|
||||||
|
#else
|
||||||
|
/* Using local time, for this application, would make more sense to me. */
|
||||||
|
/* On Windows, localtime_r produces UTC. */
|
||||||
|
/* How do we set the time zone? Google for mingw time zone. */
|
||||||
|
|
||||||
|
localtime_r (thyme, &tm);
|
||||||
|
#endif
|
||||||
|
sprintf (p->o.time_stamp, "%02d%02d%02d", tm.tm_mday, tm.tm_hour, tm.tm_min);
|
||||||
|
#if XMIT_UTC
|
||||||
|
p->o.time_stamp[6] = 'z';
|
||||||
|
#else
|
||||||
|
p->o.time_stamp[6] = '/';
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
memcpy (p->o.time_stamp, "111111z", sizeof(p->o.time_stamp));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compressed) {
|
||||||
|
set_comp_position (symtab, symbol, lat, lon,
|
||||||
|
power, height, gain,
|
||||||
|
course, speed,
|
||||||
|
&(p->u.cpos));
|
||||||
|
result_len = sizeof(p->o) + sizeof (p->u.cpos);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
set_norm_position (symtab, symbol, lat, lon, &(p->u.pos));
|
||||||
|
result_len = sizeof(p->o) + sizeof (p->u.pos);
|
||||||
|
|
||||||
|
/* Optional data extension. (singular) */
|
||||||
|
/* Can't have both course/speed and PHG. Former gets priority. */
|
||||||
|
|
||||||
|
if (course || speed) {
|
||||||
|
result_len += cse_spd_data_extension (course, speed, presult + result_len);
|
||||||
|
}
|
||||||
|
else if (power || height || gain) {
|
||||||
|
result_len += phg_data_extension (power, height, gain, dir, presult + result_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optional frequency spec. */
|
||||||
|
|
||||||
|
if (freq != 0 || tone != 0 || offset != 0) {
|
||||||
|
result_len += frequency_spec (freq, tone, offset, presult + result_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
presult[result_len] = '\0';
|
||||||
|
|
||||||
|
/* Finally, comment text. */
|
||||||
|
|
||||||
|
if (comment != NULL) {
|
||||||
|
strcat (presult, comment);
|
||||||
|
result_len += strlen(comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (result_len);
|
||||||
|
|
||||||
|
} /* end encode_object */
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: main
|
||||||
|
*
|
||||||
|
* Purpose: Quick test for some functions in this file.
|
||||||
|
*
|
||||||
|
* Description: Just a smattering, not an organized test.
|
||||||
|
*
|
||||||
|
* $ rm a.exe ; gcc -DEN_MAIN encode_aprs.c latlong.c ; ./a.exe
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
#if EN_MAIN
|
||||||
|
|
||||||
|
void text_color_set ( enum dw_color_e c )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main (int argc, char *argv[])
|
||||||
|
{
|
||||||
|
char result[100];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*********** Position ***********/
|
||||||
|
|
||||||
|
encode_position (0, 42+34.61/60, -(71+26.47/60), 'D', '&',
|
||||||
|
0, 0, 0, NULL, 0, 0, 0, 0, 0, NULL, result);
|
||||||
|
dw_printf ("%s\n", result);
|
||||||
|
if (strcmp(result, "!4234.61ND07126.47W&") != 0) dw_printf ("ERROR!\n");
|
||||||
|
|
||||||
|
/* with PHG. */
|
||||||
|
|
||||||
|
encode_position (0, 42+34.61/60, -(71+26.47/60), 'D', '&',
|
||||||
|
50, 100, 6, "N", 0, 0, 0, 0, 0, NULL, result);
|
||||||
|
dw_printf ("%s\n", result);
|
||||||
|
if (strcmp(result, "!4234.61ND07126.47W&PHG7368") != 0) dw_printf ("ERROR!\n");
|
||||||
|
|
||||||
|
/* with freq. */
|
||||||
|
|
||||||
|
encode_position (0, 42+34.61/60, -(71+26.47/60), 'D', '&',
|
||||||
|
0, 0, 0, NULL, 0, 0, 146.955, 74.4, -0.6, NULL, result);
|
||||||
|
dw_printf ("%s\n", result);
|
||||||
|
if (strcmp(result, "!4234.61ND07126.47W&146.955MHz T074 -060 ") != 0) dw_printf ("ERROR!\n");
|
||||||
|
|
||||||
|
/* with course/speed, freq, and comment! */
|
||||||
|
|
||||||
|
encode_position (0, 42+34.61/60, -(71+26.47/60), 'D', '&',
|
||||||
|
0, 0, 0, NULL, 180, 55, 146.955, 74.4, -0.6, "River flooding", result);
|
||||||
|
dw_printf ("%s\n", result);
|
||||||
|
if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz T074 -060 River flooding") != 0) dw_printf ("ERROR!\n");
|
||||||
|
|
||||||
|
/* Course speed, no tone, + offset */
|
||||||
|
|
||||||
|
encode_position (0, 42+34.61/60, -(71+26.47/60), 'D', '&',
|
||||||
|
0, 0, 0, NULL, 180, 55, 146.955, 0, 0.6, "River flooding", result);
|
||||||
|
dw_printf ("%s\n", result);
|
||||||
|
if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz Toff +060 River flooding") != 0) dw_printf ("ERROR!\n");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*********** Compressed position. ***********/
|
||||||
|
|
||||||
|
encode_position (1, 42+34.61/60, -(71+26.47/60), 'D', '&',
|
||||||
|
0, 0, 0, NULL, 0, 0, 0, 0, 0, NULL, result);
|
||||||
|
dw_printf ("%s\n", result);
|
||||||
|
if (strcmp(result, "!D8yKC<Hn[& ") != 0) dw_printf ("ERROR!\n");
|
||||||
|
|
||||||
|
|
||||||
|
/* with PHG. In this case it is converted to precomputed radio range. */
|
||||||
|
|
||||||
|
encode_position (0, 42+34.61/60, -(71+26.47/60), 'D', '&',
|
||||||
|
50, 100, 6, "N", 0, 0, 0, 0, 0, NULL, result);
|
||||||
|
dw_printf ("%s\n", result);
|
||||||
|
if (strcmp(result, "!4234.61ND07126.47W&PHG7368 TBD ???") != 0) dw_printf ("ERROR!\n");
|
||||||
|
|
||||||
|
/* with course/speed, freq, and comment! */
|
||||||
|
|
||||||
|
encode_position (0, 42+34.61/60, -(71+26.47/60), 'D', '&',
|
||||||
|
0, 0, 0, NULL, 180, 55, 146.955, 74.4, -0.6, "River flooding", result);
|
||||||
|
dw_printf ("%s\n", result);
|
||||||
|
if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz T074 -060 River flooding") != 0) dw_printf ("ERROR!\n");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*********** Object. ***********/
|
||||||
|
|
||||||
|
|
||||||
|
encode_object ("WB1GOF-C", 0, 0, 42+34.61/60, -(71+26.47/60), 'D', '&',
|
||||||
|
0, 0, 0, NULL, result);
|
||||||
|
dw_printf ("%s\n", result);
|
||||||
|
if (strcmp(result, ";WB1GOF-C *111111z4234.61ND07126.47W& TBD???") != 0) dw_printf ("ERROR!\n");
|
||||||
|
|
||||||
|
|
||||||
|
return(0);
|
||||||
|
|
||||||
|
} /* end main */
|
||||||
|
|
||||||
|
#endif /* unit test */
|
||||||
|
|
||||||
|
|
||||||
|
/* end encode_aprs.c */
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
int encode_position (int compressed, double lat, double lon,
|
||||||
|
char symtab, char symbol,
|
||||||
|
int power, int height, int gain, char *dir,
|
||||||
|
int course, int speed,
|
||||||
|
float freq, float tone, float offset,
|
||||||
|
char *comment,
|
||||||
|
char *presult);
|
||||||
|
|
||||||
|
int encode_object (char *name, int compressed, time_t thyme, double lat, double lon,
|
||||||
|
char symtab, char symbol,
|
||||||
|
int power, int height, int gain, char *dir,
|
||||||
|
int course, int speed,
|
||||||
|
float freq, float tone, float offset, char *comment,
|
||||||
|
char *presult);
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011 John Langner, WB2OSZ
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate the FCS for an AX.25 frame.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fcs_calc.h"
|
||||||
|
|
||||||
|
|
||||||
|
static const unsigned short ccitt_table[256] = {
|
||||||
|
|
||||||
|
// from http://www.ietf.org/rfc/rfc1549.txt
|
||||||
|
|
||||||
|
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
|
||||||
|
0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
|
||||||
|
0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
|
||||||
|
0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
|
||||||
|
0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
|
||||||
|
0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
|
||||||
|
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
|
||||||
|
0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
|
||||||
|
0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
|
||||||
|
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
|
||||||
|
0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
|
||||||
|
0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
|
||||||
|
0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
|
||||||
|
0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
|
||||||
|
0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
|
||||||
|
0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
|
||||||
|
0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
|
||||||
|
0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
|
||||||
|
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
|
||||||
|
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
|
||||||
|
0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
|
||||||
|
0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
|
||||||
|
0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
|
||||||
|
0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
|
||||||
|
0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
|
||||||
|
0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
|
||||||
|
0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
|
||||||
|
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
|
||||||
|
0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
|
||||||
|
0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
|
||||||
|
0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
|
||||||
|
0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use this for an AX.25 frame.
|
||||||
|
*/
|
||||||
|
|
||||||
|
unsigned short fcs_calc (unsigned char *data, int len)
|
||||||
|
{
|
||||||
|
unsigned short crc = 0xffff;
|
||||||
|
int j;
|
||||||
|
|
||||||
|
for (j=0; j<len; j++) {
|
||||||
|
|
||||||
|
crc = ((crc) >> 8) ^ ccitt_table[((crc) ^ data[j]) & 0xff];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ( crc ^ 0xffff );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This can be used when we want to calculate a single CRC over disjoint data.
|
||||||
|
*
|
||||||
|
* crc = crc16 (region1, sizeof(region1), 0xffff);
|
||||||
|
* crc = crc16 (region2, sizeof(region2), crc);
|
||||||
|
* crc = crc16 (region3, sizeof(region3), crc);
|
||||||
|
*/
|
||||||
|
|
||||||
|
unsigned short crc16 (unsigned char *data, int len, unsigned short seed)
|
||||||
|
{
|
||||||
|
unsigned short crc = seed;
|
||||||
|
int j;
|
||||||
|
|
||||||
|
for (j=0; j<len; j++) {
|
||||||
|
|
||||||
|
crc = ((crc) >> 8) ^ ccitt_table[((crc) ^ data[j]) & 0xff];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ( crc ^ 0xffff );
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
/* fcs_calc.h */
|
||||||
|
|
||||||
|
|
||||||
|
unsigned short fcs_calc (unsigned char *data, int len);
|
||||||
|
|
||||||
|
unsigned short crc16 (unsigned char *data, int len, unsigned short seed);
|
||||||
|
|
||||||
|
/* end fcs_calc.h */
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
#define TUNE_MS_FILTER_SIZE 140
|
||||||
|
#define TUNE_PRE_BAUD 1.080
|
|
@ -0,0 +1,172 @@
|
||||||
|
/* fsk_demod_state.h */
|
||||||
|
|
||||||
|
#ifndef FSK_DEMOD_STATE_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Demodulator state.
|
||||||
|
* Different copy is required for each channel & subchannel being processed concurrently.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum bp_window_e { BP_WINDOW_TRUNCATED,
|
||||||
|
BP_WINDOW_COSINE,
|
||||||
|
BP_WINDOW_HAMMING,
|
||||||
|
BP_WINDOW_BLACKMAN,
|
||||||
|
BP_WINDOW_FLATTOP } bp_window_t;
|
||||||
|
|
||||||
|
struct demodulator_state_s
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* These are set once during initialization.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define TICKS_PER_PLL_CYCLE ( 256.0 * 256.0 * 256.0 * 256.0 )
|
||||||
|
|
||||||
|
int pll_step_per_sample; // PLL is advanced by this much each audio sample.
|
||||||
|
// Data is sampled when it overflows.
|
||||||
|
|
||||||
|
|
||||||
|
int ms_filter_size; /* Size of mark & space filters, in audio samples. */
|
||||||
|
/* Started off as a guess of one bit length */
|
||||||
|
/* but somewhat longer turned out to be better. */
|
||||||
|
/* Currently using same size for any prefilter. */
|
||||||
|
|
||||||
|
#define MAX_FILTER_SIZE 320 /* 304 is needed for profile C, 300 baud & 44100. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FIR filter length relative to one bit time.
|
||||||
|
* Use same for both bandpass and lowpass.
|
||||||
|
*/
|
||||||
|
float filter_len_bits;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Window type for the mark/space filters.
|
||||||
|
*/
|
||||||
|
bp_window_t bp_window;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Alternate Low pass filters.
|
||||||
|
* First is arbitrary number for quick IIR.
|
||||||
|
* Second is frequency as ratio to baud rate for FIR.
|
||||||
|
*/
|
||||||
|
int lpf_use_fir; /* 0 for IIR, 1 for FIR. */
|
||||||
|
float lpf_iir;
|
||||||
|
float lpf_baud;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Automatic gain control. Fast attack and slow decay factors.
|
||||||
|
*/
|
||||||
|
float agc_fast_attack;
|
||||||
|
float agc_slow_decay;
|
||||||
|
/*
|
||||||
|
* Hysteresis before final demodulator 0 / 1 decision.
|
||||||
|
*/
|
||||||
|
float hysteresis;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Phase Locked Loop (PLL) inertia.
|
||||||
|
* Larger number means less influence by signal transitions.
|
||||||
|
*/
|
||||||
|
float pll_locked_inertia;
|
||||||
|
float pll_searching_inertia;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Optional band pass pre-filter before mark/space detector.
|
||||||
|
*/
|
||||||
|
int use_prefilter; /* True to enable it. */
|
||||||
|
|
||||||
|
float prefilter_baud; /* Cutoff frequencies, as fraction of */
|
||||||
|
/* baud rate, beyond tones used. */
|
||||||
|
/* Example, if we used 1600/1800 tones at */
|
||||||
|
/* 300 baud, and this was 0.5, the cutoff */
|
||||||
|
/* frequencies would be: */
|
||||||
|
/* lower = min(1600,1800) - 0.5 * 300 = 1450 */
|
||||||
|
/* upper = max(1600,1800) + 0.5 * 300 = 1950 */
|
||||||
|
|
||||||
|
float pre_filter[MAX_FILTER_SIZE] __attribute__((aligned(16)));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Kernel for the mark and space detection filters.
|
||||||
|
*/
|
||||||
|
|
||||||
|
float m_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
|
||||||
|
float m_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
|
||||||
|
|
||||||
|
float s_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
|
||||||
|
float s_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The rest are continuously updated.
|
||||||
|
*/
|
||||||
|
signed int data_clock_pll; // PLL for data clock recovery.
|
||||||
|
// It is incremented by pll_step_per_sample
|
||||||
|
// for each audio sample.
|
||||||
|
|
||||||
|
signed int prev_d_c_pll; // Previous value of above, before
|
||||||
|
// incrementing, to detect overflows.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Most recent raw audio samples, before/after prefiltering.
|
||||||
|
*/
|
||||||
|
float raw_cb[MAX_FILTER_SIZE] __attribute__((aligned(16)));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Input to the mark/space detector.
|
||||||
|
* Could be prefiltered or raw audio.
|
||||||
|
*/
|
||||||
|
float ms_in_cb[MAX_FILTER_SIZE] __attribute__((aligned(16)));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Outputs from the mark and space amplitude detection,
|
||||||
|
* used as inputs to the FIR lowpass filters.
|
||||||
|
* Kernel for the lowpass filters.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int lp_filter_size;
|
||||||
|
|
||||||
|
float m_amp_cb[MAX_FILTER_SIZE] __attribute__((aligned(16)));
|
||||||
|
float s_amp_cb[MAX_FILTER_SIZE] __attribute__((aligned(16)));
|
||||||
|
|
||||||
|
float lp_filter[MAX_FILTER_SIZE] __attribute__((aligned(16)));
|
||||||
|
|
||||||
|
|
||||||
|
float m_peak, s_peak;
|
||||||
|
float m_valley, s_valley;
|
||||||
|
float m_amp_prev, s_amp_prev;
|
||||||
|
|
||||||
|
int prev_demod_data; // Previous data bit detected.
|
||||||
|
// Used to look for transitions.
|
||||||
|
|
||||||
|
|
||||||
|
/* These are used only for "9600" baud data. */
|
||||||
|
|
||||||
|
int lfsr; // Descrambler shift register.
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Finally, try to come up with some sort of measure of the audio input level.
|
||||||
|
* Let's try gathering both the peak and average of the
|
||||||
|
* absolute value of the input signal over some period such as 100 mS.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
int lev_period; // How many samples go into one measure.
|
||||||
|
|
||||||
|
int lev_count; // Number accumulated so far.
|
||||||
|
|
||||||
|
float lev_peak_acc; // Highest peak so far.
|
||||||
|
|
||||||
|
float lev_sum_acc; // Accumulated sum so far.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These will be updated every 'lev_period' samples:
|
||||||
|
*/
|
||||||
|
float lev_last_peak;
|
||||||
|
float lev_last_ave;
|
||||||
|
float lev_prev_peak;
|
||||||
|
float lev_prev_ave;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#define FSK_DEMOD_STATE_H 1
|
||||||
|
#endif
|
|
@ -0,0 +1,7 @@
|
||||||
|
/* 1200 bits/sec with Audio sample rate = 11025 */
|
||||||
|
/* Mark freq = 1200, Space freq = 2200 */
|
||||||
|
|
||||||
|
static const signed short m_sin_table[9] = { 0 , 7347 , 11257 , 9899 , 3909 , -3909 , -9899 , -11257 , -7347 };
|
||||||
|
static const signed short m_cos_table[9] = { 11431 , 8756 , 1984 , -5715 , -10741 , -10741 , -5715 , 1984 , 8756 };
|
||||||
|
static const signed short s_sin_table[9] = { 0 , 10950 , 6281 , -7347 , -10496 , 1327 , 11257 , 5130 , -8314 };
|
||||||
|
static const signed short s_cos_table[9] = { 11431 , 3278 , -9550 , -8756 , 4527 , 11353 , 1984 , -10215 , -7844 };
|
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef FSK_GEN_FILTER_H
|
||||||
|
#define FSK_GEN_FILTER_H 1
|
||||||
|
|
||||||
|
#include "audio.h"
|
||||||
|
#include "fsk_demod_state.h"
|
||||||
|
|
||||||
|
void fsk_gen_filter (int samples_per_sec,
|
||||||
|
int baud,
|
||||||
|
int mark_freq, int space_freq,
|
||||||
|
char profile,
|
||||||
|
struct demodulator_state_s *D);
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,729 @@
|
||||||
|
//
|
||||||
|
// 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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: gen_packets.c
|
||||||
|
*
|
||||||
|
* Purpose: Test program for generating AFSK AX.25 frames.
|
||||||
|
*
|
||||||
|
* Description: Given messages are converted to audio and written
|
||||||
|
* to a .WAV type audio file.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Bugs: Most options not implemented for second audio channel.
|
||||||
|
*
|
||||||
|
*------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "audio.h"
|
||||||
|
#include "ax25_pad.h"
|
||||||
|
#include "hdlc_send.h"
|
||||||
|
#include "gen_tone.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
|
||||||
|
|
||||||
|
static void usage (char **argv);
|
||||||
|
static int audio_file_open (char *fname, struct audio_s *pa);
|
||||||
|
static int audio_file_close (void);
|
||||||
|
|
||||||
|
static int g_add_noise = 0;
|
||||||
|
static float g_noise_level = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
int c;
|
||||||
|
int digit_optind = 0;
|
||||||
|
int err;
|
||||||
|
unsigned char fbuf[AX25_MAX_PACKET_LEN+2];
|
||||||
|
int flen;
|
||||||
|
int packet_count = 0;
|
||||||
|
int i;
|
||||||
|
int chan;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up default values for the modem.
|
||||||
|
*/
|
||||||
|
struct audio_s modem;
|
||||||
|
|
||||||
|
modem.num_channels = DEFAULT_NUM_CHANNELS; /* -2 stereo */
|
||||||
|
modem.samples_per_sec = DEFAULT_SAMPLES_PER_SEC; /* -r option */
|
||||||
|
modem.bits_per_sample = DEFAULT_BITS_PER_SAMPLE; /* -8 for 8 instead of 16 bits */
|
||||||
|
|
||||||
|
for (chan = 0; chan < MAX_CHANS; chan++) {
|
||||||
|
modem.modem_type[chan] = AFSK; /* change with -g */
|
||||||
|
modem.mark_freq[chan] = DEFAULT_MARK_FREQ; /* -m option */
|
||||||
|
modem.space_freq[chan] = DEFAULT_SPACE_FREQ; /* -s option */
|
||||||
|
modem.baud[chan] = DEFAULT_BAUD; /* -b option */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up other default values.
|
||||||
|
*/
|
||||||
|
int amplitude = 50; /* -a option */
|
||||||
|
int leading_zeros = 12; /* -z option */
|
||||||
|
char output_file[256]; /* -o option */
|
||||||
|
FILE *input_fp = NULL; /* File or NULL for built-in message */
|
||||||
|
|
||||||
|
packet_t pp;
|
||||||
|
|
||||||
|
|
||||||
|
strcpy (output_file, "");
|
||||||
|
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
int this_option_optind = optind ? optind : 1;
|
||||||
|
int option_index = 0;
|
||||||
|
static struct option long_options[] = {
|
||||||
|
{"future1", 1, 0, 0},
|
||||||
|
{"future2", 0, 0, 0},
|
||||||
|
{"future3", 1, 0, 'c'},
|
||||||
|
{0, 0, 0, 0}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ':' following option character means arg is required. */
|
||||||
|
|
||||||
|
c = getopt_long(argc, argv, "gm:s:a:b:B:r:n:o:z:82",
|
||||||
|
long_options, &option_index);
|
||||||
|
if (c == -1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
|
||||||
|
case 0: /* possible future use */
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf("option %s", long_options[option_index].name);
|
||||||
|
if (optarg) {
|
||||||
|
dw_printf(" with arg %s", optarg);
|
||||||
|
}
|
||||||
|
dw_printf("\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'b': /* -b for data Bit rate */
|
||||||
|
|
||||||
|
modem.baud[0] = atoi(optarg);
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("Data rate set to %d bits / second.\n", modem.baud[0]);
|
||||||
|
if (modem.baud[0] < 100 || modem.baud[0] > 10000) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Use a more reasonable bit rate in range of 100 - 10000.\n");
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'B': /* -B for data Bit rate */
|
||||||
|
/* 300 implies 1600/1800 AFSK. */
|
||||||
|
/* 1200 implies 1200/2200 AFSK. */
|
||||||
|
/* 9600 implies scrambled. */
|
||||||
|
|
||||||
|
modem.baud[0] = atoi(optarg);
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("Data rate set to %d bits / second.\n", modem.baud[0]);
|
||||||
|
if (modem.baud[0] < 100 || modem.baud[0] > 10000) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Use a more reasonable bit rate in range of 100 - 10000.\n");
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (modem.baud[0]) {
|
||||||
|
case 300:
|
||||||
|
modem.mark_freq[0] = 1600;
|
||||||
|
modem.space_freq[0] = 1800;
|
||||||
|
break;
|
||||||
|
case 1200:
|
||||||
|
modem.mark_freq[0] = 1200;
|
||||||
|
modem.space_freq[0] = 2200;
|
||||||
|
break;
|
||||||
|
case 9600:
|
||||||
|
modem.modem_type[0] = SCRAMBLE;
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("Using scrambled baseband signal rather than AFSK.\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'g': /* -g for g3ruh scrambling */
|
||||||
|
|
||||||
|
modem.modem_type[0] = SCRAMBLE;
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("Using scrambled baseband signal rather than AFSK.\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'm': /* -m for Mark freq */
|
||||||
|
|
||||||
|
modem.mark_freq[0] = atoi(optarg);
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("Mark frequency set to %d Hz.\n", modem.mark_freq[0]);
|
||||||
|
if (modem.mark_freq[0] < 300 || modem.mark_freq[0] > 3000) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Use a more reasonable value in range of 300 - 3000.\n");
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 's': /* -s for Space freq */
|
||||||
|
|
||||||
|
modem.space_freq[0] = atoi(optarg);
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("Space frequency set to %d Hz.\n", modem.space_freq[0]);
|
||||||
|
if (modem.space_freq[0] < 300 || modem.space_freq[0] > 3000) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Use a more reasonable value in range of 300 - 3000.\n");
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'n': /* -n number of packets with increasing noise. */
|
||||||
|
|
||||||
|
packet_count = atoi(optarg);
|
||||||
|
|
||||||
|
g_add_noise = 1;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'a': /* -a for amplitude */
|
||||||
|
|
||||||
|
amplitude = atoi(optarg);
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("Amplitude set to %d%%.\n", amplitude);
|
||||||
|
if (amplitude < 0 || amplitude > 100) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Amplitude must be in range of 0 to 100.\n");
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'r': /* -r for audio sample Rate */
|
||||||
|
|
||||||
|
modem.samples_per_sec = atoi(optarg);
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("Audio sample rate set to %d samples / second.\n", modem.samples_per_sec);
|
||||||
|
if (modem.samples_per_sec < MIN_SAMPLES_PER_SEC || modem.samples_per_sec > MAX_SAMPLES_PER_SEC) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Use a more reasonable audio sample rate in range of %d - %d.\n",
|
||||||
|
MIN_SAMPLES_PER_SEC, MAX_SAMPLES_PER_SEC);
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'z': /* -z leading zeros before frame flag */
|
||||||
|
|
||||||
|
leading_zeros = atoi(optarg);
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("Send %d zero bits before frame flag.\n", leading_zeros);
|
||||||
|
|
||||||
|
/* The demodulator needs a few for the clock recovery PLL. */
|
||||||
|
/* We don't want to be here all day either. */
|
||||||
|
/* We can't translast to time yet because the data bit rate */
|
||||||
|
/* could be changed later. */
|
||||||
|
|
||||||
|
if (leading_zeros < 8 || leading_zeros > 12000) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Use a more reasonable value.\n");
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '8': /* -8 for 8 bit samples */
|
||||||
|
|
||||||
|
modem.bits_per_sample = 8;
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf("8 bits per audio sample rather than 16.\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '2': /* -2 for 2 channels of sound */
|
||||||
|
|
||||||
|
modem.num_channels = 2;
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf("2 channels of sound rather than 1.\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'o': /* -o for Output file */
|
||||||
|
|
||||||
|
strcpy (output_file, optarg);
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("Output file set to %s\n", output_file);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '?':
|
||||||
|
|
||||||
|
/* Unknown option message was already printed. */
|
||||||
|
usage (argv);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
/* Should not be here. */
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf("?? getopt returned character code 0%o ??\n", c);
|
||||||
|
usage (argv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optind < argc) {
|
||||||
|
|
||||||
|
char str[400];
|
||||||
|
|
||||||
|
// dw_printf("non-option ARGV-elements: ");
|
||||||
|
// while (optind < argc)
|
||||||
|
// dw_printf("%s ", argv[optind++]);
|
||||||
|
//dw_printf("\n");
|
||||||
|
|
||||||
|
if (optind < argc - 1) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Warning: File(s) beyond the first are ignored.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(argv[optind], "-") == 0) {
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("Reading from stdin ...\n");
|
||||||
|
input_fp = stdin;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
input_fp = fopen(argv[optind], "r");
|
||||||
|
if (input_fp == NULL) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Can't open %s for read.\n", argv[optind]);
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("Reading from %s ...\n", argv[optind]);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (fgets (str, sizeof(str), input_fp) != NULL) {
|
||||||
|
text_color_set(DW_COLOR_REC);
|
||||||
|
dw_printf ("%s", str);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_fp != stdin) {
|
||||||
|
fclose (input_fp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("built in message...\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (strlen(output_file) == 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("ERROR: The -o ouput file option must be specified.\n");
|
||||||
|
usage (argv);
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
err = audio_file_open (output_file, &modem);
|
||||||
|
|
||||||
|
|
||||||
|
if (err < 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("ERROR - Can't open output file.\n");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
gen_tone_init (&modem, amplitude);
|
||||||
|
|
||||||
|
assert (modem.bits_per_sample == 8 || modem.bits_per_sample == 16);
|
||||||
|
assert (modem.num_channels == 1 || modem.num_channels == 2);
|
||||||
|
assert (modem.samples_per_sec >= MIN_SAMPLES_PER_SEC && modem.samples_per_sec <= MAX_SAMPLES_PER_SEC);
|
||||||
|
|
||||||
|
|
||||||
|
if (packet_count > 0) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generate packets with increasing noise level.
|
||||||
|
* Would probably be better to record real noise from a radio but
|
||||||
|
* for now just use a random number generator.
|
||||||
|
*/
|
||||||
|
for (i = 1; i <= packet_count; i++) {
|
||||||
|
|
||||||
|
char stemp[80];
|
||||||
|
|
||||||
|
if (modem.modem_type[0] == SCRAMBLE) {
|
||||||
|
g_noise_level = 0.33 * (amplitude / 100.0) * ((float)i / packet_count);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g_noise_level = (amplitude / 100.0) * ((float)i / packet_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf (stemp, "WB2OSZ-1>APRS,W1AB-9,W1ABC-10,WB1ABC-15:,Hello, world! %04d", i);
|
||||||
|
|
||||||
|
pp = ax25_from_text (stemp, 1);
|
||||||
|
flen = ax25_pack (pp, fbuf);
|
||||||
|
for (c=0; c<modem.num_channels; c++)
|
||||||
|
{
|
||||||
|
hdlc_send_flags (c, 8, 0);
|
||||||
|
hdlc_send_frame (c, fbuf, flen);
|
||||||
|
hdlc_send_flags (c, 2, 1);
|
||||||
|
}
|
||||||
|
ax25_delete (pp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Builtin default 4 packets.
|
||||||
|
*/
|
||||||
|
pp = ax25_from_text ("WB2OSZ-1>APRS,W1AB-9,W1ABC-10,WB1ABC-15:,Hello, world!", 1);
|
||||||
|
flen = ax25_pack (pp, fbuf);
|
||||||
|
for (c=0; c<modem.num_channels; c++)
|
||||||
|
{
|
||||||
|
hdlc_send_flags (c, 8, 0);
|
||||||
|
hdlc_send_frame (c, fbuf, flen);
|
||||||
|
hdlc_send_flags (c, 2, 1);
|
||||||
|
}
|
||||||
|
ax25_delete (pp);
|
||||||
|
|
||||||
|
hdlc_send_flags (c, 8, 0);
|
||||||
|
|
||||||
|
pp = ax25_from_text ("WB2OSZ-1>APRS,W1AB-9*,W1ABC-10,WB1ABC-15:,Hello, world!", 1);
|
||||||
|
flen = ax25_pack (pp, fbuf);
|
||||||
|
for (c=0; c<modem.num_channels; c++)
|
||||||
|
{
|
||||||
|
hdlc_send_frame (c, fbuf, flen);
|
||||||
|
}
|
||||||
|
ax25_delete (pp);
|
||||||
|
|
||||||
|
pp = ax25_from_text ("WB2OSZ-1>APRS,W1AB-9,W1ABC-10*,WB1ABC-15:,Hello, world!", 1);
|
||||||
|
flen = ax25_pack (pp, fbuf);
|
||||||
|
for (c=0; c<modem.num_channels; c++)
|
||||||
|
{
|
||||||
|
hdlc_send_frame (c, fbuf, flen);
|
||||||
|
}
|
||||||
|
ax25_delete (pp);
|
||||||
|
|
||||||
|
pp = ax25_from_text ("WB2OSZ-1>APRS,W1AB-9,W1ABC-10,WB1ABC-15*:,Hello, world!", 1);
|
||||||
|
flen = ax25_pack (pp, fbuf);
|
||||||
|
for (c=0; c<modem.num_channels; c++)
|
||||||
|
{
|
||||||
|
hdlc_send_frame (c, fbuf, flen);
|
||||||
|
}
|
||||||
|
ax25_delete (pp);
|
||||||
|
|
||||||
|
hdlc_send_flags (c, 2, 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_file_close();
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void usage (char **argv)
|
||||||
|
{
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("\n");
|
||||||
|
dw_printf ("Usage: xxx [options] [file]\n");
|
||||||
|
dw_printf ("Options:\n");
|
||||||
|
dw_printf (" -a <number> Signal amplitude in range of 0 - 100%%. Default 50.\n");
|
||||||
|
dw_printf (" -b <number> Bits / second for data. Default is %d.\n", DEFAULT_BAUD);
|
||||||
|
dw_printf (" -B <number> Bits / second for data. Proper modem selected for 300, 1200, 9600.\n");
|
||||||
|
dw_printf (" -g Scrambled baseband rather than AFSK.\n");
|
||||||
|
dw_printf (" -m <number> Mark frequency. Default is %d.\n", DEFAULT_MARK_FREQ);
|
||||||
|
dw_printf (" -s <number> Space frequency. Default is %d.\n", DEFAULT_SPACE_FREQ);
|
||||||
|
dw_printf (" -r <number> Audio sample Rate. Default is %d.\n", DEFAULT_SAMPLES_PER_SEC);
|
||||||
|
dw_printf (" -n <number> Generate specified number of frames with increasing noise.\n");
|
||||||
|
dw_printf (" -o <file> Send output to .wav file.\n");
|
||||||
|
dw_printf (" -8 8 bit audio rather than 16.\n");
|
||||||
|
dw_printf (" -2 2 channels of audio rather than 1.\n");
|
||||||
|
dw_printf (" -z <number> Number of leading zero bits before frame.\n");
|
||||||
|
dw_printf (" Default is 12 which is .01 seconds at 1200 bits/sec.\n");
|
||||||
|
|
||||||
|
dw_printf ("\n");
|
||||||
|
dw_printf ("An optional file may be specified to provide messages other than\n");
|
||||||
|
dw_printf ("the default built-n message. The format should correspond to ...\n");
|
||||||
|
dw_printf ("blah blah blah. For example,\n");
|
||||||
|
dw_printf (" WB2OSZ-1>APDW10,WIDE2-2:!4237.14NS07120.83W#\n");
|
||||||
|
dw_printf ("\n");
|
||||||
|
dw_printf ("Example: %s\n", argv[0]);
|
||||||
|
dw_printf ("\n");
|
||||||
|
dw_printf (" With all defaults, a built-in test message is generated\n");
|
||||||
|
dw_printf (" with standard Bell 202 tones used for packet radio on ordinary\n");
|
||||||
|
dw_printf (" VHF FM transceivers.\n");
|
||||||
|
dw_printf ("\n");
|
||||||
|
dw_printf ("Example: %s -g -b 9600\n", argv[0]);
|
||||||
|
dw_printf ("Shortcut: %s -B 9600\n", argv[0]);
|
||||||
|
dw_printf ("\n");
|
||||||
|
dw_printf (" 9600 baud mode.\n");
|
||||||
|
dw_printf ("\n");
|
||||||
|
dw_printf ("Example: %s -m 1600 -s 1800 -b 300\n", argv[0]);
|
||||||
|
dw_printf ("Shortcut: %s -B 300\n", argv[0]);
|
||||||
|
dw_printf ("\n");
|
||||||
|
dw_printf (" 200 Hz shift, 300 baud, suitable for HF SSB transceiver.\n");
|
||||||
|
dw_printf ("\n");
|
||||||
|
dw_printf ("Example: echo -n \"WB2OSZ>WORLD:Hello, world!\" | %s -a 25 -o x.wav -\n", argv[0]);
|
||||||
|
dw_printf ("\n");
|
||||||
|
dw_printf (" Read message from stdin and put quarter volume sound into the file x.wav.\n");
|
||||||
|
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: audio_file_open
|
||||||
|
*
|
||||||
|
* Purpose: Open a .WAV format file for output.
|
||||||
|
*
|
||||||
|
* Inputs: fname - Name of .WAV file to create.
|
||||||
|
*
|
||||||
|
* pa - Address of structure of type audio_s.
|
||||||
|
*
|
||||||
|
* The fields that we care about are:
|
||||||
|
* num_channels
|
||||||
|
* samples_per_sec
|
||||||
|
* bits_per_sample
|
||||||
|
* If zero, reasonable defaults will be provided.
|
||||||
|
*
|
||||||
|
* Returns: 0 for success, -1 for failure.
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
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. */
|
||||||
|
} ;
|
||||||
|
|
||||||
|
/* 8 bit samples are unsigned bytes */
|
||||||
|
/* in range of 0 .. 255. */
|
||||||
|
|
||||||
|
/* 16 bit samples are signed short */
|
||||||
|
/* in range of -32768 .. +32767. */
|
||||||
|
|
||||||
|
static FILE *out_fp = NULL;
|
||||||
|
|
||||||
|
static struct wav_header header;
|
||||||
|
|
||||||
|
static int byte_count; /* Number of data bytes written to file. */
|
||||||
|
/* Will be written to header when file is closed. */
|
||||||
|
|
||||||
|
|
||||||
|
static int audio_file_open (char *fname, struct audio_s *pa)
|
||||||
|
{
|
||||||
|
int n;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fill in defaults for any missing values.
|
||||||
|
*/
|
||||||
|
if (pa -> num_channels == 0)
|
||||||
|
pa -> num_channels = DEFAULT_NUM_CHANNELS;
|
||||||
|
|
||||||
|
if (pa -> samples_per_sec == 0)
|
||||||
|
pa -> samples_per_sec = DEFAULT_SAMPLES_PER_SEC;
|
||||||
|
|
||||||
|
if (pa -> bits_per_sample == 0)
|
||||||
|
pa -> bits_per_sample = DEFAULT_BITS_PER_SAMPLE;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write the file header. Don't know length yet.
|
||||||
|
*/
|
||||||
|
out_fp = fopen (fname, "wb");
|
||||||
|
|
||||||
|
if (out_fp == NULL) {
|
||||||
|
text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't open file for write: %s\n", fname);
|
||||||
|
perror ("");
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
memset (&header, 0, sizeof(header));
|
||||||
|
|
||||||
|
memcpy (header.riff, "RIFF", (size_t)4);
|
||||||
|
header.filesize = 0;
|
||||||
|
memcpy (header.wave, "WAVE", (size_t)4);
|
||||||
|
memcpy (header.fmt, "fmt ", (size_t)4);
|
||||||
|
header.fmtsize = 16; // Always 16.
|
||||||
|
header.wformattag = 1; // 1 for PCM.
|
||||||
|
|
||||||
|
header.nchannels = pa -> num_channels;
|
||||||
|
header.nsamplespersec = pa -> samples_per_sec;
|
||||||
|
header.wbitspersample = pa -> bits_per_sample;
|
||||||
|
|
||||||
|
header.nblockalign = header.wbitspersample / 8 * header.nchannels;
|
||||||
|
header.navgbytespersec = header.nblockalign * header.nsamplespersec;
|
||||||
|
memcpy (header.data, "data", (size_t)4);
|
||||||
|
header.datasize = 0;
|
||||||
|
|
||||||
|
assert (header.nchannels == 1 || header.nchannels == 2);
|
||||||
|
|
||||||
|
n = fwrite (&header, sizeof(header), (size_t)1, out_fp);
|
||||||
|
|
||||||
|
if (n != 1) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Couldn't write header to: %s\n", fname);
|
||||||
|
perror ("");
|
||||||
|
fclose (out_fp);
|
||||||
|
out_fp = NULL;
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Number of bytes written will be filled in later.
|
||||||
|
*/
|
||||||
|
byte_count = 0;
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
|
||||||
|
} /* end audio_open */
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: audio_put
|
||||||
|
*
|
||||||
|
* Purpose: Send one byte to the audio output file.
|
||||||
|
*
|
||||||
|
* Inputs: 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.
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
int audio_put (int c)
|
||||||
|
{
|
||||||
|
static short sample16;
|
||||||
|
int s;
|
||||||
|
|
||||||
|
if (g_add_noise) {
|
||||||
|
|
||||||
|
if ((byte_count & 1) == 0) {
|
||||||
|
sample16 = c & 0xff; /* save lower byte. */
|
||||||
|
byte_count++;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
float r;
|
||||||
|
|
||||||
|
sample16 |= (c << 8) & 0xff00; /* insert upper byte. */
|
||||||
|
byte_count++;
|
||||||
|
s = sample16; // sign extend.
|
||||||
|
|
||||||
|
/* Add random noise to the signal. */
|
||||||
|
/* r should be in range of -1 .. +1. */
|
||||||
|
|
||||||
|
r = (rand() - RAND_MAX/2.0) / (RAND_MAX/2.0);
|
||||||
|
|
||||||
|
s += 5 * r * g_noise_level * 32767;
|
||||||
|
|
||||||
|
if (s > 32767) s = 32767;
|
||||||
|
if (s < -32767) s = -32767;
|
||||||
|
|
||||||
|
putc(s & 0xff, out_fp);
|
||||||
|
return (putc((s >> 8) & 0xff, out_fp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
byte_count++;
|
||||||
|
return (putc(c, out_fp));
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* end audio_put */
|
||||||
|
|
||||||
|
|
||||||
|
int audio_flush ()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: audio_file_close
|
||||||
|
*
|
||||||
|
* Purpose: Close the audio output file.
|
||||||
|
*
|
||||||
|
* Returns: Normally non-negative.
|
||||||
|
* -1 for any type of error.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Description: Must go back to beginning of file and fill in the
|
||||||
|
* size of the data.
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
static int audio_file_close (void)
|
||||||
|
{
|
||||||
|
int n;
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("audio_close()\n");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Go back and fix up lengths in header.
|
||||||
|
*/
|
||||||
|
header.filesize = byte_count + sizeof(header) - 8;
|
||||||
|
header.datasize = byte_count;
|
||||||
|
|
||||||
|
if (out_fp == NULL) {
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fflush (out_fp);
|
||||||
|
|
||||||
|
fseek (out_fp, 0L, SEEK_SET);
|
||||||
|
n = fwrite (&header, sizeof(header), (size_t)1, out_fp);
|
||||||
|
|
||||||
|
if (n != 1) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Couldn't write header to audio file.\n");
|
||||||
|
perror (""); // TODO: remove perror.
|
||||||
|
fclose (out_fp);
|
||||||
|
out_fp = NULL;
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose (out_fp);
|
||||||
|
out_fp = NULL;
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
|
||||||
|
} /* end audio_close */
|
||||||
|
|
|
@ -0,0 +1,334 @@
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011 John Langner, WB2OSZ
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Module: gen_tone.c
|
||||||
|
*
|
||||||
|
* Purpose: Convert bits to AFSK for writing to .WAV sound file
|
||||||
|
* or a sound device.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*---------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "audio.h"
|
||||||
|
#include "gen_tone.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Properties of the digitized sound stream & modem.
|
||||||
|
|
||||||
|
static struct audio_s modem;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 8 bit samples are unsigned bytes in range of 0 .. 255.
|
||||||
|
*
|
||||||
|
* 16 bit samples are signed short in range of -32768 .. +32767.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/* Constants after initialization. */
|
||||||
|
|
||||||
|
#define TICKS_PER_CYCLE ( 256.0 * 256.0 * 256.0 * 256.0 )
|
||||||
|
|
||||||
|
static int ticks_per_sample; /* same for all channels. */
|
||||||
|
|
||||||
|
static int ticks_per_bit[MAX_CHANS];
|
||||||
|
static int f1_change_per_sample[MAX_CHANS];
|
||||||
|
static int f2_change_per_sample[MAX_CHANS];
|
||||||
|
|
||||||
|
static short sine_table[256];
|
||||||
|
|
||||||
|
|
||||||
|
/* Accumulators. */
|
||||||
|
|
||||||
|
static unsigned int tone_phase[MAX_CHANS]; // Phase accumulator for tone generation.
|
||||||
|
// Upper bits are used as index into sine table.
|
||||||
|
|
||||||
|
static int bit_len_acc[MAX_CHANS]; // To accumulate fractional samples per bit.
|
||||||
|
|
||||||
|
static int lfsr[MAX_CHANS]; // Shift register for scrambler.
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: gen_tone_init
|
||||||
|
*
|
||||||
|
* Purpose: Initialize for AFSK tone generation which might
|
||||||
|
* be used for RTTY or amateur packet radio.
|
||||||
|
*
|
||||||
|
* Inputs: pp - Pointer to modem parameter structure, modem_s.
|
||||||
|
*
|
||||||
|
* The fields we care about are:
|
||||||
|
*
|
||||||
|
* samples_per_sec
|
||||||
|
* baud
|
||||||
|
* mark_freq
|
||||||
|
* space_freq
|
||||||
|
* samples_per_sec
|
||||||
|
*
|
||||||
|
* amp - Signal amplitude on scale of 0 .. 100.
|
||||||
|
*
|
||||||
|
* Returns: 0 for success.
|
||||||
|
* -1 for failure.
|
||||||
|
*
|
||||||
|
* Description: Calculate various constants for use by the direct digital synthesis
|
||||||
|
* audio tone generation.
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
static int amp16bit; /* for 9600 baud */
|
||||||
|
|
||||||
|
|
||||||
|
int gen_tone_init (struct audio_s *pp, int amp)
|
||||||
|
{
|
||||||
|
int j;
|
||||||
|
int chan = 0;
|
||||||
|
/*
|
||||||
|
* Save away modem parameters for later use.
|
||||||
|
*/
|
||||||
|
|
||||||
|
memcpy (&modem, pp, sizeof(struct audio_s));
|
||||||
|
|
||||||
|
amp16bit = (32767 * amp) / 100;
|
||||||
|
|
||||||
|
ticks_per_sample = (int) ((TICKS_PER_CYCLE / (double)modem.samples_per_sec ) + 0.5);
|
||||||
|
|
||||||
|
for (chan = 0; chan < modem.num_channels; chan++) {
|
||||||
|
|
||||||
|
ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)modem.baud[chan] ) + 0.5);
|
||||||
|
|
||||||
|
f1_change_per_sample[chan] = (int) (((double)modem.mark_freq[chan] * TICKS_PER_CYCLE / (double)modem.samples_per_sec ) + 0.5);
|
||||||
|
|
||||||
|
f2_change_per_sample[chan] = (int) (((double)modem.space_freq[chan] * TICKS_PER_CYCLE / (double)modem.samples_per_sec ) + 0.5);
|
||||||
|
|
||||||
|
tone_phase[chan] = 0;
|
||||||
|
|
||||||
|
bit_len_acc[chan] = 0;
|
||||||
|
|
||||||
|
lfsr[chan] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (j=0; j<256; j++) {
|
||||||
|
double a;
|
||||||
|
int s;
|
||||||
|
|
||||||
|
a = ((double)(j) / 256.0) * (2 * M_PI);
|
||||||
|
s = (int) (sin(a) * 32767 * amp / 100.0);
|
||||||
|
|
||||||
|
/* 16 bit sound sample is in range of -32768 .. +32767. */
|
||||||
|
|
||||||
|
assert (s >= -32768 && s <= 32767);
|
||||||
|
|
||||||
|
sine_table[j] = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
|
||||||
|
} /* end gen_tone_init */
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: gen_tone_put_bit
|
||||||
|
*
|
||||||
|
* Purpose: Generate tone of proper duration for one data bit.
|
||||||
|
*
|
||||||
|
* Inputs: chan - Audio channel, 0 = first.
|
||||||
|
*
|
||||||
|
* dat - 0 for f1, 1 for f2.
|
||||||
|
*
|
||||||
|
* -1 inserts half bit to test data
|
||||||
|
* recovery PLL.
|
||||||
|
*
|
||||||
|
* Assumption: fp is open to a file for write.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
void tone_gen_put_bit (int chan, int dat)
|
||||||
|
{
|
||||||
|
int cps = dat ? f2_change_per_sample[chan] : f1_change_per_sample[chan];
|
||||||
|
unsigned short sam = 0;
|
||||||
|
int x;
|
||||||
|
|
||||||
|
|
||||||
|
if (dat < 0) {
|
||||||
|
/* Hack to test receive PLL recovery. */
|
||||||
|
bit_len_acc[chan] -= ticks_per_bit[chan];
|
||||||
|
dat = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modem.modem_type[chan] == SCRAMBLE) {
|
||||||
|
x = (dat ^ (lfsr[chan] >> 16) ^ (lfsr[chan] >> 11)) & 1;
|
||||||
|
lfsr[chan] = (lfsr[chan] << 1) | (x & 1);
|
||||||
|
dat = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
|
||||||
|
if (modem.modem_type[chan] == AFSK) {
|
||||||
|
tone_phase[chan] += cps;
|
||||||
|
sam = sine_table[(tone_phase[chan] >> 24) & 0xff];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO: Might want to low pass filter this.
|
||||||
|
sam = dat ? amp16bit : (-amp16bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ship out an audio sample. */
|
||||||
|
|
||||||
|
assert (modem.num_channels == 1 || modem.num_channels == 2);
|
||||||
|
|
||||||
|
/* Generalize to allow 8 bits someday? */
|
||||||
|
|
||||||
|
assert (modem.bits_per_sample == 16);
|
||||||
|
|
||||||
|
|
||||||
|
if (modem.num_channels == 1)
|
||||||
|
{
|
||||||
|
audio_put (sam & 0xff);
|
||||||
|
audio_put ((sam >> 8) & 0xff);
|
||||||
|
}
|
||||||
|
else if (modem.num_channels == 2)
|
||||||
|
{
|
||||||
|
if (chan == 1)
|
||||||
|
{
|
||||||
|
audio_put (0); // silent left
|
||||||
|
audio_put (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_put (sam & 0xff);
|
||||||
|
audio_put ((sam >> 8) & 0xff);
|
||||||
|
//byte_count += 2;
|
||||||
|
|
||||||
|
if (chan == 0)
|
||||||
|
{
|
||||||
|
audio_put (0); // silent right
|
||||||
|
audio_put (0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enough for the bit time? */
|
||||||
|
|
||||||
|
bit_len_acc[chan] += ticks_per_sample;
|
||||||
|
|
||||||
|
} while (bit_len_acc[chan] < ticks_per_bit[chan]);
|
||||||
|
|
||||||
|
bit_len_acc[chan] -= ticks_per_bit[chan];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: main
|
||||||
|
*
|
||||||
|
* Purpose: Quick test program for above.
|
||||||
|
*
|
||||||
|
* Description: Compile like this for unit test:
|
||||||
|
*
|
||||||
|
* gcc -Wall -DMAIN -o gen_tone_test gen_tone.c audio.c textcolor.c
|
||||||
|
*
|
||||||
|
* gcc -Wall -DMAIN -o gen_tone_test.exe gen_tone.c audio_win.c textcolor.c -lwinmm
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
#if MAIN
|
||||||
|
|
||||||
|
|
||||||
|
int main ()
|
||||||
|
{
|
||||||
|
int n;
|
||||||
|
int chan1 = 0;
|
||||||
|
int chan2 = 1;
|
||||||
|
int r;
|
||||||
|
struct audio_s audio_param;
|
||||||
|
|
||||||
|
|
||||||
|
/* to sound card */
|
||||||
|
/* one channel. 2 times: one second of each tone. */
|
||||||
|
|
||||||
|
memset (&audio_param, 0, sizeof(audio_param));
|
||||||
|
strcpy (audio_param.adevice_in, DEFAULT_ADEVICE);
|
||||||
|
strcpy (audio_param.adevice_out, DEFAULT_ADEVICE);
|
||||||
|
|
||||||
|
audio_open (&audio_param);
|
||||||
|
gen_tone_init (&audio_param, 100);
|
||||||
|
|
||||||
|
for (r=0; r<2; r++) {
|
||||||
|
|
||||||
|
for (n=0; n<audio_param.baud[0] * 2 ; n++) {
|
||||||
|
tone_gen_put_bit ( chan1, 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
for (n=0; n<audio_param.baud[0] * 2 ; n++) {
|
||||||
|
tone_gen_put_bit ( chan1, 0 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_close();
|
||||||
|
|
||||||
|
/* Now try stereo. */
|
||||||
|
|
||||||
|
memset (&audio_param, 0, sizeof(audio_param));
|
||||||
|
strcpy (audio_param.adevice_in, DEFAULT_ADEVICE);
|
||||||
|
strcpy (audio_param.adevice_out, DEFAULT_ADEVICE);
|
||||||
|
audio_param.num_channels = 2;
|
||||||
|
|
||||||
|
audio_open (&audio_param);
|
||||||
|
gen_tone_init (&audio_param, 100);
|
||||||
|
|
||||||
|
for (r=0; r<4; r++) {
|
||||||
|
|
||||||
|
for (n=0; n<audio_param.baud[0] * 2 ; n++) {
|
||||||
|
tone_gen_put_bit ( chan1, 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
for (n=0; n<audio_param.baud[0] * 2 ; n++) {
|
||||||
|
tone_gen_put_bit ( chan1, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
for (n=0; n<audio_param.baud[0] * 2 ; n++) {
|
||||||
|
tone_gen_put_bit ( chan2, 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
for (n=0; n<audio_param.baud[0] * 2 ; n++) {
|
||||||
|
tone_gen_put_bit ( chan2, 0 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_close();
|
||||||
|
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* end gen_tone.c */
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* gen_tone.h
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
int gen_tone_init (struct audio_s *pp, int amp);
|
||||||
|
|
||||||
|
|
||||||
|
//int gen_tone_open (int nchan, int sample_rate, int bit_rate, int f1, int f2, int amp, char *fname);
|
||||||
|
|
||||||
|
//int gen_tone_open_fd (int nchan, int sample_rate, int bit_rate, int f1, int f2, int amp, int fd) ;
|
||||||
|
|
||||||
|
//int gen_tone_close (void);
|
||||||
|
|
||||||
|
void tone_gen_put_bit (int chan, int dat);
|
||||||
|
|
|
@ -0,0 +1,513 @@
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011,2012,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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
/********************************************************************************
|
||||||
|
*
|
||||||
|
* File: hdlc_rec.c
|
||||||
|
*
|
||||||
|
* Purpose: Extract HDLC frames from a stream of bits.
|
||||||
|
*
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "demod.h"
|
||||||
|
#include "hdlc_rec.h"
|
||||||
|
#include "hdlc_rec2.h"
|
||||||
|
#include "fcs_calc.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
#include "ax25_pad.h"
|
||||||
|
#include "rrbb.h"
|
||||||
|
#include "multi_modem.h"
|
||||||
|
|
||||||
|
|
||||||
|
//#define TEST 1 /* Define for unit testing. */
|
||||||
|
|
||||||
|
//#define DEBUG3 1 /* monitor the data detect signal. */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Minimum & maximum sizes of an AX.25 frame including the 2 octet FCS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define MIN_FRAME_LEN ((AX25_MIN_PACKET_LEN) + 2)
|
||||||
|
|
||||||
|
#define MAX_FRAME_LEN ((AX25_MAX_PACKET_LEN) + 2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is the current state of the HDLC decoder.
|
||||||
|
*
|
||||||
|
* It is possible to run multiple decoders concurrently by
|
||||||
|
* having a separate set of state variables for each.
|
||||||
|
*
|
||||||
|
* Should have a reset function instead of initializations here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct hdlc_state_s {
|
||||||
|
|
||||||
|
int prev_raw; /* Keep track of previous bit so */
|
||||||
|
/* we can look for transitions. */
|
||||||
|
/* Should be only 0 or 1. */
|
||||||
|
|
||||||
|
unsigned char pat_det; /* 8 bit pattern detector shift register. */
|
||||||
|
/* See below for more details. */
|
||||||
|
|
||||||
|
unsigned int flag4_det; /* Last 32 raw bits to look for 4 */
|
||||||
|
/* flag patterns in a row. */
|
||||||
|
|
||||||
|
unsigned char oacc; /* Accumulator for building up an octet. */
|
||||||
|
|
||||||
|
int olen; /* Number of bits in oacc. */
|
||||||
|
/* When this reaches 8, oacc is copied */
|
||||||
|
/* to the frame buffer and olen is zeroed. */
|
||||||
|
/* The value of -1 is a special case meaning */
|
||||||
|
/* bits should not be accumulated. */
|
||||||
|
|
||||||
|
unsigned char frame_buf[MAX_FRAME_LEN];
|
||||||
|
/* One frame is kept here. */
|
||||||
|
|
||||||
|
int frame_len; /* Number of octets in frame_buf. */
|
||||||
|
/* Should be in range of 0 .. MAX_FRAME_LEN. */
|
||||||
|
|
||||||
|
int data_detect; /* True when HDLC data is detected. */
|
||||||
|
/* This will not be triggered by voice or other */
|
||||||
|
/* noise or even tones. */
|
||||||
|
|
||||||
|
enum retry_e fix_bits; /* Level of effort to recover from */
|
||||||
|
/* a bad FCS on the frame. */
|
||||||
|
|
||||||
|
rrbb_t rrbb; /* Handle for bit array for raw received bits. */
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static struct hdlc_state_s hdlc_state[MAX_CHANS][MAX_SUBCHANS];
|
||||||
|
|
||||||
|
static int num_subchan[MAX_CHANS];
|
||||||
|
|
||||||
|
|
||||||
|
/***********************************************************************************
|
||||||
|
*
|
||||||
|
* Name: hdlc_rec_init
|
||||||
|
*
|
||||||
|
* Purpose: Call once at the beginning to initialize.
|
||||||
|
*
|
||||||
|
* Inputs: None.
|
||||||
|
*
|
||||||
|
***********************************************************************************/
|
||||||
|
|
||||||
|
static int was_init = 0;
|
||||||
|
|
||||||
|
void hdlc_rec_init (struct audio_s *pa)
|
||||||
|
{
|
||||||
|
int j, k;
|
||||||
|
struct hdlc_state_s *H;
|
||||||
|
|
||||||
|
//text_color_set(DW_COLOR_DEBUG);
|
||||||
|
//dw_printf ("hdlc_rec_init (%p) \n", pa);
|
||||||
|
|
||||||
|
assert (pa != NULL);
|
||||||
|
|
||||||
|
for (j=0; j<pa->num_channels; j++)
|
||||||
|
{
|
||||||
|
num_subchan[j] = pa->num_subchan[j];
|
||||||
|
|
||||||
|
assert (num_subchan[j] >= 1 && num_subchan[j] < MAX_SUBCHANS);
|
||||||
|
|
||||||
|
for (k=0; k<MAX_SUBCHANS; k++)
|
||||||
|
{
|
||||||
|
H = &hdlc_state[j][k];
|
||||||
|
|
||||||
|
H->prev_raw = 0;
|
||||||
|
H->pat_det = 0;
|
||||||
|
H->flag4_det = 0;
|
||||||
|
H->olen = -1;
|
||||||
|
H->frame_len = 0;
|
||||||
|
H->data_detect = 0;
|
||||||
|
H->fix_bits = pa->fix_bits;
|
||||||
|
H->rrbb = rrbb_new(j, k, pa->modem_type[j] == SCRAMBLE, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
was_init = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***********************************************************************************
|
||||||
|
*
|
||||||
|
* Name: hdlc_rec_bit
|
||||||
|
*
|
||||||
|
* Purpose: Extract HDLC frames from a stream of bits.
|
||||||
|
*
|
||||||
|
* Inputs: chan - Channel number.
|
||||||
|
*
|
||||||
|
* subchan - This allows multiple decoders per channel.
|
||||||
|
*
|
||||||
|
* raw - One bit from the demodulator.
|
||||||
|
* should be 0 or 1.
|
||||||
|
*
|
||||||
|
* is_scrambled - Is the data scrambled?
|
||||||
|
*
|
||||||
|
* descram_state - Current descrambler state.
|
||||||
|
*
|
||||||
|
* sam - Possible future: Sample from demodulator output.
|
||||||
|
* Should nominally be in roughly -1 to +1 range.
|
||||||
|
*
|
||||||
|
* Description: This is called once for each received bit.
|
||||||
|
* For each valid frame, process_rec_frame()
|
||||||
|
* is called for further processing.
|
||||||
|
*
|
||||||
|
***********************************************************************************/
|
||||||
|
|
||||||
|
#if SLICENDICE
|
||||||
|
void hdlc_rec_bit_sam (int chan, int subchan, int raw, float demod_out)
|
||||||
|
#else
|
||||||
|
void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram_state)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
|
||||||
|
int dbit; /* Data bit after undoing NRZI. */
|
||||||
|
/* Should be only 0 or 1. */
|
||||||
|
struct hdlc_state_s *H;
|
||||||
|
|
||||||
|
assert (was_init == 1);
|
||||||
|
|
||||||
|
assert (chan >= 0 && chan < MAX_CHANS);
|
||||||
|
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Different state information for each channel.
|
||||||
|
*/
|
||||||
|
H = &hdlc_state[chan][subchan];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Using NRZI encoding,
|
||||||
|
* A '0' bit is represented by an inversion since previous bit.
|
||||||
|
* A '1' bit is represented by no change.
|
||||||
|
*/
|
||||||
|
|
||||||
|
dbit = (raw == H->prev_raw);
|
||||||
|
H->prev_raw = raw;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Octets are sent LSB first.
|
||||||
|
* Shift the most recent 8 bits thru the pattern detector.
|
||||||
|
*/
|
||||||
|
H->pat_det >>= 1;
|
||||||
|
if (dbit) {
|
||||||
|
H->pat_det |= 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
H->flag4_det >>= 1;
|
||||||
|
if (dbit) {
|
||||||
|
H->flag4_det |= 0x80000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "Data Carrier detect" function based on data rather than
|
||||||
|
* tones from a modem.
|
||||||
|
*
|
||||||
|
* Idle time, at beginning of transmission should be filled
|
||||||
|
* with the special "flag" characters.
|
||||||
|
*
|
||||||
|
* Idle time of all zero bits (alternating tones at maximum rate)
|
||||||
|
* has also been observed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (H->flag4_det == 0x7e7e7e7e) {
|
||||||
|
|
||||||
|
|
||||||
|
if ( ! H->data_detect) {
|
||||||
|
H->data_detect = 1;
|
||||||
|
#if DEBUG3
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("DCD%d = 1 flags\n", chan);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (H->flag4_det == 0x7e000000) {
|
||||||
|
|
||||||
|
|
||||||
|
if ( ! H->data_detect) {
|
||||||
|
H->data_detect = 1;
|
||||||
|
#if DEBUG3
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("DCD%d = 1 zero fill\n", chan);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Loss of signal should result in lack of transitions.
|
||||||
|
* (all '1' bits) for at least a little while.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
if (H->pat_det == 0xff) {
|
||||||
|
|
||||||
|
if ( H->data_detect ) {
|
||||||
|
H->data_detect = 0;
|
||||||
|
#if DEBUG3
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("DCD%d = 0\n", chan);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* End of data carrier detect.
|
||||||
|
*
|
||||||
|
* The rest is concerned with framing.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if SLICENDICE
|
||||||
|
rrbb2_append_bit (H->rrbb, demod_out);
|
||||||
|
#else
|
||||||
|
rrbb_append_bit (H->rrbb, raw);
|
||||||
|
#endif
|
||||||
|
if (H->pat_det == 0x7e) {
|
||||||
|
|
||||||
|
rrbb_chop8 (H->rrbb);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The special pattern 01111110 indicates beginning and ending of a frame.
|
||||||
|
* If we have an adequate number of whole octets, it is a candidate for
|
||||||
|
* further processing.
|
||||||
|
*
|
||||||
|
* It might look odd that olen is being tested for 7 instead of 0.
|
||||||
|
* This is because oacc would already have 7 bits from the special
|
||||||
|
* "flag" pattern before it is detected here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#if OLD_WAY
|
||||||
|
|
||||||
|
#if TEST
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("\nfound flag, olen = %d, frame_len = %d\n", olen, frame_len);
|
||||||
|
#endif
|
||||||
|
if (H->olen == 7 && H->frame_len >= MIN_FRAME_LEN) {
|
||||||
|
|
||||||
|
unsigned short actual_fcs, expected_fcs;
|
||||||
|
|
||||||
|
#if TEST
|
||||||
|
int j;
|
||||||
|
dw_printf ("TRADITIONAL: frame len = %d\n", H->frame_len);
|
||||||
|
for (j=0; j<H->frame_len; j++) {
|
||||||
|
dw_printf (" %02x", H->frame_buf[j]);
|
||||||
|
}
|
||||||
|
dw_printf ("\n");
|
||||||
|
|
||||||
|
#endif
|
||||||
|
/* Check FCS, low byte first, and process... */
|
||||||
|
|
||||||
|
/* Alternatively, it is possible to include the two FCS bytes */
|
||||||
|
/* in the CRC calculation and look for a magic constant. */
|
||||||
|
/* That would be easier in the case where the CRC is being */
|
||||||
|
/* accumulated along the way as the octets are received. */
|
||||||
|
/* I think making a second pass over it and comparing is */
|
||||||
|
/* easier to understand. */
|
||||||
|
|
||||||
|
actual_fcs = H->frame_buf[H->frame_len-2] | (H->frame_buf[H->frame_len-1] << 8);
|
||||||
|
|
||||||
|
expected_fcs = fcs_calc (H->frame_buf, H->frame_len - 2);
|
||||||
|
|
||||||
|
if (actual_fcs == expected_fcs) {
|
||||||
|
int alevel = demod_get_audio_level (chan, subchan);
|
||||||
|
|
||||||
|
multi_modem_process_rec_frame (chan, subchan, H->frame_buf, H->frame_len - 2, alevel, RETRY_NONE); /* len-2 to remove FCS. */
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
#if TEST
|
||||||
|
dw_printf ("*** actual fcs = %04x, expected fcs = %04x ***\n", actual_fcs, expected_fcs);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
/*
|
||||||
|
* New way - Decode the raw bits in later step.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if TEST
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("\nfound flag, %d bits in frame\n", rrbb_get_len(H->rrbb) - 1);
|
||||||
|
#endif
|
||||||
|
if (rrbb_get_len(H->rrbb) >= MIN_FRAME_LEN * 8) {
|
||||||
|
|
||||||
|
int alevel = demod_get_audio_level (chan, subchan);
|
||||||
|
|
||||||
|
rrbb_set_audio_level (H->rrbb, alevel);
|
||||||
|
hdlc_rec2_block (H->rrbb, H->fix_bits);
|
||||||
|
/* Now owned by someone else who will free it. */
|
||||||
|
H->rrbb = rrbb_new (chan, subchan, is_scrambled, descram_state); /* Allocate a new one. */
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rrbb_clear (H->rrbb, is_scrambled, descram_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
H->olen = 0; /* Allow accumulation of octets. */
|
||||||
|
H->frame_len = 0;
|
||||||
|
|
||||||
|
#if SLICENDICE
|
||||||
|
rrbb2_append_bit (H->rrbb, H->prev_raw ? 1.0 : -1.0); /* Last bit of flag. Needed to get first data bit. */
|
||||||
|
#else
|
||||||
|
rrbb_append_bit (H->rrbb, H->prev_raw); /* Last bit of flag. Needed to get first data bit. */
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (H->pat_det == 0xfe) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Valid data will never have 7 one bits in a row.
|
||||||
|
*
|
||||||
|
* 11111110
|
||||||
|
*
|
||||||
|
* This indicates loss of signal.
|
||||||
|
*/
|
||||||
|
|
||||||
|
H->olen = -1; /* Stop accumulating octets. */
|
||||||
|
H->frame_len = 0; /* Discard anything in progress. */
|
||||||
|
|
||||||
|
rrbb_clear (H->rrbb, is_scrambled, descram_state);
|
||||||
|
#if SLICENDICE
|
||||||
|
rrbb2_append_bit (H->rrbb, H->prev_raw ? 1.0 : -1.0); /* Last bit of flag. Needed to get first data bit. */
|
||||||
|
#else
|
||||||
|
rrbb_append_bit (H->rrbb, H->prev_raw); /* Last bit of flag. Needed to get first data bit. */
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else if ( (H->pat_det & 0xfc) == 0x7c ) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we have five '1' bits in a row, followed by a '0' bit,
|
||||||
|
*
|
||||||
|
* 0111110xx
|
||||||
|
*
|
||||||
|
* the current '0' bit should be discarded because it was added for
|
||||||
|
* "bit stuffing."
|
||||||
|
*/
|
||||||
|
;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In all other cases, accumulate bits into octets, and complete octets
|
||||||
|
* into the frame buffer.
|
||||||
|
*/
|
||||||
|
if (H->olen >= 0) {
|
||||||
|
|
||||||
|
H->oacc >>= 1;
|
||||||
|
if (dbit) {
|
||||||
|
H->oacc |= 0x80;
|
||||||
|
}
|
||||||
|
H->olen++;
|
||||||
|
|
||||||
|
if (H->olen == 8) {
|
||||||
|
H->olen = 0;
|
||||||
|
|
||||||
|
if (H->frame_len < MAX_FRAME_LEN) {
|
||||||
|
H->frame_buf[H->frame_len] = H->oacc;
|
||||||
|
H->frame_len++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: hdlc_rec_data_detect_1
|
||||||
|
* hdlc_rec_data_detect_any
|
||||||
|
*
|
||||||
|
* Purpose: Determine if the radio channel is curently busy
|
||||||
|
* with packet data.
|
||||||
|
* This version doesn't care about voice or other sounds.
|
||||||
|
* This is used by the transmit logic to transmit only
|
||||||
|
* when the channel is clear.
|
||||||
|
*
|
||||||
|
* Inputs: chan - Audio channel. 0 for left, 1 for right.
|
||||||
|
*
|
||||||
|
* Returns: True if channel is busy (data detected) or
|
||||||
|
* false if OK to transmit.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Description: We have two different versions here.
|
||||||
|
*
|
||||||
|
* hdlc_rec_data_detect_1 tests a single decoder (subchan)
|
||||||
|
* and is used by the DPLL to determine how much inertia
|
||||||
|
* to use when trying to follow the incoming signal.
|
||||||
|
*
|
||||||
|
* hdlc_rec_data_detect_any sees if ANY of the decoders
|
||||||
|
* for this channel are receving a signal. This is
|
||||||
|
* used to determine whether the channel is clear and
|
||||||
|
* we can transmit. This would apply to the 300 baud
|
||||||
|
* HF SSB case where we have multiple decoders running
|
||||||
|
* at the same time. The channel is busy if ANY of them
|
||||||
|
* thinks the channel is busy.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
int hdlc_rec_data_detect_1 (int chan, int subchan)
|
||||||
|
{
|
||||||
|
assert (chan >= 0 && chan < MAX_CHANS);
|
||||||
|
|
||||||
|
return ( hdlc_state[chan][subchan].data_detect );
|
||||||
|
|
||||||
|
} /* end hdlc_rec_data_detect_1 */
|
||||||
|
|
||||||
|
|
||||||
|
int hdlc_rec_data_detect_any (int chan)
|
||||||
|
{
|
||||||
|
int subchan;
|
||||||
|
|
||||||
|
assert (chan >= 0 && chan < MAX_CHANS);
|
||||||
|
|
||||||
|
for (subchan = 0; subchan < num_subchan[chan]; subchan++) {
|
||||||
|
|
||||||
|
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
|
||||||
|
|
||||||
|
if (hdlc_state[chan][subchan].data_detect) {
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (0);
|
||||||
|
|
||||||
|
|
||||||
|
} /* end hdlc_rec_data_detect_any */
|
||||||
|
|
||||||
|
|
||||||
|
/* end hdlc_rec.c */
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
|
||||||
|
|
||||||
|
#include "audio.h"
|
||||||
|
|
||||||
|
#include "rrbb.h" /* Possibly defines SLICENDICE. */
|
||||||
|
|
||||||
|
|
||||||
|
void hdlc_rec_init (struct audio_s *pa);
|
||||||
|
|
||||||
|
#if SLICENDICE
|
||||||
|
void hdlc_rec_bit_sam (int chan, int subchan, int raw, float demod_out);
|
||||||
|
#else
|
||||||
|
void hdlc_rec_bit (int chan, int subchan, int raw, int is_scrambled, int descram_state);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* Provided elsewhere to process a complete frame. */
|
||||||
|
|
||||||
|
//void process_rec_frame (int chan, unsigned char *fbuf, int flen, int level);
|
||||||
|
|
||||||
|
/* Transmit needs to know when someone else is transmitting. */
|
||||||
|
|
||||||
|
int hdlc_rec_data_detect_1 (int chan, int subchan);
|
||||||
|
int hdlc_rec_data_detect_any (int chan);
|
|
@ -0,0 +1,661 @@
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011, 2012, 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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
/********************************************************************************
|
||||||
|
*
|
||||||
|
* File: hdlc_rec2.c
|
||||||
|
*
|
||||||
|
* Purpose: Extract HDLC frame from a block of bits after someone
|
||||||
|
* else has done the work of pulling it out from between
|
||||||
|
* the special "flag" sequences.
|
||||||
|
*
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "hdlc_rec2.h"
|
||||||
|
#include "fcs_calc.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
#include "ax25_pad.h"
|
||||||
|
#include "rrbb.h"
|
||||||
|
#include "rdq.h"
|
||||||
|
#include "multi_modem.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Minimum & maximum sizes of an AX.25 frame including the 2 octet FCS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define MIN_FRAME_LEN ((AX25_MIN_PACKET_LEN) + 2)
|
||||||
|
|
||||||
|
#define MAX_FRAME_LEN ((AX25_MAX_PACKET_LEN) + 2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is the current state of the HDLC decoder.
|
||||||
|
*
|
||||||
|
* It is possible to run multiple decoders concurrently by
|
||||||
|
* having a separate set of state variables for each.
|
||||||
|
*
|
||||||
|
* Should have a reset function instead of initializations here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct hdlc_state_s {
|
||||||
|
|
||||||
|
int prev_raw; /* Keep track of previous bit so */
|
||||||
|
/* we can look for transitions. */
|
||||||
|
/* Should be only 0 or 1. */
|
||||||
|
|
||||||
|
unsigned char pat_det; /* 8 bit pattern detector shift register. */
|
||||||
|
/* See below for more details. */
|
||||||
|
|
||||||
|
unsigned char oacc; /* Accumulator for building up an octet. */
|
||||||
|
|
||||||
|
int olen; /* Number of bits in oacc. */
|
||||||
|
/* When this reaches 8, oacc is copied */
|
||||||
|
/* to the frame buffer and olen is zeroed. */
|
||||||
|
|
||||||
|
unsigned char frame_buf[MAX_FRAME_LEN];
|
||||||
|
/* One frame is kept here. */
|
||||||
|
|
||||||
|
int frame_len; /* Number of octets in frame_buf. */
|
||||||
|
/* Should be in range of 0 .. MAX_FRAME_LEN. */
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static int try_decode (rrbb_t block, int chan, int subchan, int alevel, retry_t bits_flipped, int flip_a, int flip_b, int flip_c);
|
||||||
|
static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int alevel, retry_t fix_bits);
|
||||||
|
static int sanity_check (unsigned char *buf, int blen, retry_t bits_flipped);
|
||||||
|
#if DEBUG
|
||||||
|
static double dtime_now (void);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/***********************************************************************************
|
||||||
|
*
|
||||||
|
* Name: hdlc_rec2_block
|
||||||
|
*
|
||||||
|
* Purpose: Extract HDLC frame from a stream of bits.
|
||||||
|
*
|
||||||
|
* Inputs: block - Handle for bit array.
|
||||||
|
* fix_bits - Level of effort to recover frames with bad FCS.
|
||||||
|
*
|
||||||
|
* Description: The other (original) hdlc decoder took one bit at a time
|
||||||
|
* right out of the demodulator.
|
||||||
|
*
|
||||||
|
* This is different in that it processes a block of bits
|
||||||
|
* previously extracted from between two "flag" patterns.
|
||||||
|
*
|
||||||
|
* This allows us to try decoding the same received data more
|
||||||
|
* than once.
|
||||||
|
*
|
||||||
|
* Bugs: This does not work for 9600 baud, more accurately when
|
||||||
|
* the transmitted bits are scrambled.
|
||||||
|
*
|
||||||
|
* Currently we unscramble the bits as they come from the
|
||||||
|
* receiver. Instead we need to save the original received
|
||||||
|
* bits and apply the descrambling after flipping the bits.
|
||||||
|
*
|
||||||
|
***********************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
void hdlc_rec2_block (rrbb_t block, retry_t fix_bits)
|
||||||
|
{
|
||||||
|
int chan = rrbb_get_chan(block);
|
||||||
|
int subchan = rrbb_get_subchan(block);
|
||||||
|
int alevel = rrbb_get_audio_level(block);
|
||||||
|
int ok;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
#if DEBUGx
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("\n--- try to decode ---\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if SLICENDICE
|
||||||
|
/*
|
||||||
|
* Unfinished experiment. Get back to this again someday.
|
||||||
|
* The demodulator output is (should be) roughly in the range of -1 to 1.
|
||||||
|
* Formerly we sliced it at 0 and saved only a single bit for the sample.
|
||||||
|
* Now we save the sample so we can try adjusting the slicing point.
|
||||||
|
*
|
||||||
|
* First time thru, set the slicing point to 0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (n = 0; n < 1 ; n++) {
|
||||||
|
|
||||||
|
rrbb_set_slice_val (block, n);
|
||||||
|
|
||||||
|
ok = try_decode (block, chan, subchan, alevel, RETRY_NONE, -1, -1, -1);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
//#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("Got it with no errors. Slice val = %d \n", n);
|
||||||
|
//#endif
|
||||||
|
rrbb_delete (block);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rrbb_set_slice_val (block, 0);
|
||||||
|
|
||||||
|
#else /* not SLICENDICE */
|
||||||
|
|
||||||
|
ok = try_decode (block, chan, subchan, alevel, RETRY_NONE, -1, -1, -1);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("Got it the first time.\n");
|
||||||
|
#endif
|
||||||
|
rrbb_delete (block);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (try_to_fix_quick_now (block, chan, subchan, alevel, fix_bits)) {
|
||||||
|
rrbb_delete (block);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Put in queue for retrying later at lower priority.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (fix_bits < RETRY_TWO_SEP) {
|
||||||
|
rrbb_delete (block);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rdq_append (block);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int alevel, retry_t fix_bits)
|
||||||
|
{
|
||||||
|
int ok;
|
||||||
|
int len, i;
|
||||||
|
|
||||||
|
|
||||||
|
len = rrbb_get_len(block);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try fixing one bit.
|
||||||
|
*/
|
||||||
|
if (fix_bits < RETRY_SINGLE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i=0; i<len; i++) {
|
||||||
|
ok = try_decode (block, chan, subchan, alevel, RETRY_SINGLE, i, -1, -1);
|
||||||
|
if (ok) {
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("*** Success by flipping SINGLE bit %d of %d ***\n", i, len);
|
||||||
|
#endif
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try fixing two adjacent bits.
|
||||||
|
*/
|
||||||
|
if (fix_bits < RETRY_DOUBLE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i=0; i<len-1; i++) {
|
||||||
|
ok = try_decode (block, chan, subchan, alevel, RETRY_DOUBLE, i, i+1, -1);
|
||||||
|
if (ok) {
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("*** Success by flipping DOUBLE bit %d of %d ***\n", i, len);
|
||||||
|
#endif
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try fixing adjacent three bits.
|
||||||
|
*/
|
||||||
|
if (fix_bits < RETRY_TRIPLE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = rrbb_get_len(block);
|
||||||
|
for (i=0; i<len-2; i++) {
|
||||||
|
ok = try_decode (block, chan, subchan, alevel, RETRY_TRIPLE, i, i+1, i+2);
|
||||||
|
if (ok) {
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("*** Success by flipping TRIPLE bit %d of %d ***\n", i, len);
|
||||||
|
#endif
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hdlc_rec2_try_to_fix_later (rrbb_t block, int chan, int subchan, int alevel)
|
||||||
|
{
|
||||||
|
int ok;
|
||||||
|
int len, i;
|
||||||
|
#if DEBUG
|
||||||
|
double tstart, tend;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
len = rrbb_get_len(block);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Two non-adjacent ("separated") single bits.
|
||||||
|
* It chews up a lot of CPU time. Test takes 4 times longer to run.
|
||||||
|
*
|
||||||
|
* Ran up to 4.82 seconds for 1040 bits before giving up.
|
||||||
|
* Processing time is order N squared so time goes up rapidly with larger frames.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
tstart = dtime_now();
|
||||||
|
#endif
|
||||||
|
len = rrbb_get_len(block);
|
||||||
|
for (i=0; i<len-2; i++) {
|
||||||
|
int j;
|
||||||
|
|
||||||
|
ok = 0;
|
||||||
|
for (j=i+2; j<len; j++) {
|
||||||
|
ok = try_decode (block, chan, subchan, alevel, RETRY_TWO_SEP, i, j, -1);
|
||||||
|
if (ok)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ok) {
|
||||||
|
#if DEBUG
|
||||||
|
tend = dtime_now();
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("*** Success by flipping TWO SEPARATED bits %d and %d of %d *** %.3f sec.\n", i, j, len, tend-tstart);
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if DEBUGx
|
||||||
|
tend = dtime_now();
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("*** No luck flipping TWO SEPARATED bits of %d *** %.3f sec.\n", len, tend-tstart);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static int try_decode (rrbb_t block, int chan, int subchan, int alevel, retry_t bits_flipped, int flip_a, int flip_b, int flip_c)
|
||||||
|
{
|
||||||
|
struct hdlc_state_s H;
|
||||||
|
int blen; /* Block length in bits. */
|
||||||
|
int i;
|
||||||
|
int raw; /* From demodulator. */
|
||||||
|
int dbit; /* Data bit after undoing NRZI. */
|
||||||
|
|
||||||
|
|
||||||
|
H.prev_raw = rrbb_get_bit (block, 0); /* Actually last bit of the */
|
||||||
|
/* opening flag so we can derive the */
|
||||||
|
/* first data bit. */
|
||||||
|
|
||||||
|
/* Does this make sense? */
|
||||||
|
/* This is the last bit of the "flag" pattern. */
|
||||||
|
/* If it was corrupted we wouldn't have detected */
|
||||||
|
/* the start of frame. */
|
||||||
|
|
||||||
|
if (0 == flip_a || 0 == flip_b || 0 == flip_c){
|
||||||
|
H.prev_raw = ! H.prev_raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
H.pat_det = 0;
|
||||||
|
H.oacc = 0;
|
||||||
|
H.olen = 0;
|
||||||
|
H.frame_len = 0;
|
||||||
|
|
||||||
|
blen = rrbb_get_len (block);
|
||||||
|
|
||||||
|
#if DEBUGx
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("try_decode: blen=%d\n", blen);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (i=1; i<blen; i++) {
|
||||||
|
|
||||||
|
raw = rrbb_get_bit (block, i);
|
||||||
|
|
||||||
|
if (i == flip_a || i == flip_b || i == flip_c){
|
||||||
|
raw = ! raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Using NRZI encoding,
|
||||||
|
* A '0' bit is represented by an inversion since previous bit.
|
||||||
|
* A '1' bit is represented by no change.
|
||||||
|
*/
|
||||||
|
|
||||||
|
dbit = (raw == H.prev_raw);
|
||||||
|
H.prev_raw = raw;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Octets are sent LSB first.
|
||||||
|
* Shift the most recent 8 bits thru the pattern detector.
|
||||||
|
*/
|
||||||
|
H.pat_det >>= 1;
|
||||||
|
if (dbit) {
|
||||||
|
H.pat_det |= 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (H.pat_det == 0x7e) {
|
||||||
|
/* The special pattern 01111110 indicates beginning and ending of a frame. */
|
||||||
|
#if DEBUGx
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("try_decode: found flag, i=%d\n", i);
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if (H.pat_det == 0xfe) {
|
||||||
|
/* Valid data will never have 7 one bits in a row. */
|
||||||
|
#if DEBUGx
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("try_decode: found abort, i=%d\n", i);
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if ( (H.pat_det & 0xfc) == 0x7c ) {
|
||||||
|
/*
|
||||||
|
* If we have five '1' bits in a row, followed by a '0' bit,
|
||||||
|
*
|
||||||
|
* 0111110xx
|
||||||
|
*
|
||||||
|
* the current '0' bit should be discarded because it was added for
|
||||||
|
* "bit stuffing."
|
||||||
|
*/
|
||||||
|
;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In all other cases, accumulate bits into octets, and complete octets
|
||||||
|
* into the frame buffer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
H.oacc >>= 1;
|
||||||
|
if (dbit) {
|
||||||
|
H.oacc |= 0x80;
|
||||||
|
}
|
||||||
|
H.olen++;
|
||||||
|
|
||||||
|
if (H.olen == 8) {
|
||||||
|
H.olen = 0;
|
||||||
|
|
||||||
|
if (H.frame_len < MAX_FRAME_LEN) {
|
||||||
|
H.frame_buf[H.frame_len] = H.oacc;
|
||||||
|
H.frame_len++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* end of loop on all bits in block */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Do we have a minimum number of complete bytes?
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if DEBUGx
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("try_decode: olen=%d, frame_len=%d\n", H.olen, H.frame_len);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (H.olen == 0 && H.frame_len >= MIN_FRAME_LEN) {
|
||||||
|
|
||||||
|
unsigned short actual_fcs, expected_fcs;
|
||||||
|
|
||||||
|
#if DEBUGx
|
||||||
|
int j;
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("NEW WAY: frame len = %d\n", H.frame_len);
|
||||||
|
for (j=0; j<H.frame_len; j++) {
|
||||||
|
dw_printf (" %02x", H.frame_buf[j]);
|
||||||
|
}
|
||||||
|
dw_printf ("\n");
|
||||||
|
#endif
|
||||||
|
/* Check FCS, low byte first, and process... */
|
||||||
|
|
||||||
|
/* Alternatively, it is possible to include the two FCS bytes */
|
||||||
|
/* in the CRC calculation and look for a magic constant. */
|
||||||
|
/* That would be easier in the case where the CRC is being */
|
||||||
|
/* accumulated along the way as the octets are received. */
|
||||||
|
/* I think making a second pass over it and comparing is */
|
||||||
|
/* easier to understand. */
|
||||||
|
|
||||||
|
actual_fcs = H.frame_buf[H.frame_len-2] | (H.frame_buf[H.frame_len-1] << 8);
|
||||||
|
|
||||||
|
expected_fcs = fcs_calc (H.frame_buf, H.frame_len - 2);
|
||||||
|
|
||||||
|
if (actual_fcs == expected_fcs && sanity_check (H.frame_buf, H.frame_len - 2, bits_flipped)) {
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Shouldn't be necessary to pass chan, subchan, alevel into
|
||||||
|
// try_decode because we can obtain them from block.
|
||||||
|
// Let's make sure that assumption is good...
|
||||||
|
|
||||||
|
assert (rrbb_get_chan(block) == chan);
|
||||||
|
assert (rrbb_get_subchan(block) == subchan);
|
||||||
|
assert (rrbb_get_audio_level(block) == alevel);
|
||||||
|
|
||||||
|
multi_modem_process_rec_frame (chan, subchan, H.frame_buf, H.frame_len - 2, alevel, bits_flipped); /* len-2 to remove FCS. */
|
||||||
|
return 1; /* success */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0; /* failure. */
|
||||||
|
|
||||||
|
} /* end try_decode */
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try to weed out bogus packets from initially failed FCS matches.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static int sanity_check (unsigned char *buf, int blen, retry_t bits_flipped)
|
||||||
|
{
|
||||||
|
int alen; /* Length of address part. */
|
||||||
|
int j;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* No sanity check if we didn't try fixing the data.
|
||||||
|
* Should we have different levels of checking depending on
|
||||||
|
* how much we try changing the raw data?
|
||||||
|
*/
|
||||||
|
if (bits_flipped == RETRY_NONE) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUGx
|
||||||
|
text_color_set(DW_COLOR_XMIT);
|
||||||
|
dw_printf ("sanity_check: address part length = %d\n", alen);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Address part must be a multiple of 7.
|
||||||
|
*/
|
||||||
|
|
||||||
|
alen = 0;
|
||||||
|
for (j=0; j<blen && alen==0; j++) {
|
||||||
|
if (buf[j] & 0x01) {
|
||||||
|
alen = j + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alen % 7 != 0) {
|
||||||
|
#if DEBUGx
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("sanity_check: FAILED. Address part not multiple of 7.\n");
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Need at least 2 addresses and maximum of 8 digipeaters.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (alen/7 < 2 || alen/7 > 10) {
|
||||||
|
#if DEBUGx
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("sanity_check: FAILED. Too few or many addresses.\n");
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Addresses can contain only upper case letters, digits, and space.
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (j=0; j<alen; j+=7) {
|
||||||
|
|
||||||
|
char addr[7];
|
||||||
|
|
||||||
|
addr[0] = buf[j+0] >> 1;
|
||||||
|
addr[1] = buf[j+1] >> 1;
|
||||||
|
addr[2] = buf[j+2] >> 1;
|
||||||
|
addr[3] = buf[j+3] >> 1;
|
||||||
|
addr[4] = buf[j+4] >> 1;
|
||||||
|
addr[5] = buf[j+5] >> 1;
|
||||||
|
addr[6] = '\0';
|
||||||
|
|
||||||
|
|
||||||
|
if ( (! isupper(addr[0]) && ! isdigit(addr[0])) ||
|
||||||
|
(! isupper(addr[1]) && ! isdigit(addr[1]) && addr[1] != ' ') ||
|
||||||
|
(! isupper(addr[2]) && ! isdigit(addr[2]) && addr[2] != ' ') ||
|
||||||
|
(! isupper(addr[3]) && ! isdigit(addr[3]) && addr[3] != ' ') ||
|
||||||
|
(! isupper(addr[4]) && ! isdigit(addr[4]) && addr[4] != ' ') ||
|
||||||
|
(! isupper(addr[5]) && ! isdigit(addr[5]) && addr[5] != ' ')) {
|
||||||
|
#if DEBUGx
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("sanity_check: FAILED. Invalid characters in addresses \"%s\"\n", addr);
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The next two bytes should be 0x03 and 0xf0 for APRS.
|
||||||
|
* Checking that would mean precluding use for other types of packet operation.
|
||||||
|
*
|
||||||
|
* The next section is also assumes APRS and might discard good data
|
||||||
|
* for other protocols.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Finally, look for bogus characters in the information part.
|
||||||
|
* In theory, the bytes could have any values.
|
||||||
|
* In practice, we find only printable ASCII characters and:
|
||||||
|
*
|
||||||
|
* 0x0a line feed
|
||||||
|
* 0x0d carriage return
|
||||||
|
* 0x1c MIC-E
|
||||||
|
* 0x1d MIC-E
|
||||||
|
* 0x1e MIC-E
|
||||||
|
* 0x1f MIC-E
|
||||||
|
* 0x7f MIC-E
|
||||||
|
* 0x80 "{UIV32N}<0x0d><0x9f><0x80>"
|
||||||
|
* 0x9f "{UIV32N}<0x0d><0x9f><0x80>"
|
||||||
|
* 0xb0 degree symbol, ISO LATIN1
|
||||||
|
* (Note: UTF-8 uses two byte sequence 0xc2 0xb0.)
|
||||||
|
* 0xbe invalid MIC-E encoding.
|
||||||
|
* 0xf8 degree symbol, Microsoft code page 437
|
||||||
|
*
|
||||||
|
* So, if we have something other than these (in English speaking countries!),
|
||||||
|
* chances are that we have bogus data from twiddling the wrong bits.
|
||||||
|
*
|
||||||
|
* Notice that we shouldn't get here for good packets. This extra level
|
||||||
|
* of checking happens only if we twiddled a couple of bits, possibly
|
||||||
|
* creating bad data. We want to be very fussy.
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (j=alen+2; j<blen; j++) {
|
||||||
|
int ch = buf[j];
|
||||||
|
|
||||||
|
if ( ! (( ch >= 0x1c && ch <= 0x7f)
|
||||||
|
|| ch == 0x0a
|
||||||
|
|| ch == 0x0d
|
||||||
|
|| ch == 0x80
|
||||||
|
|| ch == 0x9f
|
||||||
|
|| ch == 0xb0
|
||||||
|
|| ch == 0xf8) ) {
|
||||||
|
#if DEBUGx
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("sanity_check: FAILED. Probably bogus info char 0x%02x\n", ch);
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* end hdlc_rec2.c */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Also in xmit.c. Move to some common location.
|
||||||
|
|
||||||
|
|
||||||
|
/* Current time in seconds but more resolution than time(). */
|
||||||
|
|
||||||
|
/* We don't care what date a 0 value represents because we */
|
||||||
|
/* only use this to calculate elapsed time. */
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
|
||||||
|
static double dtime_now (void)
|
||||||
|
{
|
||||||
|
#if __WIN32__
|
||||||
|
/* 64 bit integer is number of 100 nanosecond intervals from Jan 1, 1601. */
|
||||||
|
|
||||||
|
FILETIME ft;
|
||||||
|
|
||||||
|
GetSystemTimeAsFileTime (&ft);
|
||||||
|
|
||||||
|
return ((( (double)ft.dwHighDateTime * (256. * 256. * 256. * 256.) +
|
||||||
|
(double)ft.dwLowDateTime ) / 10000000.) - 11644473600.);
|
||||||
|
#else
|
||||||
|
/* tv_sec is seconds from Jan 1, 1970. */
|
||||||
|
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
clock_gettime (CLOCK_REALTIME, &ts);
|
||||||
|
|
||||||
|
return (ts.tv_sec + ts.tv_nsec / 1000000000.);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,35 @@
|
||||||
|
|
||||||
|
#ifndef HDLC_REC2_H
|
||||||
|
#define HDLC_REC2_H 1
|
||||||
|
|
||||||
|
|
||||||
|
#include "rrbb.h"
|
||||||
|
#include "ax25_pad.h" /* for packet_t */
|
||||||
|
|
||||||
|
typedef enum retry_e {
|
||||||
|
RETRY_NONE=0,
|
||||||
|
RETRY_SINGLE=1,
|
||||||
|
RETRY_DOUBLE=2,
|
||||||
|
RETRY_TRIPLE=3,
|
||||||
|
RETRY_TWO_SEP=4 } retry_t;
|
||||||
|
|
||||||
|
#if defined(DIREWOLF_C) || defined(ATEST_C) || defined(UDPTEST_C)
|
||||||
|
|
||||||
|
static const char * retry_text[] = {
|
||||||
|
"NONE",
|
||||||
|
"SINGLE",
|
||||||
|
"DOUBLE",
|
||||||
|
"TRIPLE",
|
||||||
|
"TWO_SEP" };
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void hdlc_rec2_block (rrbb_t block, retry_t fix_bits);
|
||||||
|
|
||||||
|
void hdlc_rec2_try_to_fix_later (rrbb_t block, int chan, int subchan, int alevel);
|
||||||
|
|
||||||
|
/* Provided by the top level application to process a complete frame. */
|
||||||
|
|
||||||
|
void app_process_rec_packet (int chan, int subchan, packet_t pp, int level, retry_t retries, char *spectrum);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,215 @@
|
||||||
|
//
|
||||||
|
// 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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "hdlc_send.h"
|
||||||
|
#include "audio.h"
|
||||||
|
#include "gen_tone.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
#include "fcs_calc.h"
|
||||||
|
|
||||||
|
static void send_control (int, int);
|
||||||
|
static void send_data (int, int);
|
||||||
|
static void send_bit (int, int);
|
||||||
|
|
||||||
|
static int number_of_bits_sent;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: hdlc_send
|
||||||
|
*
|
||||||
|
* Purpose: Convert HDLC frames to a stream of bits.
|
||||||
|
*
|
||||||
|
* Inputs: chan - Audio channel number, 0 = first.
|
||||||
|
*
|
||||||
|
* fbuf - Frame buffer address.
|
||||||
|
*
|
||||||
|
* flen - Frame length, not including the FCS.
|
||||||
|
*
|
||||||
|
* Outputs: Bits are shipped out by calling tone_gen_put_bit().
|
||||||
|
*
|
||||||
|
* Returns: Number of bits sent including "flags" and the
|
||||||
|
* stuffing bits.
|
||||||
|
* The required time can be calculated by dividing this
|
||||||
|
* number by the transmit rate of bits/sec.
|
||||||
|
*
|
||||||
|
* Description: Convert to stream of bits including:
|
||||||
|
* start flag
|
||||||
|
* bit stuffed data
|
||||||
|
* calculated FCS
|
||||||
|
* end flag
|
||||||
|
* NRZI encoding
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Assumptions: It is assumed that the tone_gen module has been
|
||||||
|
* properly initialized so that bits sent with
|
||||||
|
* tone_gen_put_bit() are processed correctly.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
int hdlc_send_frame (int chan, unsigned char *fbuf, int flen)
|
||||||
|
{
|
||||||
|
int j, fcs;
|
||||||
|
|
||||||
|
|
||||||
|
number_of_bits_sent = 0;
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("hdlc_send_frame ( chan = %d, fbuf = %p, flen = %d )\n", chan, fbuf, flen);
|
||||||
|
fflush (stdout);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
send_control (chan, 0x7e); /* Start frame */
|
||||||
|
|
||||||
|
for (j=0; j<flen; j++) {
|
||||||
|
send_data (chan, fbuf[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fcs = fcs_calc (fbuf, flen);
|
||||||
|
|
||||||
|
send_data (chan, fcs & 0xff);
|
||||||
|
send_data (chan, (fcs >> 8) & 0xff);
|
||||||
|
|
||||||
|
send_control (chan, 0x7e); /* End frame */
|
||||||
|
|
||||||
|
return (number_of_bits_sent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: hdlc_send_flags
|
||||||
|
*
|
||||||
|
* Purpose: Send HDLC flags before and after the frame.
|
||||||
|
*
|
||||||
|
* Inputs: chan - Audio channel number, 0 = first.
|
||||||
|
*
|
||||||
|
* nflags - Number of flag patterns to send.
|
||||||
|
*
|
||||||
|
* finish - True for end of transmission.
|
||||||
|
* This causes the last audio buffer to be flushed.
|
||||||
|
*
|
||||||
|
* Outputs: Bits are shipped out by calling tone_gen_put_bit().
|
||||||
|
*
|
||||||
|
* Returns: Number of bits sent.
|
||||||
|
* There is no bit-stuffing so we would expect this to
|
||||||
|
* be 8 * nflags.
|
||||||
|
* The required time can be calculated by dividing this
|
||||||
|
* number by the transmit rate of bits/sec.
|
||||||
|
*
|
||||||
|
* Assumptions: It is assumed that the tone_gen module has been
|
||||||
|
* properly initialized so that bits sent with
|
||||||
|
* tone_gen_put_bit() are processed correctly.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------*/
|
||||||
|
|
||||||
|
int hdlc_send_flags (int chan, int nflags, int finish)
|
||||||
|
{
|
||||||
|
int j;
|
||||||
|
|
||||||
|
|
||||||
|
number_of_bits_sent = 0;
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("hdlc_send_flags ( chan = %d, nflags = %d, finish = %d )\n", chan, nflags, finish);
|
||||||
|
fflush (stdout);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* The AX.25 spec states that when the transmitter is on but not sending data */
|
||||||
|
/* it should send a continuous stream of "flags." */
|
||||||
|
|
||||||
|
for (j=0; j<nflags; j++) {
|
||||||
|
send_control (chan, 0x7e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Push out the final partial buffer! */
|
||||||
|
|
||||||
|
if (finish) {
|
||||||
|
audio_flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (number_of_bits_sent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static int stuff = 0;
|
||||||
|
|
||||||
|
static void send_control (int chan, int x)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i=0; i<8; i++) {
|
||||||
|
send_bit (chan, x & 1);
|
||||||
|
x >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
stuff = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void send_data (int chan, int x)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i=0; i<8; i++) {
|
||||||
|
send_bit (chan, x & 1);
|
||||||
|
if (x & 1) {
|
||||||
|
stuff++;
|
||||||
|
if (stuff == 5) {
|
||||||
|
send_bit (chan, 0);
|
||||||
|
stuff = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stuff = 0;
|
||||||
|
}
|
||||||
|
x >>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NRZI encoding.
|
||||||
|
* data 1 bit -> no change.
|
||||||
|
* data 0 bit -> invert signal.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void send_bit (int chan, int b)
|
||||||
|
{
|
||||||
|
static int output;
|
||||||
|
|
||||||
|
if (b == 0) {
|
||||||
|
output = ! output;
|
||||||
|
}
|
||||||
|
|
||||||
|
tone_gen_put_bit (chan, output);
|
||||||
|
|
||||||
|
number_of_bits_sent++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* end hdlc_send.c */
|
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
/* hdlc_send.h */
|
||||||
|
|
||||||
|
int hdlc_send_frame (int chan, unsigned char *fbuf, int flen);
|
||||||
|
|
||||||
|
int hdlc_send_flags (int chan, int flags, int finish);
|
||||||
|
|
||||||
|
/* end hdlc_send.h */
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
|
||||||
|
/*----------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: igate.h
|
||||||
|
*
|
||||||
|
* Purpose: Interface to the Internet Gateway functions.
|
||||||
|
*
|
||||||
|
*-----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef IGATE_H
|
||||||
|
#define IGATE_H 1
|
||||||
|
|
||||||
|
|
||||||
|
#include "ax25_pad.h"
|
||||||
|
#include "digipeater.h"
|
||||||
|
|
||||||
|
#define DEFAULT_IGATE_PORT 14580
|
||||||
|
|
||||||
|
|
||||||
|
struct igate_config_s {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For logging into the IGate server.
|
||||||
|
*/
|
||||||
|
char t2_server_name[40]; /* Tier 2 IGate server name. */
|
||||||
|
|
||||||
|
int t2_server_port; /* Typically 14580. */
|
||||||
|
|
||||||
|
char t2_login[AX25_MAX_ADDR_LEN];/* e.g. WA9XYZ-15 */
|
||||||
|
/* Note that the ssid could be any two alphanumeric */
|
||||||
|
/* characters not just 1 thru 15. */
|
||||||
|
/* Could be same or different than the radio call(s). */
|
||||||
|
/* Not sure what the consequences would be. */
|
||||||
|
|
||||||
|
char t2_passcode[8]; /* Max. 5 digits. Could be "-1". */
|
||||||
|
|
||||||
|
char *t2_filter; /* Optional filter for IS -> RF direction. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For transmitting.
|
||||||
|
*/
|
||||||
|
int tx_chan; /* Radio channel for transmitting. */
|
||||||
|
/* 0=first, etc. -1 for none. */
|
||||||
|
|
||||||
|
char tx_via[80]; /* VIA path for transmitting third party packets. */
|
||||||
|
/* Usual text representation. */
|
||||||
|
/* Must start with "," if not empty so it can */
|
||||||
|
/* simply be inserted after the destination address. */
|
||||||
|
|
||||||
|
int tx_limit_1; /* Max. packets to transmit in 1 minute. */
|
||||||
|
|
||||||
|
int tx_limit_5; /* Max. packets to transmit in 5 minutes. */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Call this once at startup */
|
||||||
|
|
||||||
|
void igate_init (struct igate_config_s *p_igate_config, struct digi_config_s *p_digi_config);
|
||||||
|
|
||||||
|
/* Call this with each packet received from the radio. */
|
||||||
|
|
||||||
|
void igate_send_rec_packet (int chan, packet_t recv_pp);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,923 @@
|
||||||
|
//
|
||||||
|
// 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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Module: kiss.c
|
||||||
|
*
|
||||||
|
* Purpose: Act as a virtual KISS TNC for use by other packet radio applications.
|
||||||
|
*
|
||||||
|
* Input:
|
||||||
|
*
|
||||||
|
* Outputs:
|
||||||
|
*
|
||||||
|
* Description: This provides a pseudo terminal for communication with a client application.
|
||||||
|
*
|
||||||
|
* It implements the KISS TNC protocol as described in:
|
||||||
|
* http://www.ka9q.net/papers/kiss.html
|
||||||
|
*
|
||||||
|
* Briefly, a frame is composed of
|
||||||
|
*
|
||||||
|
* * FEND (0xC0)
|
||||||
|
* * Contents - with special escape sequences so a 0xc0
|
||||||
|
* byte in the data is not taken as end of frame.
|
||||||
|
* as part of the data.
|
||||||
|
* * FEND
|
||||||
|
*
|
||||||
|
* The first byte of the frame contains:
|
||||||
|
*
|
||||||
|
* * port number in upper nybble.
|
||||||
|
* * command in lower nybble.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Commands from application recognized:
|
||||||
|
*
|
||||||
|
* 0 Data Frame AX.25 frame in raw format.
|
||||||
|
*
|
||||||
|
* 1 TXDELAY See explanation in xmit.c.
|
||||||
|
*
|
||||||
|
* 2 Persistence " "
|
||||||
|
*
|
||||||
|
* 3 SlotTime " "
|
||||||
|
*
|
||||||
|
* 4 TXtail " "
|
||||||
|
* Spec says it is obsolete but Xastir
|
||||||
|
* sends it and we respect it.
|
||||||
|
*
|
||||||
|
* 5 FullDuplex Ignored. Always full duplex.
|
||||||
|
*
|
||||||
|
* 6 SetHardware TNC specific. Ignored.
|
||||||
|
*
|
||||||
|
* FF Return Exit KISS mode. Ignored.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Messages sent to client application:
|
||||||
|
*
|
||||||
|
* 0 Data Frame Received AX.25 frame in raw format.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Platform differences:
|
||||||
|
*
|
||||||
|
* We can use a pseudo terminal for Linux or Cygwin applications.
|
||||||
|
* However, Microsoft Windows doesn't seem to have similar functionality.
|
||||||
|
* Native Windows applications expect to see a device named COM1,
|
||||||
|
* COM2, COM3, or COM4. Some might offer more flexibility but others
|
||||||
|
* might be limited to these four choices.
|
||||||
|
*
|
||||||
|
* The documentation instucts the user to install the com0com
|
||||||
|
* “Null-modem emulator” from http://sourceforge.net/projects/com0com/
|
||||||
|
* and configure it for COM3 & COM4.
|
||||||
|
*
|
||||||
|
* By default Dire Wolf will use COM3 (/dev/ttyS2 or /dev/com3 - lower case!)
|
||||||
|
* and the client application will use COM4 (available as /dev/ttyS or
|
||||||
|
* /dev/com4 for Cygwin applications).
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This can get confusing.
|
||||||
|
*
|
||||||
|
* If __WIN32__ is defined,
|
||||||
|
* We use the Windows interface to the specfied serial port.
|
||||||
|
* This could be a real serial port or the nullmodem driver
|
||||||
|
* connected to another application.
|
||||||
|
*
|
||||||
|
* If __CYGWIN__ is defined,
|
||||||
|
* We connect to a serial port as in the previous case but
|
||||||
|
* use the Linux I/O interface.
|
||||||
|
* We also supply a pseudo terminal for any Cygwin applications
|
||||||
|
* such as Xastir so the null modem is not needed.
|
||||||
|
*
|
||||||
|
* For the Linux case,
|
||||||
|
* We supply a pseudo terminal for use by other applications.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Reference: http://www.robbayer.com/files/serial-win.pdf
|
||||||
|
*
|
||||||
|
*---------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#define __USE_XOPEN2KXSI 1
|
||||||
|
#define __USE_XOPEN 1
|
||||||
|
//#define __USE_POSIX 1
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/errno.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "tq.h"
|
||||||
|
#include "ax25_pad.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
#include "kiss.h"
|
||||||
|
#include "kiss_frame.h"
|
||||||
|
#include "xmit.h"
|
||||||
|
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
typedef HANDLE MYFDTYPE;
|
||||||
|
#define MYFDERROR INVALID_HANDLE_VALUE
|
||||||
|
#else
|
||||||
|
typedef int MYFDTYPE;
|
||||||
|
#define MYFDERROR (-1)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
static kiss_frame_t kf; /* Accumulated KISS frame and state of decoder. */
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These are for a Linux/Cygwin pseudo terminal.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if ! __WIN32__
|
||||||
|
|
||||||
|
static MYFDTYPE pt_master_fd = MYFDERROR; /* File descriptor for my end. */
|
||||||
|
|
||||||
|
static MYFDTYPE pt_slave_fd = MYFDERROR; /* File descriptor for pseudo terminal */
|
||||||
|
/* for use by application. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Symlink to pseudo terminal name which changes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DEV_KISS_TNC "/tmp/kisstnc"
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is for native Windows applications and a virtual null modem.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if __CYGWIN__ || __WIN32__
|
||||||
|
|
||||||
|
static MYFDTYPE nullmodem_fd = MYFDERROR;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static void * kiss_listen_thread (void *arg);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG9
|
||||||
|
static FILE *log_fp;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
static int kiss_debug = 0; /* Print information flowing from and to client. */
|
||||||
|
|
||||||
|
void kiss_serial_set_debug (int n)
|
||||||
|
{
|
||||||
|
kiss_debug = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* In server.c. Should probably move to some misc. function file. */
|
||||||
|
|
||||||
|
void hex_dump (unsigned char *p, int len);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: kiss_init
|
||||||
|
*
|
||||||
|
* Purpose: Set up a pseudo terminal acting as a virtual KISS TNC.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Inputs: mc->nullmodem - name of device for our end of nullmodem.
|
||||||
|
*
|
||||||
|
* Outputs:
|
||||||
|
*
|
||||||
|
* Description: (1) Create a pseudo terminal for the client to use.
|
||||||
|
* (2) Start a new thread to listen for commands from client app
|
||||||
|
* so the main application doesn't block while we wait.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
static MYFDTYPE kiss_open_pt (void);
|
||||||
|
static MYFDTYPE kiss_open_nullmodem (char *device);
|
||||||
|
|
||||||
|
void kiss_init (struct misc_config_s *mc)
|
||||||
|
{
|
||||||
|
int e;
|
||||||
|
#if __WIN32__
|
||||||
|
HANDLE kiss_nullmodem_listen_th;
|
||||||
|
#else
|
||||||
|
pthread_t kiss_pterm_listen_tid;
|
||||||
|
pthread_t kiss_nullmodem_listen_tid;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
memset (&kf, 0, sizeof(kf));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This reads messages from client.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if ! __WIN32__
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pseudo terminal for Cygwin and Linux versions.
|
||||||
|
*/
|
||||||
|
pt_master_fd = MYFDERROR;
|
||||||
|
|
||||||
|
if (mc->enable_kiss_pt) {
|
||||||
|
|
||||||
|
pt_master_fd = kiss_open_pt ();
|
||||||
|
|
||||||
|
if (pt_master_fd != MYFDERROR) {
|
||||||
|
e = pthread_create (&kiss_pterm_listen_tid, (pthread_attr_t*)NULL, kiss_listen_thread, (void*)(long)pt_master_fd);
|
||||||
|
if (e != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
perror("Could not create kiss listening thread for Linux pseudo terminal");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("Use -p command line option to enable KISS pseudo terminal.\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __CYGWIN__ || __WIN32
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cygwin and native Windows versions have serial port connection.
|
||||||
|
*/
|
||||||
|
if (strlen(mc->nullmodem) > 0) {
|
||||||
|
|
||||||
|
#if ! __WIN32__
|
||||||
|
|
||||||
|
/* Translate Windows device name into Linux name. */
|
||||||
|
/* COM1 -> /dev/ttyS0, etc. */
|
||||||
|
|
||||||
|
if (strncasecmp(mc->nullmodem, "COM", 3) == 0) {
|
||||||
|
int n = atoi (mc->nullmodem + 3);
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("Converted nullmodem device '%s'", mc->nullmodem);
|
||||||
|
if (n < 1) n = 1;
|
||||||
|
sprintf (mc->nullmodem, "/dev/ttyS%d", n-1);
|
||||||
|
dw_printf (" to Linux equivalent '%s'\n", mc->nullmodem);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
nullmodem_fd = kiss_open_nullmodem (mc->nullmodem);
|
||||||
|
|
||||||
|
if (nullmodem_fd != MYFDERROR) {
|
||||||
|
#if __WIN32__
|
||||||
|
kiss_nullmodem_listen_th = _beginthreadex (NULL, 0, kiss_listen_thread, (void*)(long)nullmodem_fd, 0, NULL);
|
||||||
|
if (kiss_nullmodem_listen_th == NULL) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Could not create kiss nullmodem thread\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
e = pthread_create (&kiss_nullmodem_listen_tid, NULL, kiss_listen_thread, (void*)(long)nullmodem_fd);
|
||||||
|
if (e != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
perror("Could not create kiss listening thread for Windows virtual COM port.");
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set (DW_COLOR_DEBUG);
|
||||||
|
#if ! __WIN32__
|
||||||
|
dw_printf ("end of kiss_init: pt_master_fd = %d\n", pt_master_fd);
|
||||||
|
#endif
|
||||||
|
#if __CYGWIN__ || __WIN32__
|
||||||
|
dw_printf ("end of kiss_init: nullmodem_fd = %d\n", nullmodem_fd);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns fd for master side of pseudo terminal or MYFDERROR for error.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if ! __WIN32__
|
||||||
|
|
||||||
|
static MYFDTYPE kiss_open_pt (void)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
char *slave_device;
|
||||||
|
struct termios ts;
|
||||||
|
int e;
|
||||||
|
//int flags;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("kiss_open_pt ( )\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
fd = posix_openpt(O_RDWR|O_NOCTTY);
|
||||||
|
|
||||||
|
if (fd == MYFDERROR
|
||||||
|
|| grantpt (fd) == MYFDERROR
|
||||||
|
|| unlockpt (fd) == MYFDERROR
|
||||||
|
|| (slave_device = ptsname (fd)) == NULL) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("ERROR - Could not create pseudo terminal for KISS TNC.\n");
|
||||||
|
return (MYFDERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
e = tcgetattr (fd, &ts);
|
||||||
|
if (e != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Can't get pseudo terminal attributes, err=%d\n", e);
|
||||||
|
perror ("pt tcgetattr");
|
||||||
|
}
|
||||||
|
|
||||||
|
cfmakeraw (&ts);
|
||||||
|
|
||||||
|
ts.c_cc[VMIN] = 1; /* wait for at least one character */
|
||||||
|
ts.c_cc[VTIME] = 0; /* no fancy timing. */
|
||||||
|
|
||||||
|
|
||||||
|
e = tcsetattr (fd, TCSANOW, &ts);
|
||||||
|
if (e != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Can't set pseudo terminal attributes, err=%d\n", e);
|
||||||
|
perror ("pt tcsetattr");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* After running for a while on Linux, the write eventually
|
||||||
|
* blocks if no one is reading from the other side of
|
||||||
|
* the pseudo terminal. We get stuck on the kiss data
|
||||||
|
* write and reception stops.
|
||||||
|
*
|
||||||
|
* I tried using ioctl(,TIOCOUTQ,) to see how much was in
|
||||||
|
* the queue but that always returned zero. (Ubuntu)
|
||||||
|
*
|
||||||
|
* Let's try using non-blocking writes and see if we get
|
||||||
|
* the EWOULDBLOCK status instead of hanging.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if 0 // this is worse. all writes fail. errno = 0 bad file descriptor
|
||||||
|
flags = fcntl(fd, F_GETFL, 0);
|
||||||
|
e = fcntl (fd, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
if (e != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Can't set pseudo terminal to nonblocking, fcntl returns %d, errno = %d\n", e, errno);
|
||||||
|
perror ("pt fcntl");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if 0 // same
|
||||||
|
flags = 1;
|
||||||
|
e = ioctl (fd, FIONBIO, &flags);
|
||||||
|
if (e != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Can't set pseudo terminal to nonblocking, ioctl returns %d, errno = %d\n", e, errno);
|
||||||
|
perror ("pt ioctl");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf("Virtual KISS TNC is available on %s\n", slave_device);
|
||||||
|
dw_printf("WARNING - Dire Wolf will hang eventually if nothing is reading from it.\n");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The device name is not the same every time.
|
||||||
|
* This is inconvenient for the application because it might
|
||||||
|
* be necessary to change the device name in the configuration.
|
||||||
|
* Create a symlink, /tmp/kisstnc, so the application configuration
|
||||||
|
* does not need to change when the pseudo terminal name changes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//TODO: remove symlink on exit.
|
||||||
|
unlink (DEV_KISS_TNC);
|
||||||
|
|
||||||
|
if (symlink (slave_device, DEV_KISS_TNC) == 0) {
|
||||||
|
dw_printf ("Created symlink %s -> %s\n", DEV_KISS_TNC, slave_device);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Failed to create symlink %s\n", DEV_KISS_TNC);
|
||||||
|
perror ("");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 1
|
||||||
|
// Sample code shows this. Why would we open it here?
|
||||||
|
// On Ubuntu, the slave side disappears after a few
|
||||||
|
// seconds if no one opens it.
|
||||||
|
|
||||||
|
pt_slave_fd = open(slave_device, O_RDWR|O_NOCTTY);
|
||||||
|
|
||||||
|
if (pt_slave_fd < 0)
|
||||||
|
return MYFDERROR;
|
||||||
|
#endif
|
||||||
|
return (fd);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns fd for our side of null modem or MYFDERROR for error.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#if __CYGWIN__ || __WIN32__
|
||||||
|
|
||||||
|
static MYFDTYPE kiss_open_nullmodem (char *devicename)
|
||||||
|
{
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
|
||||||
|
MYFDTYPE fd;
|
||||||
|
DCB dcb;
|
||||||
|
int ok;
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("kiss_open_nullmodem ( '%s' )\n", devicename);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if DEBUG9
|
||||||
|
log_fp = fopen ("kiss-debug.txt", "w");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Need to use FILE_FLAG_OVERLAPPED for full duplex operation.
|
||||||
|
// Without it, write blocks when waiting on read.
|
||||||
|
|
||||||
|
// Read http://support.microsoft.com/kb/156932
|
||||||
|
|
||||||
|
|
||||||
|
fd = CreateFile(devicename, GENERIC_READ | GENERIC_WRITE,
|
||||||
|
0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
|
||||||
|
|
||||||
|
if (fd == MYFDERROR) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("ERROR - Could not connect to %s side of null modem for Windows KISS TNC.\n", devicename);
|
||||||
|
return (MYFDERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reference: http://msdn.microsoft.com/en-us/library/windows/desktop/aa363201(v=vs.85).aspx */
|
||||||
|
|
||||||
|
memset (&dcb, 0, sizeof(dcb));
|
||||||
|
dcb.DCBlength = sizeof(DCB);
|
||||||
|
|
||||||
|
ok = GetCommState (fd, &dcb);
|
||||||
|
if (! ok) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("kiss_open_nullmodem: GetCommState failed.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214(v=vs.85).aspx */
|
||||||
|
|
||||||
|
// dcb.BaudRate ? shouldn't matter
|
||||||
|
dcb.fBinary = 1;
|
||||||
|
dcb.fParity = 0;
|
||||||
|
dcb.fOutxCtsFlow = 0;
|
||||||
|
dcb.fOutxDsrFlow = 0;
|
||||||
|
dcb.fDtrControl = 0;
|
||||||
|
dcb.fDsrSensitivity = 0;
|
||||||
|
dcb.fOutX = 0;
|
||||||
|
dcb.fInX = 0;
|
||||||
|
dcb.fErrorChar = 0;
|
||||||
|
dcb.fNull = 0; /* Don't drop nul characters! */
|
||||||
|
dcb.fRtsControl = 0;
|
||||||
|
dcb.ByteSize = 8;
|
||||||
|
dcb.Parity = NOPARITY;
|
||||||
|
dcb.StopBits = ONESTOPBIT;
|
||||||
|
|
||||||
|
ok = SetCommState (fd, &dcb);
|
||||||
|
if (! ok) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("kiss_open_nullmodem: SetCommState failed.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf("Virtual KISS TNC is connected to %s side of null modem.\n", devicename);
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
/* Cygwin version. */
|
||||||
|
|
||||||
|
int fd;
|
||||||
|
struct termios ts;
|
||||||
|
int e;
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("kiss_open_nullmodem ( '%s' )\n", devicename);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
fd = open (devicename, O_RDWR);
|
||||||
|
|
||||||
|
if (fd == MYFDERROR) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("ERROR - Could not connect to %s side of null modem for Windows KISS TNC.\n", devicename);
|
||||||
|
return (MYFDERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
e = tcgetattr (fd, &ts);
|
||||||
|
if (e != 0) { perror ("nm tcgetattr"); }
|
||||||
|
|
||||||
|
cfmakeraw (&ts);
|
||||||
|
|
||||||
|
ts.c_cc[VMIN] = 1; /* wait for at least one character */
|
||||||
|
ts.c_cc[VTIME] = 0; /* no fancy timing. */
|
||||||
|
|
||||||
|
e = tcsetattr (fd, TCSANOW, &ts);
|
||||||
|
if (e != 0) { perror ("nm tcsetattr"); }
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf("Virtual KISS TNC is connected to %s side of null modem.\n", devicename);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return (fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: kiss_send_rec_packet
|
||||||
|
*
|
||||||
|
* Purpose: Send a received packet or text string to the client app.
|
||||||
|
*
|
||||||
|
* Inputs: chan - Channel number where packet was received.
|
||||||
|
* 0 = first, 1 = second if any.
|
||||||
|
*
|
||||||
|
* pp - Identifier for packet object.
|
||||||
|
*
|
||||||
|
* fbuf - Address of raw received frame buffer
|
||||||
|
* or a text string.
|
||||||
|
*
|
||||||
|
* flen - Length of raw received frame not including the FCS
|
||||||
|
* or -1 for a text string.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Description: Send message to client.
|
||||||
|
* We really don't care if anyone is listening or not.
|
||||||
|
* I don't even know if we can find out.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen)
|
||||||
|
{
|
||||||
|
unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN];
|
||||||
|
int kiss_len;
|
||||||
|
int j;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
#if ! __WIN32__
|
||||||
|
if (pt_master_fd == MYFDERROR) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __CYGWIN__ || __WIN32__
|
||||||
|
|
||||||
|
if (nullmodem_fd == MYFDERROR) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (flen < 0) {
|
||||||
|
flen = strlen((char*)fbuf);
|
||||||
|
if (kiss_debug) {
|
||||||
|
kiss_debug_print (TO_CLIENT, "Fake command prompt", fbuf, flen);
|
||||||
|
}
|
||||||
|
strcpy ((char *)kiss_buff, (char *)fbuf);
|
||||||
|
kiss_len = strlen((char *)kiss_buff);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
kiss_len = 0;
|
||||||
|
kiss_buff[kiss_len++] = FEND;
|
||||||
|
kiss_buff[kiss_len++] = chan << 4;
|
||||||
|
|
||||||
|
for (j=0; j<flen; j++) {
|
||||||
|
|
||||||
|
if (fbuf[j] == FEND) {
|
||||||
|
kiss_buff[kiss_len++] = FESC;
|
||||||
|
kiss_buff[kiss_len++] = TFEND;
|
||||||
|
}
|
||||||
|
else if (fbuf[j] == FESC) {
|
||||||
|
kiss_buff[kiss_len++] = FESC;
|
||||||
|
kiss_buff[kiss_len++] = TFESC;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
kiss_buff[kiss_len++] = fbuf[j];
|
||||||
|
}
|
||||||
|
assert (kiss_len < sizeof (kiss_buff));
|
||||||
|
}
|
||||||
|
kiss_buff[kiss_len++] = FEND;
|
||||||
|
|
||||||
|
/* This has the escapes but not the surrounding FENDs. */
|
||||||
|
|
||||||
|
if (kiss_debug) {
|
||||||
|
kiss_debug_print (TO_CLIENT, NULL, kiss_buff+1, kiss_len-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ! __WIN32__
|
||||||
|
|
||||||
|
/* Pseudo terminal for Cygwin and Linux. */
|
||||||
|
|
||||||
|
|
||||||
|
err = write (pt_master_fd, kiss_buff, (size_t)kiss_len);
|
||||||
|
|
||||||
|
if (err == -1 && errno == EWOULDBLOCK) {
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set (DW_COLOR_INFO);
|
||||||
|
dw_printf ("KISS SEND - discarding message because write would block.\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else if (err != kiss_len)
|
||||||
|
{
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("\nError sending KISS message to client application on pseudo terminal. fd=%d, len=%d, write returned %d, errno = %d\n\n",
|
||||||
|
pt_master_fd, kiss_len, err, errno);
|
||||||
|
perror ("pt write");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __CYGWIN__ || __WIN32__
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This write can block if nothing is connected to the other end.
|
||||||
|
* The solution is found in the com0com ReadMe file:
|
||||||
|
*
|
||||||
|
* Q. My application hangs during its startup when it sends anything to one paired
|
||||||
|
* COM port. The only way to unhang it is to start HyperTerminal, which is connected
|
||||||
|
* to the other paired COM port. I didn't have this problem with physical serial
|
||||||
|
* ports.
|
||||||
|
* A. Your application can hang because receive buffer overrun is disabled by
|
||||||
|
* default. You can fix the problem by enabling receive buffer overrun for the
|
||||||
|
* receiving port. Also, to prevent some flow control issues you need to enable
|
||||||
|
* baud rate emulation for the sending port. So, if your application use port CNCA0
|
||||||
|
* and other paired port is CNCB0, then:
|
||||||
|
*
|
||||||
|
* 1. Launch the Setup Command Prompt shortcut.
|
||||||
|
* 2. Enter the change commands, for example:
|
||||||
|
*
|
||||||
|
* command> change CNCB0 EmuOverrun=yes
|
||||||
|
* command> change CNCA0 EmuBR=yes
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
|
||||||
|
DWORD nwritten;
|
||||||
|
|
||||||
|
/* Without this, write blocks while we are waiting on a read. */
|
||||||
|
static OVERLAPPED ov_wr;
|
||||||
|
memset (&ov_wr, 0, sizeof(ov_wr));
|
||||||
|
|
||||||
|
if ( ! WriteFile (nullmodem_fd, kiss_buff, kiss_len, &nwritten, &ov_wr))
|
||||||
|
{
|
||||||
|
err = GetLastError();
|
||||||
|
if (err != ERROR_IO_PENDING)
|
||||||
|
{
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("\nError sending KISS message to client application thru null modem. Error %d.\n\n", (int)GetLastError());
|
||||||
|
//CloseHandle (nullmodem_fd);
|
||||||
|
//nullmodem_fd = MYFDERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (nwritten != flen)
|
||||||
|
{
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("\nError sending KISS message to client application thru null modem. Only %d of %d written.\n\n", (int)nwritten, kiss_len);
|
||||||
|
//CloseHandle (nullmodem_fd);
|
||||||
|
//nullmodem_fd = MYFDERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
/* Could wait with GetOverlappedResult but we never */
|
||||||
|
/* have an issues in this direction. */
|
||||||
|
//text_color_set(DW_COLOR_DEBUG);
|
||||||
|
//dw_printf ("KISS SEND completed. wrote %d / %d\n", nwritten, kiss_len);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#else
|
||||||
|
err = write (nullmodem_fd, kiss_buf, (size_t)kiss_len);
|
||||||
|
if (err != len)
|
||||||
|
{
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("\nError sending KISS message to client application thru null modem. err=%d\n\n", err);
|
||||||
|
//close (nullmodem_fd);
|
||||||
|
//nullmodem_fd = MYFDERROR;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} /* kiss_send_rec_packet */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: kiss_listen_thread
|
||||||
|
*
|
||||||
|
* Purpose: Wait for messages from an application.
|
||||||
|
*
|
||||||
|
* Inputs: arg - File descriptor for reading.
|
||||||
|
*
|
||||||
|
* Outputs: pt_slave_fd - File descriptor for communicating with client app.
|
||||||
|
*
|
||||||
|
* Description: Process messages from the client application.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
//TODO: should pass fd by reference so it can be zapped.
|
||||||
|
//BUG: If we close it here, that fact doesn't get back
|
||||||
|
// to the main receiving thread.
|
||||||
|
|
||||||
|
/* Return one byte (value 0 - 255) or terminate thread on error. */
|
||||||
|
|
||||||
|
|
||||||
|
static int kiss_get (MYFDTYPE fd)
|
||||||
|
{
|
||||||
|
unsigned char ch;
|
||||||
|
|
||||||
|
#if __WIN32__ /* Native Windows version. */
|
||||||
|
|
||||||
|
DWORD n;
|
||||||
|
static OVERLAPPED ov_rd;
|
||||||
|
|
||||||
|
memset (&ov_rd, 0, sizeof(ov_rd));
|
||||||
|
ov_rd.hEvent = CreateEvent (NULL, TRUE, FALSE, NULL);
|
||||||
|
|
||||||
|
|
||||||
|
/* Overlapped I/O makes reading rather complicated. */
|
||||||
|
/* See: http://msdn.microsoft.com/en-us/library/ms810467.aspx */
|
||||||
|
|
||||||
|
/* It seems that the read completes OK with a count */
|
||||||
|
/* of 0 every time we send a message to the serial port. */
|
||||||
|
|
||||||
|
n = 0; /* Number of characters read. */
|
||||||
|
|
||||||
|
while (n == 0) {
|
||||||
|
|
||||||
|
if ( ! ReadFile (fd, &ch, 1, &n, &ov_rd))
|
||||||
|
{
|
||||||
|
int err1 = GetLastError();
|
||||||
|
|
||||||
|
if (err1 == ERROR_IO_PENDING)
|
||||||
|
{
|
||||||
|
/* Wait for completion. */
|
||||||
|
|
||||||
|
if (WaitForSingleObject (ov_rd.hEvent, INFINITE) == WAIT_OBJECT_0)
|
||||||
|
{
|
||||||
|
if ( ! GetOverlappedResult (fd, &ov_rd, &n, 1))
|
||||||
|
{
|
||||||
|
int err3 = GetLastError();
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("\nKISS GetOverlappedResult error %d.\n\n", err3);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Success! n should be 1 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("\nKISS ReadFile error %d. Closing connection.\n\n", err1);
|
||||||
|
//CloseHandle (fd);
|
||||||
|
//fd = MYFDERROR;
|
||||||
|
//pthread_exit (NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* end while n==0 */
|
||||||
|
|
||||||
|
CloseHandle(ov_rd.hEvent);
|
||||||
|
|
||||||
|
if (n != 1) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("\nKISS failed to get one byte. n=%d.\n\n", (int)n);
|
||||||
|
|
||||||
|
#if DEBUG9
|
||||||
|
fprintf (log_fp, "n=%d\n", n);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#else /* Linux/Cygwin version */
|
||||||
|
|
||||||
|
int n;
|
||||||
|
|
||||||
|
n = read(fd, &ch, (size_t)1);
|
||||||
|
|
||||||
|
if (n != 1) {
|
||||||
|
//text_color_set(DW_COLOR_ERROR);
|
||||||
|
//dw_printf ("\nError receiving kiss message from client application. Closing connection %d.\n\n", fd);
|
||||||
|
|
||||||
|
close (fd);
|
||||||
|
|
||||||
|
fd = MYFDERROR;
|
||||||
|
pthread_exit (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if DEBUGx
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("kiss_get(%d) returns 0x%02x\n", fd, ch);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if DEBUG9
|
||||||
|
fprintf (log_fp, "%02x %c %c", ch,
|
||||||
|
isprint(ch) ? ch : '.' ,
|
||||||
|
(isupper(ch>>1) || isdigit(ch>>1) || (ch>>1) == ' ') ? (ch>>1) : '.');
|
||||||
|
if (ch == FEND) fprintf (log_fp, " FEND");
|
||||||
|
if (ch == FESC) fprintf (log_fp, " FESC");
|
||||||
|
if (ch == TFEND) fprintf (log_fp, " TFEND");
|
||||||
|
if (ch == TFESC) fprintf (log_fp, " TFESC");
|
||||||
|
if (ch == '\r') fprintf (log_fp, " CR");
|
||||||
|
if (ch == '\n') fprintf (log_fp, " LF");
|
||||||
|
fprintf (log_fp, "\n");
|
||||||
|
if (ch == FEND) fflush (log_fp);
|
||||||
|
#endif
|
||||||
|
return (ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static void * kiss_listen_thread (void *arg)
|
||||||
|
{
|
||||||
|
MYFDTYPE fd = (MYFDTYPE)(long)arg;
|
||||||
|
|
||||||
|
unsigned char ch;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("kiss_listen_thread ( %d )\n", fd);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
ch = kiss_get(fd);
|
||||||
|
|
||||||
|
if (kiss_frame (&kf, ch, kiss_debug, kiss_send_rec_packet)) {
|
||||||
|
kiss_process_msg (&kf, kiss_debug);
|
||||||
|
}
|
||||||
|
} /* while (1) */
|
||||||
|
|
||||||
|
return (NULL); /* Unreachable but avoids compiler warning. */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* end kiss.c */
|
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Name: kiss.h
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "ax25_pad.h" /* for packet_t */
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void kiss_init (struct misc_config_s *misc_config);
|
||||||
|
|
||||||
|
void kiss_send_rec_packet (int chan, unsigned char *fbuf, int flen);
|
||||||
|
|
||||||
|
void kiss_serial_set_debug (int n);
|
||||||
|
|
||||||
|
|
||||||
|
/* end kiss.h */
|
|
@ -0,0 +1,407 @@
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Module: kiss_frame.c
|
||||||
|
*
|
||||||
|
* Purpose: Common code used by Serial port and network versions of KISS protocol.
|
||||||
|
*
|
||||||
|
* Description: The KISS TNS protocol is described in http://www.ka9q.net/papers/kiss.html
|
||||||
|
*
|
||||||
|
* Briefly, a frame is composed of
|
||||||
|
*
|
||||||
|
* * FEND (0xC0)
|
||||||
|
* * Contents - with special escape sequences so a 0xc0
|
||||||
|
* byte in the data is not taken as end of frame.
|
||||||
|
* as part of the data.
|
||||||
|
* * FEND
|
||||||
|
*
|
||||||
|
* The first byte of the frame contains:
|
||||||
|
*
|
||||||
|
* * port number in upper nybble.
|
||||||
|
* * command in lower nybble.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Commands from application recognized:
|
||||||
|
*
|
||||||
|
* 0 Data Frame AX.25 frame in raw format.
|
||||||
|
*
|
||||||
|
* 1 TXDELAY See explanation in xmit.c.
|
||||||
|
*
|
||||||
|
* 2 Persistence " "
|
||||||
|
*
|
||||||
|
* 3 SlotTime " "
|
||||||
|
*
|
||||||
|
* 4 TXtail " "
|
||||||
|
* Spec says it is obsolete but Xastir
|
||||||
|
* sends it and we respect it.
|
||||||
|
*
|
||||||
|
* 5 FullDuplex Ignored. Always full duplex.
|
||||||
|
*
|
||||||
|
* 6 SetHardware TNC specific. Ignored.
|
||||||
|
*
|
||||||
|
* FF Return Exit KISS mode. Ignored.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Messages sent to client application:
|
||||||
|
*
|
||||||
|
* 0 Data Frame Received AX.25 frame in raw format.
|
||||||
|
*
|
||||||
|
*---------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "ax25_pad.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
#include "kiss_frame.h"
|
||||||
|
#include "tq.h"
|
||||||
|
#include "xmit.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: kiss_frame
|
||||||
|
*
|
||||||
|
* Purpose: Extract a KISS frame from byte stream.
|
||||||
|
*
|
||||||
|
* Inputs: kf - Current state of building a frame.
|
||||||
|
* ch - A byte from the input stream.
|
||||||
|
* debug - Activates debug output.
|
||||||
|
* sendfun - Function to send something to the client application.
|
||||||
|
*
|
||||||
|
* Outputs: kf - Current state is updated.
|
||||||
|
*
|
||||||
|
* Returns: TRUE when a complete frame is ready for processing.
|
||||||
|
*
|
||||||
|
* Bug: For send, the debug output shows exactly what is
|
||||||
|
* being sent including the surrounding FEND and any
|
||||||
|
* escapes. For receive, we don't show those.
|
||||||
|
*
|
||||||
|
*-----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Application might send some commands to put TNC into KISS mode.
|
||||||
|
* For example, APRSIS32 sends something like:
|
||||||
|
*
|
||||||
|
* <0x0d>
|
||||||
|
* <0x0d>
|
||||||
|
* XFLOW OFF<0x0d>
|
||||||
|
* FULLDUP OFF<0x0d>
|
||||||
|
* KISS ON<0x0d>
|
||||||
|
* RESTART<0x0d>
|
||||||
|
* <0x03><0x03><0x03>
|
||||||
|
* TC 1<0x0d>
|
||||||
|
* TN 2,0<0x0d><0x0d><0x0d>
|
||||||
|
* XFLOW OFF<0x0d>
|
||||||
|
* FULLDUP OFF<0x0d>
|
||||||
|
* KISS ON<0x0d>
|
||||||
|
* RESTART<0x0d>
|
||||||
|
*
|
||||||
|
* This keeps repeating over and over and over and over again if
|
||||||
|
* it doesn't get any sort of response.
|
||||||
|
*
|
||||||
|
* Let's try to keep it happy by sending back a command prompt.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int kiss_frame (kiss_frame_t *kf, unsigned char ch, int debug, void (*sendfun)(int,unsigned char*,int))
|
||||||
|
{
|
||||||
|
|
||||||
|
switch (kf->state) {
|
||||||
|
|
||||||
|
case KS_SEARCHING: /* Searching for starting FEND. */
|
||||||
|
|
||||||
|
if (ch == FEND) {
|
||||||
|
|
||||||
|
/* Start of frame. But first print any collected noise for debugging. */
|
||||||
|
|
||||||
|
if (kf->noise_len > 0) {
|
||||||
|
if (debug) {
|
||||||
|
kiss_debug_print (FROM_CLIENT, "Rejected Noise", kf->noise, kf->noise_len);
|
||||||
|
}
|
||||||
|
kf->noise_len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
kf->kiss_len = 0;
|
||||||
|
kf->state = KS_COLLECTING;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Noise to be rejected. */
|
||||||
|
|
||||||
|
if (kf->noise_len < MAX_NOISE_LEN) {
|
||||||
|
kf->noise[kf->noise_len++] = ch;
|
||||||
|
}
|
||||||
|
if (ch == '\r') {
|
||||||
|
if (debug) {
|
||||||
|
kiss_debug_print (FROM_CLIENT, "Rejected Noise", kf->noise, kf->noise_len);
|
||||||
|
kf->noise[kf->noise_len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Try to appease it by sending something back. */
|
||||||
|
if (strcasecmp("restart\r", (char*)(kf->noise)) == 0 ||
|
||||||
|
strcasecmp("reset\r", (char*)(kf->noise)) == 0) {
|
||||||
|
(*sendfun) (0, (unsigned char *)"\xc0\xc0", -1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
(*sendfun) (0, (unsigned char *)"\r\ncmd:", -1);
|
||||||
|
}
|
||||||
|
kf->noise_len = 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case KS_COLLECTING: /* Frame collection in progress. */
|
||||||
|
|
||||||
|
if (ch == FEND) {
|
||||||
|
|
||||||
|
/* End of frame. */
|
||||||
|
|
||||||
|
if (kf->kiss_len == 0) {
|
||||||
|
/* Empty frame. Just go on collecting. */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
kiss_debug_print (FROM_CLIENT, NULL, kf->kiss_msg, kf->kiss_len);
|
||||||
|
}
|
||||||
|
kf->state = KS_SEARCHING;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kf->kiss_len < MAX_KISS_LEN) {
|
||||||
|
kf->kiss_msg[kf->kiss_len++] = ch;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("KISS message exceeded maximum length.\n");
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case KS_ESCAPE: /* Expecting TFESC or TFEND. */
|
||||||
|
|
||||||
|
if (kf->kiss_len >= MAX_KISS_LEN) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("KISS message exceeded maximum length.\n");
|
||||||
|
kf->state = KS_COLLECTING;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == TFESC) {
|
||||||
|
kf->kiss_msg[kf->kiss_len++] = FESC;
|
||||||
|
}
|
||||||
|
else if (ch == TFEND) {
|
||||||
|
kf->kiss_msg[kf->kiss_len++] = FEND;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("KISS protocol error. TFESC or TFEND expected.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
kf->state = KS_COLLECTING;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0; /* unreachable but suppress compiler warning. */
|
||||||
|
|
||||||
|
} /* end kiss_frame */
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: kiss_process_msg
|
||||||
|
*
|
||||||
|
* Purpose: Process a message from the KISS client.
|
||||||
|
*
|
||||||
|
* Inputs: kf - Current state of building a frame.
|
||||||
|
* Should be complete.
|
||||||
|
*
|
||||||
|
* debug - Debug option is selected.
|
||||||
|
*
|
||||||
|
*-----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
void kiss_process_msg (kiss_frame_t *kf, int debug)
|
||||||
|
{
|
||||||
|
int port;
|
||||||
|
int cmd;
|
||||||
|
packet_t pp;
|
||||||
|
|
||||||
|
port = (kf->kiss_msg[0] >> 4) & 0xf;
|
||||||
|
cmd = kf->kiss_msg[0] & 0xf;
|
||||||
|
|
||||||
|
switch (cmd)
|
||||||
|
{
|
||||||
|
case 0: /* Data Frame */
|
||||||
|
|
||||||
|
/* Special hack - Discard apparently bad data from Linux AX25. */
|
||||||
|
|
||||||
|
if ((port == 2 || port == 8) &&
|
||||||
|
kf->kiss_msg[1] == 'Q' << 1 &&
|
||||||
|
kf->kiss_msg[2] == 'S' << 1 &&
|
||||||
|
kf->kiss_msg[3] == 'T' << 1 &&
|
||||||
|
kf->kiss_msg[4] == ' ' << 1 &&
|
||||||
|
kf->kiss_msg[15] == 3 &&
|
||||||
|
kf->kiss_msg[16] == 0xcd) {
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Special case - Drop packets which appear to be in error.\n");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pp = ax25_from_frame (kf->kiss_msg+1, kf->kiss_len-1, -1);
|
||||||
|
if (pp == NULL) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("ERROR - Invalid KISS data frame from client app.\n");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
/* How can we determine if it is an original or repeated message? */
|
||||||
|
/* If there is at least one digipeater in the frame, AND */
|
||||||
|
/* that digipeater has been used, it should go out quickly thru */
|
||||||
|
/* the high priority queue. */
|
||||||
|
/* Otherwise, it is an original for the low priority queue. */
|
||||||
|
|
||||||
|
if (ax25_get_num_repeaters(pp) >= 1 &&
|
||||||
|
ax25_get_h(pp,AX25_REPEATER_1)) {
|
||||||
|
tq_append (port, TQ_PRIO_0_HI, pp);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tq_append (port, TQ_PRIO_1_LO, pp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: /* TXDELAY */
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("KISS protocol set TXDELAY = %d, port %d\n", kf->kiss_msg[1], port);
|
||||||
|
xmit_set_txdelay (port, kf->kiss_msg[1]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: /* Persistence */
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("KISS protocol set Persistence = %d, port %d\n", kf->kiss_msg[1], port);
|
||||||
|
xmit_set_persist (port, kf->kiss_msg[1]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3: /* SlotTime */
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("KISS protocol set SlotTime = %d, port %d\n", kf->kiss_msg[1], port);
|
||||||
|
xmit_set_slottime (port, kf->kiss_msg[1]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4: /* TXtail */
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("KISS protocol set TXtail = %d, port %d\n", kf->kiss_msg[1], port);
|
||||||
|
xmit_set_txtail (port, kf->kiss_msg[1]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5: /* FullDuplex */
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("KISS protocol set FullDuplex = %d, port %d\n", kf->kiss_msg[1], port);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 6: /* TNC specific */
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("KISS protocol set hardware - ignored.\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 15: /* End KISS mode, port should be 15. */
|
||||||
|
/* Ignore it. */
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("KISS protocol end KISS mode\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("KISS Invalid command %d\n", cmd);
|
||||||
|
kiss_debug_print (FROM_CLIENT, NULL, kf->kiss_msg, kf->kiss_len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* end kiss_process_msg */
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: kiss_debug_print
|
||||||
|
*
|
||||||
|
* Purpose: Print message to/from client for debugging.
|
||||||
|
*
|
||||||
|
* Inputs: fromto - Direction of message.
|
||||||
|
* special - Comment if not a KISS frame.
|
||||||
|
* pmsg - Address of the message block.
|
||||||
|
* msg_len - Length of the message.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
/* In server.c. Should probably move to some misc. function file. */
|
||||||
|
|
||||||
|
void hex_dump (unsigned char *p, int len);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len)
|
||||||
|
{
|
||||||
|
const char *direction [2] = { "from", "to" };
|
||||||
|
const char *prefix [2] = { "<<<", ">>>" };
|
||||||
|
const char *function[16] = {
|
||||||
|
"Data frame", "TXDELAY", "P", "SlotTime",
|
||||||
|
"TXtail", "FullDuplex", "SetHardware", "Invalid 7",
|
||||||
|
"Invalid 8", "Invalid 9", "Invalid 10", "Invalid 11",
|
||||||
|
"Invalid 12", "Invalid 13", "Invalid 14", "Return" };
|
||||||
|
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("\n");
|
||||||
|
|
||||||
|
if (special == NULL) {
|
||||||
|
dw_printf ("%s %s %s KISS client application, port %d, total length = %d\n",
|
||||||
|
prefix[(int)fromto], function[pmsg[0] & 0xf], direction[(int)fromto],
|
||||||
|
(pmsg[0] >> 4) & 0xf, msg_len);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dw_printf ("%s %s %s KISS client application, total length = %d\n",
|
||||||
|
prefix[(int)fromto], special, direction[(int)fromto],
|
||||||
|
msg_len);
|
||||||
|
}
|
||||||
|
hex_dump ((char*)pmsg, msg_len);
|
||||||
|
|
||||||
|
} /* end kiss_debug_print */
|
||||||
|
|
||||||
|
|
||||||
|
/* end kiss_frame.c */
|
|
@ -0,0 +1,47 @@
|
||||||
|
|
||||||
|
/* kiss_frame.h */
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Special characters used by SLIP protocol.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define FEND 0xC0
|
||||||
|
#define FESC 0xDB
|
||||||
|
#define TFEND 0xDC
|
||||||
|
#define TFESC 0xDD
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
enum kiss_state_e {
|
||||||
|
KS_SEARCHING, /* Looking for FEND to start KISS frame. */
|
||||||
|
KS_COLLECTING, /* In process of collecting KISS frame. */
|
||||||
|
KS_ESCAPE }; /* FESC found in frame. */
|
||||||
|
|
||||||
|
#define MAX_KISS_LEN 2048 /* Spec calls for at least 1024. */
|
||||||
|
|
||||||
|
#define MAX_NOISE_LEN 100
|
||||||
|
|
||||||
|
typedef struct kiss_frame_s {
|
||||||
|
|
||||||
|
enum kiss_state_e state;
|
||||||
|
|
||||||
|
unsigned char kiss_msg[MAX_KISS_LEN];
|
||||||
|
int kiss_len;
|
||||||
|
|
||||||
|
unsigned char noise[MAX_NOISE_LEN];
|
||||||
|
int noise_len;
|
||||||
|
|
||||||
|
} kiss_frame_t;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int kiss_frame (kiss_frame_t *kf, unsigned char ch, int debug, void (*sendfun)(int,unsigned char*,int));
|
||||||
|
|
||||||
|
void kiss_process_msg (kiss_frame_t *kf, int debug);
|
||||||
|
|
||||||
|
typedef enum fromto_e { FROM_CLIENT=0, TO_CLIENT=1 } fromto_t;
|
||||||
|
|
||||||
|
void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len);
|
||||||
|
|
||||||
|
/* end kiss_frame.h */
|
|
@ -0,0 +1,671 @@
|
||||||
|
//
|
||||||
|
// 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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Module: kissnet.c
|
||||||
|
*
|
||||||
|
* Purpose: Provide service to other applications via KISS protocol via TCP socket.
|
||||||
|
*
|
||||||
|
* Input:
|
||||||
|
*
|
||||||
|
* Outputs:
|
||||||
|
*
|
||||||
|
* Description: This provides a TCP socket for communication with a client application.
|
||||||
|
*
|
||||||
|
* It implements the KISS TNS protocol as described in:
|
||||||
|
* http://www.ka9q.net/papers/kiss.html
|
||||||
|
*
|
||||||
|
* Briefly, a frame is composed of
|
||||||
|
*
|
||||||
|
* * FEND (0xC0)
|
||||||
|
* * Contents - with special escape sequences so a 0xc0
|
||||||
|
* byte in the data is not taken as end of frame.
|
||||||
|
* as part of the data.
|
||||||
|
* * FEND
|
||||||
|
*
|
||||||
|
* The first byte of the frame contains:
|
||||||
|
*
|
||||||
|
* * port number in upper nybble.
|
||||||
|
* * command in lower nybble.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Commands from application recognized:
|
||||||
|
*
|
||||||
|
* 0 Data Frame AX.25 frame in raw format.
|
||||||
|
*
|
||||||
|
* 1 TXDELAY See explanation in xmit.c.
|
||||||
|
*
|
||||||
|
* 2 Persistence " "
|
||||||
|
*
|
||||||
|
* 3 SlotTime " "
|
||||||
|
*
|
||||||
|
* 4 TXtail " "
|
||||||
|
* Spec says it is obsolete but Xastir
|
||||||
|
* sends it and we respect it.
|
||||||
|
*
|
||||||
|
* 5 FullDuplex Ignored. Always full duplex.
|
||||||
|
*
|
||||||
|
* 6 SetHardware TNC specific. Ignored.
|
||||||
|
*
|
||||||
|
* FF Return Exit KISS mode. Ignored.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Messages sent to client application:
|
||||||
|
*
|
||||||
|
* 0 Data Frame Received AX.25 frame in raw format.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* References: Getting Started with Winsock
|
||||||
|
* http://msdn.microsoft.com/en-us/library/windows/desktop/bb530742(v=vs.85).aspx
|
||||||
|
*
|
||||||
|
* Future: Originally we had:
|
||||||
|
* KISS over serial port.
|
||||||
|
* AGW over socket.
|
||||||
|
* This is the two of them munged together and we end up with duplicate code.
|
||||||
|
* It would have been better to separate out the transport and application layers.
|
||||||
|
* Maybe someday.
|
||||||
|
*
|
||||||
|
*---------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Native Windows: Use the Winsock interface.
|
||||||
|
* Linux: Use the BSD socket interface.
|
||||||
|
* Cygwin: Can use either one.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
#include <winsock2.h>
|
||||||
|
#define _WIN32_WINNT 0x0501
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#else
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "tq.h"
|
||||||
|
#include "ax25_pad.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
#include "audio.h"
|
||||||
|
#include "kissnet.h"
|
||||||
|
#include "kiss_frame.h"
|
||||||
|
#include "xmit.h"
|
||||||
|
|
||||||
|
|
||||||
|
static kiss_frame_t kf; /* Accumulated KISS frame and state of decoder. */
|
||||||
|
|
||||||
|
|
||||||
|
static int client_sock; /* File descriptor for socket for */
|
||||||
|
/* communication with client application. */
|
||||||
|
/* Set to -1 if not connected. */
|
||||||
|
/* (Don't use SOCKET type because it is unsigned.) */
|
||||||
|
|
||||||
|
static int num_channels; /* Number of radio ports. */
|
||||||
|
|
||||||
|
|
||||||
|
static void * connect_listen_thread (void *arg);
|
||||||
|
static void * kissnet_listen_thread (void *arg);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static int kiss_debug = 0; /* Print information flowing from and to client. */
|
||||||
|
|
||||||
|
void kiss_net_set_debug (int n)
|
||||||
|
{
|
||||||
|
kiss_debug = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: kissnet_init
|
||||||
|
*
|
||||||
|
* Purpose: Set up a server to listen for connection requests from
|
||||||
|
* an application such as Xastir or APRSIS32.
|
||||||
|
*
|
||||||
|
* Inputs: mc->kiss_port - TCP port for server.
|
||||||
|
* Main program has default of 8000 but allows
|
||||||
|
* an alternative to be specified on the command line
|
||||||
|
*
|
||||||
|
* Outputs:
|
||||||
|
*
|
||||||
|
* Description: This starts two threads:
|
||||||
|
* * to listen for a connection from client app.
|
||||||
|
* * to listen for commands from client app.
|
||||||
|
* so the main application doesn't block while we wait for these.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
void kissnet_init (struct misc_config_s *mc)
|
||||||
|
{
|
||||||
|
#if __WIN32__
|
||||||
|
HANDLE connect_listen_th;
|
||||||
|
HANDLE cmd_listen_th;
|
||||||
|
#else
|
||||||
|
pthread_t connect_listen_tid;
|
||||||
|
pthread_t cmd_listen_tid;
|
||||||
|
#endif
|
||||||
|
int e;
|
||||||
|
int kiss_port = mc->kiss_port;
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("kissnet_init ( %d )\n", kiss_port);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
memset (&kf, 0, sizeof(kf));
|
||||||
|
|
||||||
|
client_sock = -1;
|
||||||
|
num_channels = mc->num_channels;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This waits for a client to connect and sets client_sock.
|
||||||
|
*/
|
||||||
|
#if __WIN32__
|
||||||
|
connect_listen_th = _beginthreadex (NULL, 0, connect_listen_thread, (void *)kiss_port, 0, NULL);
|
||||||
|
if (connect_listen_th == NULL) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Could not create KISS socket connect listening thread\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
e = pthread_create (&connect_listen_tid, NULL, connect_listen_thread, (void *)(long)kiss_port);
|
||||||
|
if (e != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
perror("Could not create KISS socket connect listening thread");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This reads messages from client when client_sock is valid.
|
||||||
|
*/
|
||||||
|
#if __WIN32__
|
||||||
|
cmd_listen_th = _beginthreadex (NULL, 0, kissnet_listen_thread, NULL, 0, NULL);
|
||||||
|
if (cmd_listen_th == NULL) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Could not create KISS socket command listening thread\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
e = pthread_create (&cmd_listen_tid, NULL, kissnet_listen_thread, NULL);
|
||||||
|
if (e != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
perror("Could not create KISS socket command listening thread");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: connect_listen_thread
|
||||||
|
*
|
||||||
|
* Purpose: Wait for a connection request from an application.
|
||||||
|
*
|
||||||
|
* Inputs: arg - TCP port for server.
|
||||||
|
* Main program has default of 8001 but allows
|
||||||
|
* an alternative to be specified on the command line
|
||||||
|
*
|
||||||
|
* Outputs: client_sock - File descriptor for communicating with client app.
|
||||||
|
*
|
||||||
|
* Description: Wait for connection request from client and establish
|
||||||
|
* communication.
|
||||||
|
* Note that the client can go away and come back again and
|
||||||
|
* re-establish communication without restarting this application.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
static void * connect_listen_thread (void *arg)
|
||||||
|
{
|
||||||
|
#if __WIN32__
|
||||||
|
|
||||||
|
struct addrinfo hints;
|
||||||
|
struct addrinfo *ai = NULL;
|
||||||
|
int err;
|
||||||
|
char kiss_port_str[12];
|
||||||
|
|
||||||
|
SOCKET listen_sock;
|
||||||
|
WSADATA wsadata;
|
||||||
|
|
||||||
|
sprintf (kiss_port_str, "%d", (int)(long)arg);
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("DEBUG: kissnet port = %d = '%s'\n", (int)(long)arg, kiss_port_str);
|
||||||
|
#endif
|
||||||
|
err = WSAStartup (MAKEWORD(2,2), &wsadata);
|
||||||
|
if (err != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf("WSAStartup failed: %d\n", err);
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
//sleep (1);
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
memset (&hints, 0, sizeof(hints));
|
||||||
|
hints.ai_family = AF_INET;
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
hints.ai_protocol = IPPROTO_TCP;
|
||||||
|
hints.ai_flags = AI_PASSIVE;
|
||||||
|
|
||||||
|
err = getaddrinfo(NULL, kiss_port_str, &hints, &ai);
|
||||||
|
if (err != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf("getaddrinfo failed: %d\n", err);
|
||||||
|
//sleep (1);
|
||||||
|
WSACleanup();
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
listen_sock= socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
|
||||||
|
if (listen_sock == INVALID_SOCKET) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("connect_listen_thread: Socket creation failed, err=%d", WSAGetLastError());
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf("Binding to port %s ... \n", kiss_port_str);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
err = bind( listen_sock, ai->ai_addr, (int)ai->ai_addrlen);
|
||||||
|
if (err == SOCKET_ERROR) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf("Bind failed with error: %d\n", WSAGetLastError());
|
||||||
|
freeaddrinfo(ai);
|
||||||
|
closesocket(listen_sock);
|
||||||
|
WSACleanup();
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
freeaddrinfo(ai);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf("opened KISS socket as fd (%d) on port (%s) for stream i/o\n", listen_sock, kiss_port_str );
|
||||||
|
#endif
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
|
||||||
|
while (client_sock > 0) {
|
||||||
|
SLEEP_SEC(1); /* Already connected. Try again later. */
|
||||||
|
}
|
||||||
|
|
||||||
|
#define QUEUE_SIZE 5
|
||||||
|
|
||||||
|
if(listen(listen_sock,QUEUE_SIZE) == SOCKET_ERROR)
|
||||||
|
{
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf("Listen failed with error: %d\n", WSAGetLastError());
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf("Ready to accept KISS client application on port %s ...\n", kiss_port_str);
|
||||||
|
|
||||||
|
client_sock = accept(listen_sock, NULL, NULL);
|
||||||
|
|
||||||
|
if (client_sock == -1) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf("Accept failed with error: %d\n", WSAGetLastError());
|
||||||
|
closesocket(listen_sock);
|
||||||
|
WSACleanup();
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf("\nConnected to KISS client application ...\n\n");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
struct sockaddr_in sockaddr; /* Internet socket address stuct */
|
||||||
|
socklen_t sockaddr_size = sizeof(struct sockaddr_in);
|
||||||
|
int kiss_port = (int)(long)arg;
|
||||||
|
int listen_sock;
|
||||||
|
|
||||||
|
listen_sock= socket(AF_INET,SOCK_STREAM,0);
|
||||||
|
if (listen_sock == -1) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
perror ("connect_listen_thread: Socket creation failed");
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
sockaddr.sin_addr.s_addr = INADDR_ANY;
|
||||||
|
sockaddr.sin_port = htons(kiss_port);
|
||||||
|
sockaddr.sin_family = AF_INET;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf("Binding to port %d ... \n", kiss_port);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (bind(listen_sock,(struct sockaddr*)&sockaddr,sizeof(sockaddr)) == -1) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
perror ("connect_listen_thread: Bind failed");
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
getsockname( listen_sock, (struct sockaddr *)(&sockaddr), &sockaddr_size);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf("opened KISS socket as fd (%d) on port (%d) for stream i/o\n", listen_sock, ntohs(sockaddr.sin_port) );
|
||||||
|
#endif
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
|
||||||
|
while (client_sock > 0) {
|
||||||
|
SLEEP_SEC(1); /* Already connected. Try again later. */
|
||||||
|
}
|
||||||
|
|
||||||
|
#define QUEUE_SIZE 5
|
||||||
|
|
||||||
|
if(listen(listen_sock,QUEUE_SIZE) == -1)
|
||||||
|
{
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
perror ("connect_listen_thread: Listen failed");
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf("Ready to accept KISS client application on port %d ...\n", kiss_port);
|
||||||
|
|
||||||
|
client_sock = accept(listen_sock, (struct sockaddr*)(&sockaddr),&sockaddr_size);
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf("\nConnected to KISS client application ...\n\n");
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: kissnet_send_rec_packet
|
||||||
|
*
|
||||||
|
* Purpose: Send a received packet to the client app.
|
||||||
|
*
|
||||||
|
* Inputs: chan - Channel number where packet was received.
|
||||||
|
* 0 = first, 1 = second if any.
|
||||||
|
*
|
||||||
|
* fbuf - Address of raw received frame buffer
|
||||||
|
* or a text string.
|
||||||
|
*
|
||||||
|
* flen - Number of bytes for AX.25 frame.
|
||||||
|
* or -1 for a text string.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Description: Send message to client if connected.
|
||||||
|
* Disconnect from client, and notify user, if any error.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
void kissnet_send_rec_packet (int chan, unsigned char *fbuf, int flen)
|
||||||
|
{
|
||||||
|
unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN];
|
||||||
|
int kiss_len;
|
||||||
|
int j;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
|
||||||
|
if (client_sock == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (flen < 0) {
|
||||||
|
flen = strlen((char*)fbuf);
|
||||||
|
if (kiss_debug) {
|
||||||
|
kiss_debug_print (TO_CLIENT, "Fake command prompt", fbuf, flen);
|
||||||
|
}
|
||||||
|
strcpy ((char *)kiss_buff, (char *)fbuf);
|
||||||
|
kiss_len = strlen((char *)kiss_buff);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
kiss_len = 0;
|
||||||
|
kiss_buff[kiss_len++] = FEND;
|
||||||
|
kiss_buff[kiss_len++] = chan << 4;
|
||||||
|
|
||||||
|
for (j=0; j<flen; j++) {
|
||||||
|
|
||||||
|
if (fbuf[j] == FEND) {
|
||||||
|
kiss_buff[kiss_len++] = FESC;
|
||||||
|
kiss_buff[kiss_len++] = TFEND;
|
||||||
|
}
|
||||||
|
else if (fbuf[j] == FESC) {
|
||||||
|
kiss_buff[kiss_len++] = FESC;
|
||||||
|
kiss_buff[kiss_len++] = TFESC;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
kiss_buff[kiss_len++] = fbuf[j];
|
||||||
|
}
|
||||||
|
assert (kiss_len < sizeof (kiss_buff));
|
||||||
|
}
|
||||||
|
kiss_buff[kiss_len++] = FEND;
|
||||||
|
|
||||||
|
/* Bug: This has the escapes but not the surrounding FENDs. */
|
||||||
|
|
||||||
|
if (kiss_debug) {
|
||||||
|
kiss_debug_print (TO_CLIENT, NULL, kiss_buff+1, kiss_len-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
err = send (client_sock, (char*)kiss_buff, kiss_len, 0);
|
||||||
|
if (err == SOCKET_ERROR)
|
||||||
|
{
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("\nError %d sending message to KISS client application. Closing connection.\n\n", WSAGetLastError());
|
||||||
|
closesocket (client_sock);
|
||||||
|
client_sock = -1;
|
||||||
|
WSACleanup();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
err = write (client_sock, kiss_buff, kiss_len);
|
||||||
|
if (err <= 0)
|
||||||
|
{
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("\nError sending message to KISS client application. Closing connection.\n\n");
|
||||||
|
close (client_sock);
|
||||||
|
client_sock = -1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} /* end kissnet_send_rec_packet */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: read_from_socket
|
||||||
|
*
|
||||||
|
* Purpose: Read from socket until we have desired number of bytes.
|
||||||
|
*
|
||||||
|
* Inputs: fd - file descriptor.
|
||||||
|
* ptr - address where data should be placed.
|
||||||
|
* len - desired number of bytes.
|
||||||
|
*
|
||||||
|
* Description: Just a wrapper for the "read" system call but it should
|
||||||
|
* never return fewer than the desired number of bytes.
|
||||||
|
*
|
||||||
|
* Not really needed for KISS because we are dealing with
|
||||||
|
* a stream of bytes rather than message blocks.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
static int read_from_socket (int fd, char *ptr, int len)
|
||||||
|
{
|
||||||
|
int got_bytes = 0;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("read_from_socket (%d, %p, %d)\n", fd, ptr, len);
|
||||||
|
#endif
|
||||||
|
while (got_bytes < len) {
|
||||||
|
int n;
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
|
||||||
|
//TODO: any flags for send/recv?
|
||||||
|
|
||||||
|
n = recv (fd, ptr + got_bytes, len - got_bytes, 0);
|
||||||
|
#else
|
||||||
|
n = read (fd, ptr + got_bytes, len - got_bytes);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("read_from_socket: n = %d\n", n);
|
||||||
|
#endif
|
||||||
|
if (n <= 0) {
|
||||||
|
return (n);
|
||||||
|
}
|
||||||
|
|
||||||
|
got_bytes += n;
|
||||||
|
}
|
||||||
|
assert (got_bytes >= 0 && got_bytes <= len);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("read_from_socket: return %d\n", got_bytes);
|
||||||
|
#endif
|
||||||
|
return (got_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: kissnet_listen_thread
|
||||||
|
*
|
||||||
|
* Purpose: Wait for KISS messages from an application.
|
||||||
|
*
|
||||||
|
* Inputs: arg - Not used.
|
||||||
|
*
|
||||||
|
* Outputs: client_sock - File descriptor for communicating with client app.
|
||||||
|
*
|
||||||
|
* Description: Process messages from the client application.
|
||||||
|
* Note that the client can go away and come back again and
|
||||||
|
* re-establish communication without restarting this application.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
/* Return one byte (value 0 - 255) */
|
||||||
|
|
||||||
|
|
||||||
|
static int kiss_get (void)
|
||||||
|
{
|
||||||
|
unsigned char ch;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
|
||||||
|
while (client_sock <= 0) {
|
||||||
|
SLEEP_SEC(1); /* Not connected. Try again later. */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Just get one byte at a time. */
|
||||||
|
|
||||||
|
n = read_from_socket (client_sock, (char *)(&ch), 1);
|
||||||
|
|
||||||
|
if (n == 1) {
|
||||||
|
#if DEBUG9
|
||||||
|
dw_printf (log_fp, "%02x %c %c", ch,
|
||||||
|
isprint(ch) ? ch : '.' ,
|
||||||
|
(isupper(ch>>1) || isdigit(ch>>1) || (ch>>1) == ' ') ? (ch>>1) : '.');
|
||||||
|
if (ch == FEND) fprintf (log_fp, " FEND");
|
||||||
|
if (ch == FESC) fprintf (log_fp, " FESC");
|
||||||
|
if (ch == TFEND) fprintf (log_fp, " TFEND");
|
||||||
|
if (ch == TFESC) fprintf (log_fp, " TFESC");
|
||||||
|
if (ch == '\r') fprintf (log_fp, " CR");
|
||||||
|
if (ch == '\n') fprintf (log_fp, " LF");
|
||||||
|
fprintf (log_fp, "\n");
|
||||||
|
if (ch == FEND) fflush (log_fp);
|
||||||
|
#endif
|
||||||
|
return(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("\nError reading KISS byte from clent application. Closing connection.\n\n");
|
||||||
|
#if __WIN32__
|
||||||
|
closesocket (client_sock);
|
||||||
|
#else
|
||||||
|
close (client_sock);
|
||||||
|
#endif
|
||||||
|
client_sock = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static void * kissnet_listen_thread (void *arg)
|
||||||
|
{
|
||||||
|
unsigned char ch;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("kissnet_listen_thread ( socket = %d )\n", client_sock);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
ch = kiss_get();
|
||||||
|
|
||||||
|
if (kiss_frame (&kf, ch, kiss_debug, kissnet_send_rec_packet)) {
|
||||||
|
kiss_process_msg (&kf, kiss_debug);
|
||||||
|
}
|
||||||
|
} /* while (1) */
|
||||||
|
|
||||||
|
return (NULL); /* to suppress compiler warning. */
|
||||||
|
|
||||||
|
} /* end kissnet_listen_thread */
|
||||||
|
|
||||||
|
/* end kissnet.c */
|
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Name: kissnet.h
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "ax25_pad.h" /* for packet_t */
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void kissnet_init (struct misc_config_s *misc_config);
|
||||||
|
|
||||||
|
void kissnet_send_rec_packet (int chan, unsigned char *fbuf, int flen);
|
||||||
|
|
||||||
|
void kiss_net_set_debug (int n);
|
||||||
|
|
||||||
|
|
||||||
|
/* end kissnet.h */
|
|
@ -0,0 +1,281 @@
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Module: latlong.c
|
||||||
|
*
|
||||||
|
* Purpose: Various functions for dealing with latitude and longitude.
|
||||||
|
*
|
||||||
|
* Description: Originally, these were scattered around in many places.
|
||||||
|
* Over time they might all be gathered into one place
|
||||||
|
* for consistency, reuse, and easier maintenance.
|
||||||
|
*
|
||||||
|
*---------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "latlong.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: latitude_to_str
|
||||||
|
*
|
||||||
|
* Purpose: Convert numeric latitude to string for transmission.
|
||||||
|
*
|
||||||
|
* Inputs: dlat - Floating point degrees.
|
||||||
|
* ambiguity - If 1, 2, 3, or 4, blank out that many trailing digits.
|
||||||
|
*
|
||||||
|
* Outputs: slat - String in format ddmm.mm[NS]
|
||||||
|
*
|
||||||
|
* Returns: None
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
void latitude_to_str (double dlat, int ambiguity, char *slat)
|
||||||
|
{
|
||||||
|
char hemi; /* Hemisphere: N or S */
|
||||||
|
int ideg; /* whole number of degrees. */
|
||||||
|
double dmin; /* Minutes after removing degrees. */
|
||||||
|
char smin[8]; /* Minutes in format mm.mm */
|
||||||
|
|
||||||
|
if (dlat < -90.) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Latitude is less than -90. Changing to -90.n");
|
||||||
|
dlat = -90.;
|
||||||
|
}
|
||||||
|
if (dlat > 90.) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Latitude is greater than 90. Changing to 90.n");
|
||||||
|
dlat = 90.;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dlat < 0) {
|
||||||
|
dlat = (- dlat);
|
||||||
|
hemi = 'S';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
hemi = 'N';
|
||||||
|
}
|
||||||
|
|
||||||
|
ideg = (int)dlat;
|
||||||
|
dmin = (dlat - ideg) * 60.;
|
||||||
|
|
||||||
|
sprintf (smin, "%05.2f", dmin);
|
||||||
|
/* Due to roundoff, 59.9999 could come out as "60.00" */
|
||||||
|
if (smin[0] == '6') {
|
||||||
|
smin[0] = '0';
|
||||||
|
ideg++;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf (slat, "%02d%s%c", ideg, smin, hemi);
|
||||||
|
|
||||||
|
if (ambiguity >= 1) {
|
||||||
|
slat[6] = ' ';
|
||||||
|
if (ambiguity >= 2) {
|
||||||
|
slat[5] = ' ';
|
||||||
|
if (ambiguity >= 3) {
|
||||||
|
slat[3] = ' ';
|
||||||
|
if (ambiguity >= 4) {
|
||||||
|
slat[2] = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* end latitude_to_str */
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: longitude_to_str
|
||||||
|
*
|
||||||
|
* Purpose: Convert numeric longitude to string for transmission.
|
||||||
|
*
|
||||||
|
* Inputs: dlong - Floating point degrees.
|
||||||
|
* ambiguity - If 1, 2, 3, or 4, blank out that many trailing digits.
|
||||||
|
*
|
||||||
|
* Outputs: slat - String in format dddmm.mm[NS]
|
||||||
|
*
|
||||||
|
* Returns: None
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
void longitude_to_str (double dlong, int ambiguity, char *slong)
|
||||||
|
{
|
||||||
|
char hemi; /* Hemisphere: N or S */
|
||||||
|
int ideg; /* whole number of degrees. */
|
||||||
|
double dmin; /* Minutes after removing degrees. */
|
||||||
|
char smin[8]; /* Minutes in format mm.mm */
|
||||||
|
|
||||||
|
if (dlong < -180.) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Longitude is less than -180. Changing to -180.n");
|
||||||
|
dlong = -180.;
|
||||||
|
}
|
||||||
|
if (dlong > 180.) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Longitude is greater than 180. Changing to 180.n");
|
||||||
|
dlong = 180.;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dlong < 0) {
|
||||||
|
dlong = (- dlong);
|
||||||
|
hemi = 'W';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
hemi = 'E';
|
||||||
|
}
|
||||||
|
|
||||||
|
ideg = (int)dlong;
|
||||||
|
dmin = (dlong - ideg) * 60.;
|
||||||
|
|
||||||
|
sprintf (smin, "%05.2f", dmin);
|
||||||
|
/* Due to roundoff, 59.9999 could come out as "60.00" */
|
||||||
|
if (smin[0] == '6') {
|
||||||
|
smin[0] = '0';
|
||||||
|
ideg++;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf (slong, "%03d%s%c", ideg, smin, hemi);
|
||||||
|
/*
|
||||||
|
* The spec says position ambiguity in latitude also
|
||||||
|
* applies to longitude automatically.
|
||||||
|
* Blanking longitude digits is not necessary but I do it
|
||||||
|
* because it makes things clearer.
|
||||||
|
*/
|
||||||
|
if (ambiguity >= 1) {
|
||||||
|
slong[7] = ' ';
|
||||||
|
if (ambiguity >= 2) {
|
||||||
|
slong[6] = ' ';
|
||||||
|
if (ambiguity >= 3) {
|
||||||
|
slong[4] = ' ';
|
||||||
|
if (ambiguity >= 4) {
|
||||||
|
slong[3] = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* end longitude_to_str */
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: latitude_to_comp_str
|
||||||
|
*
|
||||||
|
* Purpose: Convert numeric latitude to compressed string for transmission.
|
||||||
|
*
|
||||||
|
* Inputs: dlat - Floating point degrees.
|
||||||
|
*
|
||||||
|
* Outputs: slat - String in format yyyy.
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
void latitude_to_comp_str (double dlat, char *clat)
|
||||||
|
{
|
||||||
|
int y, y0, y1, y2, y3;
|
||||||
|
|
||||||
|
if (dlat < -90.) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Latitude is less than -90. Changing to -90.n");
|
||||||
|
dlat = -90.;
|
||||||
|
}
|
||||||
|
if (dlat > 90.) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Latitude is greater than 90. Changing to 90.n");
|
||||||
|
dlat = 90.;
|
||||||
|
}
|
||||||
|
|
||||||
|
y = (int)round(380926. * (90. - dlat));
|
||||||
|
|
||||||
|
y0 = y / (91*91*91);
|
||||||
|
y -= y0 * (91*91*91);
|
||||||
|
|
||||||
|
y1 = y / (91*91);
|
||||||
|
y -= y1 * (91*91);
|
||||||
|
|
||||||
|
y2 = y / (91);
|
||||||
|
y -= y2 * (91);
|
||||||
|
|
||||||
|
y3 = y;
|
||||||
|
|
||||||
|
clat[0] = y0 + 33;
|
||||||
|
clat[1] = y1 + 33;
|
||||||
|
clat[2] = y2 + 33;
|
||||||
|
clat[3] = y3 + 33;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: longitude_to_comp_str
|
||||||
|
*
|
||||||
|
* Purpose: Convert numeric longitude to compressed string for transmission.
|
||||||
|
*
|
||||||
|
* Inputs: dlong - Floating point degrees.
|
||||||
|
*
|
||||||
|
* Outputs: slat - String in format xxxx.
|
||||||
|
*
|
||||||
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
|
void longitude_to_comp_str (double dlong, char *clon)
|
||||||
|
{
|
||||||
|
int x, x0, x1, x2, x3;
|
||||||
|
|
||||||
|
if (dlong < -180.) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Longitude is less than -180. Changing to -180.n");
|
||||||
|
dlong = -180.;
|
||||||
|
}
|
||||||
|
if (dlong > 180.) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Longitude is greater than 180. Changing to 180.n");
|
||||||
|
dlong = 180.;
|
||||||
|
}
|
||||||
|
|
||||||
|
x = (int)round(190463. * (180. + dlong));
|
||||||
|
|
||||||
|
x0 = x / (91*91*91);
|
||||||
|
x -= x0 * (91*91*91);
|
||||||
|
|
||||||
|
x1 = x / (91*91);
|
||||||
|
x -= x1 * (91*91);
|
||||||
|
|
||||||
|
x2 = x / (91);
|
||||||
|
x -= x2 * (91);
|
||||||
|
|
||||||
|
x3 = x;
|
||||||
|
|
||||||
|
clon[0] = x0 + 33;
|
||||||
|
clon[1] = x1 + 33;
|
||||||
|
clon[2] = x2 + 33;
|
||||||
|
clon[3] = x3 + 33;
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
/* latlong.h */
|
||||||
|
|
||||||
|
|
||||||
|
/* Use this value for unknown latitude/longitude or other values. */
|
||||||
|
|
||||||
|
#define G_UNKNOWN (-999999)
|
||||||
|
|
||||||
|
|
||||||
|
void latitude_to_str (double dlat, int ambiguity, char *slat);
|
||||||
|
void longitude_to_str (double dlong, int ambiguity, char *slong);
|
||||||
|
void latitude_to_comp_str (double dlat, char *clat);
|
||||||
|
void longitude_to_comp_str (double dlon, char *clon);
|
|
@ -0,0 +1,55 @@
|
||||||
|
/* Latitude / Longitude to UTM conversion */
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "LatLong-UTMconversion.h"
|
||||||
|
|
||||||
|
|
||||||
|
static void usage();
|
||||||
|
|
||||||
|
|
||||||
|
void main (int argc, char *argv[])
|
||||||
|
{
|
||||||
|
double easting;
|
||||||
|
double northing;
|
||||||
|
double lat, lon;
|
||||||
|
char zone[8];
|
||||||
|
|
||||||
|
if (argc != 3) usage();
|
||||||
|
|
||||||
|
|
||||||
|
lat = atof(argv[1]);
|
||||||
|
if (lat < -90 || lat > 90) {
|
||||||
|
fprintf (stderr, "Latitude value is out of range.\n\n");
|
||||||
|
usage();
|
||||||
|
}
|
||||||
|
|
||||||
|
lon = atof(argv[2]);
|
||||||
|
if (lon < -180 || lon > 180) {
|
||||||
|
fprintf (stderr, "Longitude value is out of range.\n\n");
|
||||||
|
usage();
|
||||||
|
}
|
||||||
|
|
||||||
|
LLtoUTM (WSG84, lat, lon, &northing, &easting, zone);
|
||||||
|
|
||||||
|
printf ("zone = %s, easting = %.0f, northing = %.0f\n", zone, easting, northing);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void usage (void)
|
||||||
|
{
|
||||||
|
fprintf (stderr, "Latitude / Longitude to UTM conversion\n");
|
||||||
|
fprintf (stderr, "\n");
|
||||||
|
fprintf (stderr, "Usage:\n");
|
||||||
|
fprintf (stderr, "\tll2utm latitude longitude\n");
|
||||||
|
fprintf (stderr, "\n");
|
||||||
|
fprintf (stderr, "where,\n");
|
||||||
|
fprintf (stderr, "\tLatitude and longitude are in decimal degrees.\n");
|
||||||
|
fprintf (stderr, "\t Use negative for south or west.\n");
|
||||||
|
fprintf (stderr, "\n");
|
||||||
|
fprintf (stderr, "Example:\n");
|
||||||
|
fprintf (stderr, "\tll2utm 42.662139 -71.365553\n");
|
||||||
|
|
||||||
|
exit (1);
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
These are part of the standard C library for Linux and Cygwin.
|
||||||
|
For the Windows version we need to include our own copy.
|
||||||
|
|
||||||
|
They were copied from Cygwin source:
|
||||||
|
|
||||||
|
/usr/src/cygwin-1.7.10-1/newlib/libc/string/strsep.c
|
||||||
|
/usr/src/cygwin-1.7.10-1/newlib/libc/string/strtok_r.c
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*-
|
||||||
|
* Copyright (c) 1990, 1993
|
||||||
|
* The Regents of the University of California. All rights reserved.
|
||||||
|
*
|
||||||
|
* This code is derived from software contributed to Berkeley by
|
||||||
|
* Chris Torek.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* 3. All advertising materials mentioning features or use of this software
|
||||||
|
* must display the following acknowledgement:
|
||||||
|
* This product includes software developed by the University of
|
||||||
|
* California, Berkeley and its contributors.
|
||||||
|
* 4. Neither the name of the University nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software
|
||||||
|
* without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
* SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//#include <sys/cdefs.h>
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find the first occurrence of find in s, ignore case.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
strcasestr(s, find)
|
||||||
|
const char *s, *find;
|
||||||
|
{
|
||||||
|
char c, sc;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
if ((c = *find++) != 0) {
|
||||||
|
c = tolower((unsigned char)c);
|
||||||
|
len = strlen(find);
|
||||||
|
do {
|
||||||
|
do {
|
||||||
|
if ((sc = *s++) == 0)
|
||||||
|
return (NULL);
|
||||||
|
} while ((char)tolower((unsigned char)sc) != c);
|
||||||
|
} while (strncasecmp(s, find, len) != 0);
|
||||||
|
s--;
|
||||||
|
}
|
||||||
|
return ((char *)s);
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
/* BSD strsep function */
|
||||||
|
|
||||||
|
/* Copyright 2002, Red Hat Inc. */
|
||||||
|
|
||||||
|
/* undef STRICT_ANSI so that strsep prototype will be defined */
|
||||||
|
#undef __STRICT_ANSI__
|
||||||
|
#include <string.h>
|
||||||
|
//#include <_ansi.h>
|
||||||
|
//#include <reent.h>
|
||||||
|
|
||||||
|
#define _DEFUN(name,arglist,args) name(args)
|
||||||
|
#define _AND ,
|
||||||
|
|
||||||
|
extern char *__strtok_r (char *, const char *, char **, int);
|
||||||
|
|
||||||
|
char *
|
||||||
|
_DEFUN (strsep, (source_ptr, delim),
|
||||||
|
register char **source_ptr _AND
|
||||||
|
register const char *delim)
|
||||||
|
{
|
||||||
|
return __strtok_r (*source_ptr, delim, source_ptr, 0);
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 1988 Regents of the University of California.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of the University nor the names of its contributors
|
||||||
|
* may be used to endorse or promote products derived from this software
|
||||||
|
* without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
* SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define _DEFUN(name,arglist,args) name(args)
|
||||||
|
#define _AND ,
|
||||||
|
|
||||||
|
char *
|
||||||
|
_DEFUN (__strtok_r, (s, delim, lasts, skip_leading_delim),
|
||||||
|
register char *s _AND
|
||||||
|
register const char *delim _AND
|
||||||
|
char **lasts _AND
|
||||||
|
int skip_leading_delim)
|
||||||
|
{
|
||||||
|
register char *spanp;
|
||||||
|
register int c, sc;
|
||||||
|
char *tok;
|
||||||
|
|
||||||
|
|
||||||
|
if (s == NULL && (s = *lasts) == NULL)
|
||||||
|
return (NULL);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Skip (span) leading delimiters (s += strspn(s, delim), sort of).
|
||||||
|
*/
|
||||||
|
cont:
|
||||||
|
c = *s++;
|
||||||
|
for (spanp = (char *)delim; (sc = *spanp++) != 0;) {
|
||||||
|
if (c == sc) {
|
||||||
|
if (skip_leading_delim) {
|
||||||
|
goto cont;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
*lasts = s;
|
||||||
|
s[-1] = 0;
|
||||||
|
return (s - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == 0) { /* no non-delimiter characters */
|
||||||
|
*lasts = NULL;
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
tok = s - 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
|
||||||
|
* Note that delim must have one NUL; we stop if we see that, too.
|
||||||
|
*/
|
||||||
|
for (;;) {
|
||||||
|
c = *s++;
|
||||||
|
spanp = (char *)delim;
|
||||||
|
do {
|
||||||
|
if ((sc = *spanp++) == c) {
|
||||||
|
if (c == 0)
|
||||||
|
s = NULL;
|
||||||
|
else
|
||||||
|
s[-1] = 0;
|
||||||
|
*lasts = s;
|
||||||
|
return (tok);
|
||||||
|
}
|
||||||
|
} while (sc != 0);
|
||||||
|
}
|
||||||
|
/* NOTREACHED */
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
_DEFUN (strtok_r, (s, delim, lasts),
|
||||||
|
register char *s _AND
|
||||||
|
register const char *delim _AND
|
||||||
|
char **lasts)
|
||||||
|
{
|
||||||
|
return __strtok_r (s, delim, lasts, 1);
|
||||||
|
}
|
|
@ -0,0 +1,381 @@
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Module: morse.c
|
||||||
|
*
|
||||||
|
* Purpose: Generate audio for morse code.
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
*
|
||||||
|
* Reference:
|
||||||
|
*
|
||||||
|
*---------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <sys/termios.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
#include "audio.h"
|
||||||
|
#include "ptt.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define WPM 10
|
||||||
|
#define TIME_UNITS_TO_MS(tu,wpm) (((tu)*1200)/(wpm))
|
||||||
|
|
||||||
|
|
||||||
|
// TODO : should be in .h file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Delay from PTT on to start of first character.
|
||||||
|
* Currently the only anticipated use for this is
|
||||||
|
* APRStt responses. In this case, we want an adequate
|
||||||
|
* delay for someone to press the # button, release
|
||||||
|
* the PTT button, and start listening for a response.
|
||||||
|
*/
|
||||||
|
#define MORSE_TXDELAY_MS 1500
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Delay from end of last character to PTT off.
|
||||||
|
* Avoid chopping off the last element.
|
||||||
|
*/
|
||||||
|
#define MORSE_TXTAIL_MS 200
|
||||||
|
|
||||||
|
|
||||||
|
static const struct morse_s {
|
||||||
|
char ch;
|
||||||
|
char enc[7];
|
||||||
|
} morse[] = {
|
||||||
|
{ 'A', ".-" },
|
||||||
|
{ 'B', "-..." },
|
||||||
|
{ 'C', "-.-." },
|
||||||
|
{ 'D', "-.." },
|
||||||
|
{ 'E', "." },
|
||||||
|
{ 'F', "..-." },
|
||||||
|
{ 'G', "--." },
|
||||||
|
{ 'H', "...." },
|
||||||
|
{ 'I', "." },
|
||||||
|
{ 'J', ".---" },
|
||||||
|
{ 'K', "-.-" },
|
||||||
|
{ 'L', ".-.." },
|
||||||
|
{ 'M', "--" },
|
||||||
|
{ 'N', "-." },
|
||||||
|
{ 'O', "---" },
|
||||||
|
{ 'P', ".--." },
|
||||||
|
{ 'Q', "--.-" },
|
||||||
|
{ 'R', ".-." },
|
||||||
|
{ 'S', "..." },
|
||||||
|
{ 'T', "-" },
|
||||||
|
{ 'U', "..-" },
|
||||||
|
{ 'V', "...-" },
|
||||||
|
{ 'W', ".--" },
|
||||||
|
{ 'X', "-..-" },
|
||||||
|
{ 'Y', "-.--" },
|
||||||
|
{ 'Z', "--.." },
|
||||||
|
{ '1', ".----" },
|
||||||
|
{ '2', "..---" },
|
||||||
|
{ '3', "...--" },
|
||||||
|
{ '4', "....-" },
|
||||||
|
{ '5', "....." },
|
||||||
|
{ '6', "-...." },
|
||||||
|
{ '7', "--..." },
|
||||||
|
{ '8', "---.." },
|
||||||
|
{ '9', "----." },
|
||||||
|
{ '0', "-----" },
|
||||||
|
{ '-', "-...-" },
|
||||||
|
{ '.', ".-.-.-" },
|
||||||
|
{ ',', "--..--" },
|
||||||
|
{ '?', "..--.." },
|
||||||
|
{ '/', "-..-." }
|
||||||
|
};
|
||||||
|
|
||||||
|
#define NUM_MORSE (sizeof(morse) / sizeof(struct morse_s))
|
||||||
|
|
||||||
|
static void morse_tone (int tu);
|
||||||
|
static void morse_quiet (int tu);
|
||||||
|
static int morse_lookup (int ch);
|
||||||
|
static int morse_units_ch (int ch);
|
||||||
|
static int morse_units_str (char *str);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: morse_send
|
||||||
|
*
|
||||||
|
* Purpose: Given a string, generate appropriate lengths of
|
||||||
|
* tone and silence.
|
||||||
|
*
|
||||||
|
* Inputs: chan - Radio channel number.
|
||||||
|
* str - Character string to send.
|
||||||
|
* wpm - Speed in words per minute.
|
||||||
|
* txdelay - Delay (ms) from PTT to first character.
|
||||||
|
* txtail - Delay (ms) from last character to PTT off.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Returns: Total number of milliseconds to activate PTT.
|
||||||
|
* This includes delays before the first character
|
||||||
|
* and after the last to avoid chopping off part of it.
|
||||||
|
*
|
||||||
|
* Description: xmit_thread calls this instead of the usual hdlc_send
|
||||||
|
* when we have a special packet that means send morse
|
||||||
|
* code.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
int morse_send (int chan, char *str, int wpm, int txdelay, int txtail)
|
||||||
|
{
|
||||||
|
int time_units;
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
time_units = 0;
|
||||||
|
for (p = str; *p != '\0'; p++) {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
i = morse_lookup (*p);
|
||||||
|
if (i >= 0) {
|
||||||
|
const char *e;
|
||||||
|
|
||||||
|
for (e = morse[i].enc; *e != '\0'; e++) {
|
||||||
|
if (*e == '.') {
|
||||||
|
morse_tone (1);
|
||||||
|
time_units++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
morse_tone (3);
|
||||||
|
time_units += 3;
|
||||||
|
}
|
||||||
|
if (e[1] != '\0') {
|
||||||
|
morse_quiet (1);
|
||||||
|
time_units++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
morse_quiet (1);
|
||||||
|
time_units++;
|
||||||
|
}
|
||||||
|
if (p[1] != '\0') {
|
||||||
|
morse_quiet (3);
|
||||||
|
time_units += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time_units != morse_units_str(str)) {
|
||||||
|
dw_printf ("morse: Internal error. Inconsistent length, %d vs. %d calculated.\n",
|
||||||
|
time_units, morse_units_str(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (txdelay +
|
||||||
|
TIME_UNITS_TO_MS(time_units, wpm) +
|
||||||
|
txtail);
|
||||||
|
|
||||||
|
} /* end morse_send */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: morse_tone
|
||||||
|
*
|
||||||
|
* Purpose: Generate tone for specified number of time units.
|
||||||
|
*
|
||||||
|
* Inputs: tu - Number of time units.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
static void morse_tone (int tu) {
|
||||||
|
int num_cycles;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
for (n=0; n<tu; n++) {
|
||||||
|
dw_printf ("#");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} /* end morse_tone */
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: morse_quiet
|
||||||
|
*
|
||||||
|
* Purpose: Generate silence for specified number of time units.
|
||||||
|
*
|
||||||
|
* Inputs: tu - Number of time units.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
static void morse_quiet (int tu) {
|
||||||
|
int n;
|
||||||
|
|
||||||
|
for (n=0; n<tu; n++) {
|
||||||
|
dw_printf (".");
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* end morse_quiet */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: morse_lookup
|
||||||
|
*
|
||||||
|
* Purpose: Given a character, find index in table above.
|
||||||
|
*
|
||||||
|
* Inputs: ch
|
||||||
|
*
|
||||||
|
* Returns: Index into table above or -1 if not found.
|
||||||
|
* Notice that space is not in the table.
|
||||||
|
* Any unusual character, that is not in the table,
|
||||||
|
* ends up being treated like space.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
static int morse_lookup (int ch)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (islower(ch)) {
|
||||||
|
ch = toupper(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i=0; i<NUM_MORSE; i++) {
|
||||||
|
if (ch == morse[i].ch) {
|
||||||
|
return (i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: morse_units_ch
|
||||||
|
*
|
||||||
|
* Purpose: Find number of time units for a character.
|
||||||
|
*
|
||||||
|
* Inputs: ch
|
||||||
|
*
|
||||||
|
* Returns: 1 for E (.)
|
||||||
|
* 3 for T (-)
|
||||||
|
* 3 for I.= (..)
|
||||||
|
* etc.
|
||||||
|
*
|
||||||
|
* The one unexpected result is 1 for space. Why not 7?
|
||||||
|
* When a space appears between two other characters,
|
||||||
|
* we already have 3 before and after so only 1 more is needed.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
static int morse_units_ch (int ch)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int len;
|
||||||
|
int k;
|
||||||
|
int units;
|
||||||
|
|
||||||
|
i = morse_lookup (ch);
|
||||||
|
if (i < 0) {
|
||||||
|
return (1); /* space or any invalid character */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
len = strlen(morse[i].enc);
|
||||||
|
units = len - 1;
|
||||||
|
|
||||||
|
for (k = 0; k < len; k++) {
|
||||||
|
switch (morse[i].enc[k]) {
|
||||||
|
case '.': units++; break;
|
||||||
|
case '-': units += 3; break;
|
||||||
|
default: dw_printf ("ERROR: morse_units_ch: should not be here.\n"); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (units);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: morse_units_str
|
||||||
|
*
|
||||||
|
* Purpose: Find number of time units for a string of characters.
|
||||||
|
*
|
||||||
|
* Inputs: str
|
||||||
|
*
|
||||||
|
* Returns: 1 for E
|
||||||
|
* 5 for EE (1 + 3 + 1)
|
||||||
|
* 9 for E E (1 + 7 + 1)
|
||||||
|
* etc.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
static int morse_units_str (char *str)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int len;
|
||||||
|
int k;
|
||||||
|
int units;
|
||||||
|
|
||||||
|
len = strlen(str);
|
||||||
|
units = (len - 1) * 3;
|
||||||
|
|
||||||
|
for (k = 0; k < len; k++) {
|
||||||
|
units += morse_units_ch(str[k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (units);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main (int argc, char *argv[]) {
|
||||||
|
|
||||||
|
dw_printf ("CQ DX\n");
|
||||||
|
morse_send (0, "CQ DX", 10, 10, 10);
|
||||||
|
dw_printf ("\n\n");
|
||||||
|
|
||||||
|
dw_printf ("wb2osz/9\n");
|
||||||
|
morse_send (0, "wb2osz/9", 10, 10, 10);
|
||||||
|
dw_printf ("\n\n");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* end morse.c */
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,484 @@
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: multi_modem.c
|
||||||
|
*
|
||||||
|
* Purpose: Use multiple modems in parallel to increase chances
|
||||||
|
* of decoding less than ideal signals.
|
||||||
|
*
|
||||||
|
* Description: The initial motivation was for HF SSB where mistuning
|
||||||
|
* causes a shift in the audio frequencies. Here, we can
|
||||||
|
* have multiple modems tuned to staggered pairs of tones
|
||||||
|
* in hopes that one will be close enough.
|
||||||
|
*
|
||||||
|
* The overall structure opens the door to other approaches
|
||||||
|
* as well. For VHF FM, the tones should always have the
|
||||||
|
* right frequencies but we might want to tinker with other
|
||||||
|
* modem parameters instead of using a single compromise.
|
||||||
|
*
|
||||||
|
* Originally: The the interface application is in 3 places:
|
||||||
|
*
|
||||||
|
* (a) Main program (direwolf.c or atest.c) calls
|
||||||
|
* demod_init to set up modem properties and
|
||||||
|
* hdlc_rec_init for the HDLC decoders.
|
||||||
|
*
|
||||||
|
* (b) demod_process_sample is called for each audio sample
|
||||||
|
* from the input audio stream.
|
||||||
|
*
|
||||||
|
* (c) When a valid AX.25 frame is found, process_rec_frame,
|
||||||
|
* provided by the application, in direwolf.c or atest.c,
|
||||||
|
* is called. Normally this comes from hdlc_rec.c but
|
||||||
|
* there are a couple other special cases to consider.
|
||||||
|
* It can be called from hdlc_rec2.c if it took a long
|
||||||
|
* time to "fix" corrupted bits. aprs_tt.c constructs
|
||||||
|
* a fake packet when a touch tone message is received.
|
||||||
|
*
|
||||||
|
* New in version 0.9:
|
||||||
|
*
|
||||||
|
* Put an extra layer in between which potentially uses
|
||||||
|
* multiple modems & HDLC decoders per channel. The tricky
|
||||||
|
* part is picking the best one when there is more than one
|
||||||
|
* success and discarding the rest.
|
||||||
|
*
|
||||||
|
*------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#define DIGIPEATER_C
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/unistd.h>
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "ax25_pad.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
#include "multi_modem.h"
|
||||||
|
#include "demod.h"
|
||||||
|
#include "hdlc_rec.h"
|
||||||
|
#include "hdlc_rec2.h"
|
||||||
|
|
||||||
|
|
||||||
|
// Properties of the radio channels.
|
||||||
|
|
||||||
|
static struct audio_s modem;
|
||||||
|
|
||||||
|
|
||||||
|
// Candidates for further processing.
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
|
||||||
|
packet_t packet_p;
|
||||||
|
int alevel;
|
||||||
|
retry_t retries;
|
||||||
|
int age;
|
||||||
|
unsigned int crc;
|
||||||
|
int score;
|
||||||
|
|
||||||
|
} candidate[MAX_CHANS][MAX_SUBCHANS];
|
||||||
|
|
||||||
|
static unsigned int crc_of_last_to_app[MAX_CHANS];
|
||||||
|
|
||||||
|
#define PROCESS_AFTER_BITS 2
|
||||||
|
|
||||||
|
static int process_age[MAX_CHANS];
|
||||||
|
|
||||||
|
static void pick_best_candidate (int chan);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: multi_modem_init
|
||||||
|
*
|
||||||
|
* Purpose: Called at application start up to initialize appropriate
|
||||||
|
* modems and HDLC decoders.
|
||||||
|
*
|
||||||
|
* Input: Modem properties structure as filled in from the configuration file.
|
||||||
|
*
|
||||||
|
* Outputs:
|
||||||
|
*
|
||||||
|
* Description: Called once at application startup time.
|
||||||
|
*
|
||||||
|
*------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
void multi_modem_init (struct audio_s *pmodem)
|
||||||
|
{
|
||||||
|
int chan;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Save parameters for later use.
|
||||||
|
*/
|
||||||
|
memcpy (&modem, pmodem, sizeof(modem));
|
||||||
|
|
||||||
|
memset (candidate, 0, sizeof(candidate));
|
||||||
|
|
||||||
|
demod_init (pmodem);
|
||||||
|
hdlc_rec_init (pmodem);
|
||||||
|
|
||||||
|
for (chan=0; chan<modem.num_channels; chan++) {
|
||||||
|
process_age[chan] = PROCESS_AFTER_BITS * modem.samples_per_sec / modem.baud[chan];
|
||||||
|
crc_of_last_to_app[chan] = 0x12345678;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: multi_modem_process_sample
|
||||||
|
*
|
||||||
|
* Purpose: Feed the sample into the proper modem(s) for the channel.
|
||||||
|
*
|
||||||
|
* Inputs: chan - Radio channel number
|
||||||
|
*
|
||||||
|
* audio_sample
|
||||||
|
*
|
||||||
|
*------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
__attribute__((hot))
|
||||||
|
void multi_modem_process_sample (int chan, int audio_sample)
|
||||||
|
{
|
||||||
|
int subchan;
|
||||||
|
|
||||||
|
for (subchan = 0; subchan < modem.num_subchan[chan]; subchan++) {
|
||||||
|
demod_process_sample(chan, subchan, audio_sample);
|
||||||
|
|
||||||
|
if (candidate[chan][subchan].packet_p != NULL) {
|
||||||
|
candidate[chan][subchan].age++;
|
||||||
|
if (candidate[chan][subchan].age > process_age[chan]) {
|
||||||
|
pick_best_candidate (chan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: multi_modem_process_rec_frame
|
||||||
|
*
|
||||||
|
* Purpose: This is called when we receive a frame with a valid
|
||||||
|
* FCS and acceptable size.
|
||||||
|
*
|
||||||
|
* Inputs: chan - Audio channel number, 0 or 1.
|
||||||
|
* subchan - Which modem/decoder found it.
|
||||||
|
* fbuf - Pointer to first byte in HDLC frame.
|
||||||
|
* flen - Number of bytes excluding the FCS.
|
||||||
|
* alevel - Audio level, range of 0 - 100.
|
||||||
|
* (Special case, use negative to skip
|
||||||
|
* display of audio level line.
|
||||||
|
* Use -2 to indicate DTMF message.)
|
||||||
|
* retries - Level of bit correction used.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Description: Add to list of candidates. Best one will be picked later.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
It gets a little more complicated when we try fixing frames
|
||||||
|
with imperfect CRCs.
|
||||||
|
|
||||||
|
Changing of adjacent bits is quick and done immediately. These
|
||||||
|
all come in at nearly the same time. The processing of two
|
||||||
|
separated bits can take a long time and is handled in the
|
||||||
|
background by another thread. These could come in seconds later.
|
||||||
|
|
||||||
|
We need a way to remove duplicates. I think these are the
|
||||||
|
two cases we need to consider.
|
||||||
|
|
||||||
|
(1) Same result as earlier no error or adjacent bit errors.
|
||||||
|
|
||||||
|
____||||_
|
||||||
|
0.0: ptr=00000000
|
||||||
|
0.1: ptr=00000000
|
||||||
|
0.2: ptr=00000000
|
||||||
|
0.3: ptr=00000000
|
||||||
|
0.4: ptr=009E5540, retry=0, age=295, crc=9458, score=5024
|
||||||
|
0.5: ptr=0082F008, retry=0, age=294, crc=9458, score=5026 ***
|
||||||
|
0.6: ptr=009CE560, retry=0, age=293, crc=9458, score=5026
|
||||||
|
0.7: ptr=009CEE08, retry=0, age=293, crc=9458, score=5024
|
||||||
|
0.8: ptr=00000000
|
||||||
|
|
||||||
|
___._____
|
||||||
|
0.0: ptr=00000000
|
||||||
|
0.1: ptr=00000000
|
||||||
|
0.2: ptr=00000000
|
||||||
|
0.3: ptr=009E5540, retry=4, age=295, crc=9458, score=1000 ***
|
||||||
|
0.4: ptr=00000000
|
||||||
|
0.5: ptr=00000000
|
||||||
|
0.6: ptr=00000000
|
||||||
|
0.7: ptr=00000000
|
||||||
|
0.8: ptr=00000000
|
||||||
|
|
||||||
|
(2) Only results from adjusting two non-adjacent bits.
|
||||||
|
|
||||||
|
|
||||||
|
||||||||_
|
||||||
|
0.0: ptr=022EBA08, retry=0, age=289, crc=5acd, score=5042
|
||||||
|
0.1: ptr=022EA8B8, retry=0, age=290, crc=5acd, score=5048
|
||||||
|
0.2: ptr=022EB160, retry=0, age=290, crc=5acd, score=5052
|
||||||
|
0.3: ptr=05BD0048, retry=0, age=291, crc=5acd, score=5054 ***
|
||||||
|
0.4: ptr=04FE0048, retry=0, age=292, crc=5acd, score=5054
|
||||||
|
0.5: ptr=05E10048, retry=0, age=294, crc=5acd, score=5052
|
||||||
|
0.6: ptr=053D0048, retry=0, age=294, crc=5acd, score=5048
|
||||||
|
0.7: ptr=02375558, retry=0, age=295, crc=5acd, score=5042
|
||||||
|
0.8: ptr=00000000
|
||||||
|
|
||||||
|
_______._
|
||||||
|
0.0: ptr=00000000
|
||||||
|
0.1: ptr=00000000
|
||||||
|
0.2: ptr=00000000
|
||||||
|
0.3: ptr=00000000
|
||||||
|
0.4: ptr=00000000
|
||||||
|
0.5: ptr=00000000
|
||||||
|
0.6: ptr=00000000
|
||||||
|
0.7: ptr=02375558, retry=4, age=295, crc=5fc5, score=1000 ***
|
||||||
|
0.8: ptr=00000000
|
||||||
|
|
||||||
|
________.
|
||||||
|
0.0: ptr=00000000
|
||||||
|
0.1: ptr=00000000
|
||||||
|
0.2: ptr=00000000
|
||||||
|
0.3: ptr=00000000
|
||||||
|
0.4: ptr=00000000
|
||||||
|
0.5: ptr=00000000
|
||||||
|
0.6: ptr=00000000
|
||||||
|
0.7: ptr=00000000
|
||||||
|
0.8: ptr=02375558, retry=4, age=295, crc=5fc5, score=1000 ***
|
||||||
|
|
||||||
|
|
||||||
|
These can both be covered by keepin the last CRC and dropping
|
||||||
|
duplicates. In theory we could get another frame in between with
|
||||||
|
a slow computer so the complete solution would be to remember more
|
||||||
|
than one.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void multi_modem_process_rec_frame (int chan, int subchan, unsigned char *fbuf, int flen, int alevel, retry_t retries)
|
||||||
|
{
|
||||||
|
packet_t pp;
|
||||||
|
|
||||||
|
|
||||||
|
assert (chan >= 0 && chan < MAX_CHANS);
|
||||||
|
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
|
||||||
|
|
||||||
|
pp = ax25_from_frame (fbuf, flen, alevel);
|
||||||
|
|
||||||
|
if (pp == NULL) {
|
||||||
|
return; /* oops! why would it fail? */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If single modem, push it thru and forget about all this foolishness.
|
||||||
|
*/
|
||||||
|
if (modem.num_subchan[chan] == 1) {
|
||||||
|
app_process_rec_packet (chan, subchan, pp, alevel, retries, "");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Special handing for two separated bit errors.
|
||||||
|
* See description earlier.
|
||||||
|
*
|
||||||
|
* Not combined with others to find the best score.
|
||||||
|
* Either pass it along or drop if duplicate.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (retries == RETRY_TWO_SEP) {
|
||||||
|
int mycrc;
|
||||||
|
char spectrum[MAX_SUBCHANS+1];
|
||||||
|
|
||||||
|
memset (spectrum, 0, sizeof(spectrum));
|
||||||
|
memset (spectrum, '_', (size_t)modem.num_subchan[chan]);
|
||||||
|
spectrum[subchan] = '.';
|
||||||
|
|
||||||
|
mycrc = ax25_m_m_crc(pp);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("\n%s\n%d.%d: ptr=%p, retry=%d, age=, crc=%04x, score= \n",
|
||||||
|
spectrum, chan, subchan, pp, (int)retries, mycrc);
|
||||||
|
#endif
|
||||||
|
if (mycrc == crc_of_last_to_app[chan]) {
|
||||||
|
/* Same as last one. Drop it. */
|
||||||
|
ax25_delete (pp);
|
||||||
|
#if DEBUG
|
||||||
|
dw_printf ("Drop duplicate.\n");
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
dw_printf ("Send the best one along.\n");
|
||||||
|
#endif
|
||||||
|
app_process_rec_packet (chan, subchan, pp, alevel, retries, spectrum);
|
||||||
|
crc_of_last_to_app[chan] = mycrc;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Otherwise, save them up for a few bit times so we can pick the best.
|
||||||
|
*/
|
||||||
|
if (candidate[chan][subchan].packet_p != NULL) {
|
||||||
|
/* Oops! Didn't expect it to be there. */
|
||||||
|
ax25_delete (candidate[chan][subchan].packet_p);
|
||||||
|
candidate[chan][subchan].packet_p = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
candidate[chan][subchan].packet_p = pp;
|
||||||
|
candidate[chan][subchan].alevel = alevel;
|
||||||
|
candidate[chan][subchan].retries = retries;
|
||||||
|
candidate[chan][subchan].age = 0;
|
||||||
|
candidate[chan][subchan].crc = ax25_m_m_crc(pp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: pick_best_candidate
|
||||||
|
*
|
||||||
|
* Purpose: This is called when we have one or more candidates
|
||||||
|
* available for a certain amount of time.
|
||||||
|
*
|
||||||
|
* Description: Pick the best one and send it up to the application.
|
||||||
|
* Discard the others.
|
||||||
|
*
|
||||||
|
* Rules: We prefer one received perfectly but will settle for
|
||||||
|
* one where some bits had to be flipped to get a good CRC.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
static void pick_best_candidate (int chan)
|
||||||
|
{
|
||||||
|
int subchan;
|
||||||
|
int best_subchan, best_score;
|
||||||
|
char spectrum[MAX_SUBCHANS+1];
|
||||||
|
int k;
|
||||||
|
|
||||||
|
memset (spectrum, 0, sizeof(spectrum));
|
||||||
|
|
||||||
|
for (subchan = 0; subchan < modem.num_subchan[chan]; subchan++) {
|
||||||
|
|
||||||
|
/* Build the spectrum display. */
|
||||||
|
|
||||||
|
if (candidate[chan][subchan].packet_p == NULL) {
|
||||||
|
spectrum[subchan] = '_';
|
||||||
|
}
|
||||||
|
else if (candidate[chan][subchan].retries == RETRY_NONE) {
|
||||||
|
spectrum[subchan] = '|';
|
||||||
|
}
|
||||||
|
else if (candidate[chan][subchan].retries == RETRY_SINGLE) {
|
||||||
|
spectrum[subchan] = ':';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
spectrum[subchan] = '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Begining score depends on effort to get a valid frame CRC. */
|
||||||
|
|
||||||
|
candidate[chan][subchan].score = 5000 - ((int)candidate[chan][subchan].retries * 1000);
|
||||||
|
|
||||||
|
/* Bump it up slightly if others nearby have the same CRC. */
|
||||||
|
|
||||||
|
for (k = 0; k < modem.num_subchan[chan]; k++) {
|
||||||
|
if (k != subchan && candidate[chan][k].packet_p != NULL) {
|
||||||
|
if (candidate[chan][k].crc == candidate[chan][subchan].crc) {
|
||||||
|
candidate[chan][subchan].score += (MAX_SUBCHANS+1) - abs(subchan-k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
best_subchan = 0;
|
||||||
|
best_score = 0;
|
||||||
|
|
||||||
|
for (subchan = 0; subchan < modem.num_subchan[chan]; subchan++) {
|
||||||
|
if (candidate[chan][subchan].packet_p != NULL) {
|
||||||
|
if (candidate[chan][subchan].score > best_score) {
|
||||||
|
best_score = candidate[chan][subchan].score;
|
||||||
|
best_subchan = subchan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("\n%s\n", spectrum);
|
||||||
|
|
||||||
|
for (subchan = 0; subchan < modem.num_subchan[chan]; subchan++) {
|
||||||
|
|
||||||
|
if (candidate[chan][subchan].packet_p == NULL) {
|
||||||
|
dw_printf ("%d.%d: ptr=%p\n", chan, subchan,
|
||||||
|
candidate[chan][subchan].packet_p);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dw_printf ("%d.%d: ptr=%p, retry=%d, age=%3d, crc=%04x, score=%d %s\n", chan, subchan,
|
||||||
|
candidate[chan][subchan].packet_p,
|
||||||
|
(int)(candidate[chan][subchan].retries),
|
||||||
|
candidate[chan][subchan].age,
|
||||||
|
candidate[chan][subchan].crc,
|
||||||
|
candidate[chan][subchan].score,
|
||||||
|
subchan == best_subchan ? "***" : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* send the best one along.
|
||||||
|
*/
|
||||||
|
app_process_rec_packet (chan, best_subchan,
|
||||||
|
candidate[chan][best_subchan].packet_p,
|
||||||
|
candidate[chan][best_subchan].alevel,
|
||||||
|
(int)(candidate[chan][best_subchan].retries),
|
||||||
|
spectrum);
|
||||||
|
crc_of_last_to_app[chan] = candidate[chan][best_subchan].crc;
|
||||||
|
|
||||||
|
/* Someone else will delete so don't do it below. */
|
||||||
|
candidate[chan][best_subchan].packet_p = NULL;
|
||||||
|
|
||||||
|
/* Clear out in preparation for next time. */
|
||||||
|
|
||||||
|
for (subchan = 0; subchan < modem.num_subchan[chan]; subchan++) {
|
||||||
|
if (candidate[chan][subchan].packet_p != NULL) {
|
||||||
|
ax25_delete (candidate[chan][subchan].packet_p);
|
||||||
|
candidate[chan][subchan].packet_p = NULL;
|
||||||
|
}
|
||||||
|
candidate[chan][subchan].alevel = 0;
|
||||||
|
candidate[chan][subchan].retries = 0;
|
||||||
|
candidate[chan][subchan].age = 0;
|
||||||
|
candidate[chan][subchan].crc = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* end multi_modem.c */
|
|
@ -0,0 +1,20 @@
|
||||||
|
/* multi_modem.h */
|
||||||
|
|
||||||
|
#ifndef MULTI_MODEM_H
|
||||||
|
#define MULTI_MODEM 1
|
||||||
|
|
||||||
|
/* Needed for typedef retry_t. */
|
||||||
|
#include "hdlc_rec2.h"
|
||||||
|
|
||||||
|
/* Needed for struct audio_s */
|
||||||
|
#include "audio.h"
|
||||||
|
|
||||||
|
|
||||||
|
void multi_modem_init (struct audio_s *pmodem);
|
||||||
|
|
||||||
|
void multi_modem_process_sample (int c, int audio_sample);
|
||||||
|
|
||||||
|
void multi_modem_process_rec_frame (int chan, int subchan, unsigned char *fbuf, int flen, int level, retry_t retries);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,690 @@
|
||||||
|
//
|
||||||
|
// 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Module: ptt.c
|
||||||
|
*
|
||||||
|
* Purpose: Activate the push to talk (PTT) signal to turn on transmitter.
|
||||||
|
*
|
||||||
|
* Description: Traditionally this is done with the RTS signal of the serial port.
|
||||||
|
*
|
||||||
|
* If we have two radio channels and only one serial port, DTR
|
||||||
|
* can be used for the second channel.
|
||||||
|
*
|
||||||
|
* If __WIN32__ is defined, we use the Windows interface.
|
||||||
|
* Otherwise we use the unix version suitable for either Cygwin or Linux.
|
||||||
|
*
|
||||||
|
* Version 0.9: Add ability to use GPIO pins on Linux.
|
||||||
|
*
|
||||||
|
* References: http://www.robbayer.com/files/serial-win.pdf
|
||||||
|
*
|
||||||
|
* https://www.kernel.org/doc/Documentation/gpio.txt
|
||||||
|
*
|
||||||
|
*---------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <sys/termios.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
/* So we can have more common code for fd. */
|
||||||
|
typedef int HANDLE;
|
||||||
|
#define INVALID_HANDLE_VALUE (-1)
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
#include "audio.h"
|
||||||
|
#include "ptt.h"
|
||||||
|
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
|
||||||
|
#define RTS_ON(fd) EscapeCommFunction(fd,SETRTS);
|
||||||
|
#define RTS_OFF(fd) EscapeCommFunction(fd,CLRRTS);
|
||||||
|
#define DTR_ON(fd) EscapeCommFunction(fd,SETDTR);
|
||||||
|
#define DTR_OFF(fd) EscapeCommFunction(fd,CLRDTR);
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define RTS_ON(fd) { int stuff; ioctl (fd, TIOCMGET, &stuff); stuff |= TIOCM_RTS; ioctl (fd, TIOCMSET, &stuff); }
|
||||||
|
#define RTS_OFF(fd) { int stuff; ioctl (fd, TIOCMGET, &stuff); stuff &= ~TIOCM_RTS; ioctl (fd, TIOCMSET, &stuff); }
|
||||||
|
#define DTR_ON(fd) { int stuff; ioctl (fd, TIOCMGET, &stuff); stuff |= TIOCM_DTR; ioctl (fd, TIOCMSET, &stuff); }
|
||||||
|
#define DTR_OFF(fd) { int stuff; ioctl (fd, TIOCMGET, &stuff); stuff &= ~TIOCM_DTR; ioctl (fd, TIOCMSET, &stuff); }
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: ptt_init
|
||||||
|
*
|
||||||
|
* Purpose: Open serial port(s) used for PTT signals and set to proper state.
|
||||||
|
*
|
||||||
|
* Inputs: modem - Structure with communication parameters.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Outputs: Remember required information for future use.
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
static int ptt_num_channels;
|
||||||
|
|
||||||
|
static ptt_method_t ptt_method[MAX_CHANS]; /* Method for PTT signal. */
|
||||||
|
/* PTT_METHOD_NONE - not configured. Could be using VOX. */
|
||||||
|
/* PTT_METHOD_SERIAL - serial (com) port. */
|
||||||
|
/* PTT_METHOD_GPIO - general purpose I/O. */
|
||||||
|
|
||||||
|
static char ptt_device[MAX_CHANS][20]; /* Name of serial port device. */
|
||||||
|
/* e.g. COM1 or /dev/ttyS0. */
|
||||||
|
|
||||||
|
static ptt_line_t ptt_line[MAX_CHANS]; /* RTS or DTR when using serial port. */
|
||||||
|
|
||||||
|
static int ptt_gpio[MAX_CHANS]; /* GPIO number. Only used for Linux. */
|
||||||
|
/* Valid only when ptt_method is PTT_METHOD_GPIO. */
|
||||||
|
|
||||||
|
static int ptt_invert[MAX_CHANS]; /* Invert the signal. */
|
||||||
|
/* Normally higher voltage means transmit. */
|
||||||
|
|
||||||
|
static HANDLE ptt_fd[MAX_CHANS]; /* Serial port handle or fd. */
|
||||||
|
/* Could be the same for two channels */
|
||||||
|
/* if using both RTS and DTR. */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void ptt_init (struct audio_s *p_modem)
|
||||||
|
{
|
||||||
|
int ch;
|
||||||
|
HANDLE fd;
|
||||||
|
#if __WIN32__
|
||||||
|
#else
|
||||||
|
int using_gpio;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("ptt_init ( ... )\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* First copy everything from p_modem to local variables
|
||||||
|
* so it is available for later use.
|
||||||
|
*
|
||||||
|
* Maybe all the PTT stuff should have its own structure.
|
||||||
|
*/
|
||||||
|
|
||||||
|
ptt_num_channels = p_modem->num_channels;
|
||||||
|
|
||||||
|
assert (ptt_num_channels >= 1 && ptt_num_channels <= MAX_CHANS);
|
||||||
|
|
||||||
|
for (ch=0; ch<ptt_num_channels; ch++) {
|
||||||
|
|
||||||
|
ptt_method[ch] = p_modem->ptt_method[ch];
|
||||||
|
strcpy (ptt_device[ch], p_modem->ptt_device[ch]);
|
||||||
|
ptt_line[ch] = p_modem->ptt_line[ch];
|
||||||
|
ptt_gpio[ch] = p_modem->ptt_gpio[ch];
|
||||||
|
ptt_invert[ch] = p_modem->ptt_invert[ch];
|
||||||
|
ptt_fd[ch] = INVALID_HANDLE_VALUE;
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("ch=%d, method=%d, device=%s, line=%d, gpio=%d, invert=%d\n",
|
||||||
|
ch,
|
||||||
|
ptt_method[ch],
|
||||||
|
ptt_device[ch],
|
||||||
|
ptt_line[ch],
|
||||||
|
ptt_gpio[ch],
|
||||||
|
ptt_invert[ch]);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up serial ports.
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (ch=0; ch<ptt_num_channels; ch++) {
|
||||||
|
|
||||||
|
if (ptt_method[ch] == PTT_METHOD_SERIAL) {
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
#else
|
||||||
|
/* Translate Windows device name into Linux name. */
|
||||||
|
/* COM1 -> /dev/ttyS0, etc. */
|
||||||
|
|
||||||
|
if (strncasecmp(ptt_device[ch], "COM", 3) == 0) {
|
||||||
|
int n = atoi (ptt_device[ch] + 3);
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("Converted PTT device '%s'", ptt_device[ch]);
|
||||||
|
if (n < 1) n = 1;
|
||||||
|
sprintf (ptt_device[ch], "/dev/ttyS%d", n-1);
|
||||||
|
dw_printf (" to Linux equivalent '%s'\n", ptt_device[ch]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
/* Can't open the same device more than once so we */
|
||||||
|
/* need more logic to look for the case of both radio */
|
||||||
|
/* channels using different pins of the same COM port. */
|
||||||
|
|
||||||
|
/* TODO: Needs to be rewritten in a more general manner */
|
||||||
|
/* if we ever have more than 2 channels. */
|
||||||
|
|
||||||
|
if (ch == 1 && strcmp(ptt_device[0],ptt_device[1]) == 0) {
|
||||||
|
fd = ptt_fd[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
#if __WIN32__
|
||||||
|
|
||||||
|
fd = CreateFile(ptt_device[ch],
|
||||||
|
GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
|
||||||
|
#else
|
||||||
|
|
||||||
|
/* O_NONBLOCK added in version 0.9. */
|
||||||
|
/* https://bugs.launchpad.net/ubuntu/+source/linux/+bug/661321/comments/12 */
|
||||||
|
|
||||||
|
fd = open (ptt_device[ch], O_RDONLY | O_NONBLOCK);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fd != INVALID_HANDLE_VALUE) {
|
||||||
|
ptt_fd[ch] = fd;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
#if __WIN32__
|
||||||
|
#else
|
||||||
|
int e = errno;
|
||||||
|
#endif
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("ERROR can't open device %s for channel %d PTT control.\n",
|
||||||
|
ptt_device[ch], ch);
|
||||||
|
#if __WIN32__
|
||||||
|
#else
|
||||||
|
dw_printf ("%s\n", strerror(errno));
|
||||||
|
#endif
|
||||||
|
/* Don't try using it later if device open failed. */
|
||||||
|
|
||||||
|
ptt_method[ch] = PTT_METHOD_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set initial state of PTT off.
|
||||||
|
* ptt_set will invert output signal if appropriate.
|
||||||
|
*/
|
||||||
|
ptt_set (ch, 0);
|
||||||
|
|
||||||
|
} /* if serial method. */
|
||||||
|
|
||||||
|
} /* For each channel. */
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set up GPIO - for Linux only.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
#else
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Does any channel use GPIO?
|
||||||
|
*/
|
||||||
|
|
||||||
|
using_gpio = 0;
|
||||||
|
for (ch=0; ch<ptt_num_channels; ch++) {
|
||||||
|
if (ptt_method[ch] == PTT_METHOD_GPIO) {
|
||||||
|
using_gpio = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (using_gpio) {
|
||||||
|
|
||||||
|
struct stat finfo;
|
||||||
|
/*
|
||||||
|
* Normally the device nodes are set up for access
|
||||||
|
* only by root. Try to change it here so we don't
|
||||||
|
* burden user with another configuration step.
|
||||||
|
*
|
||||||
|
* Does /sys/class/gpio/export even exist?
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (stat("/sys/class/gpio/export", &finfo) < 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("This system is not configured with the GPIO user interface.\n");
|
||||||
|
dw_printf ("Use a different method for PTT control.\n");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Do we have permission to access it?
|
||||||
|
*
|
||||||
|
* pi@raspberrypi /sys/class/gpio $ ls -l
|
||||||
|
* total 0
|
||||||
|
* --w------- 1 root root 4096 Aug 20 07:59 export
|
||||||
|
* lrwxrwxrwx 1 root root 0 Aug 20 07:59 gpiochip0 -> ../../devices/virtual/gpio/gpiochip0
|
||||||
|
* --w------- 1 root root 4096 Aug 20 07:59 unexport
|
||||||
|
*/
|
||||||
|
if (geteuid() != 0) {
|
||||||
|
if ( ! (finfo.st_mode & S_IWOTH)) {
|
||||||
|
|
||||||
|
/* Try to change protection. */
|
||||||
|
system ("sudo chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport");
|
||||||
|
|
||||||
|
if (stat("/sys/class/gpio/export", &finfo) < 0) {
|
||||||
|
/* Unexpected because we could do it before. */
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("This system is not configured with the GPIO user interface.\n");
|
||||||
|
dw_printf ("Use a different method for PTT control.\n");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Did we succeed in changing the protection? */
|
||||||
|
if ( ! (finfo.st_mode & S_IWOTH)) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Permissions do not allow ordinary users to access GPIO.\n");
|
||||||
|
dw_printf ("Log in as root and type this command:\n");
|
||||||
|
dw_printf (" chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport\n");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* We should now be able to create the device nodes for
|
||||||
|
* the pins we want to use.
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (ch=0; ch<ptt_num_channels; ch++) {
|
||||||
|
if (ptt_method[ch] == PTT_METHOD_GPIO) {
|
||||||
|
char stemp[80];
|
||||||
|
struct stat finfo;
|
||||||
|
|
||||||
|
fd = open("/sys/class/gpio/export", O_WRONLY);
|
||||||
|
if (fd < 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Permissions do not allow ordinary users to access GPIO.\n");
|
||||||
|
dw_printf ("Log in as root and type this command:\n");
|
||||||
|
dw_printf (" chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport\n");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
sprintf (stemp, "%d", ptt_gpio[ch]);
|
||||||
|
if (write (fd, stemp, strlen(stemp)) != strlen(stemp)) {
|
||||||
|
int e = errno;
|
||||||
|
/* Ignore EBUSY error which seems to mean */
|
||||||
|
/* the device node already exists. */
|
||||||
|
if (e != EBUSY) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Error writing \"%s\" to /sys/class/gpio/export, errno=%d\n", stemp, e);
|
||||||
|
dw_printf ("%s\n", strerror(e));
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close (fd);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We will have the same permission problem if not root.
|
||||||
|
* We only care about "direction" and "value".
|
||||||
|
*/
|
||||||
|
sprintf (stemp, "sudo chmod go+rw /sys/class/gpio/gpio%d/direction", ptt_gpio[ch]);
|
||||||
|
system (stemp);
|
||||||
|
sprintf (stemp, "sudo chmod go+rw /sys/class/gpio/gpio%d/value", ptt_gpio[ch]);
|
||||||
|
system (stemp);
|
||||||
|
|
||||||
|
sprintf (stemp, "/sys/class/gpio/gpio%d/value", ptt_gpio[ch]);
|
||||||
|
|
||||||
|
if (stat(stemp, &finfo) < 0) {
|
||||||
|
int e = errno;
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Failed to get status for %s \n", stemp);
|
||||||
|
dw_printf ("%s\n", strerror(e));
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (geteuid() != 0) {
|
||||||
|
if ( ! (finfo.st_mode & S_IWOTH)) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Permissions do not allow ordinary users to access GPIO.\n");
|
||||||
|
dw_printf ("Log in as root and type these commands:\n");
|
||||||
|
dw_printf (" chmod go+rw /sys/class/gpio/gpio%d/direction", ptt_gpio[ch]);
|
||||||
|
dw_printf (" chmod go+rw /sys/class/gpio/gpio%d/value", ptt_gpio[ch]);
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set output direction with initial state off.
|
||||||
|
*/
|
||||||
|
|
||||||
|
sprintf (stemp, "/sys/class/gpio/gpio%d/direction", ptt_gpio[ch]);
|
||||||
|
fd = open(stemp, O_WRONLY);
|
||||||
|
if (fd < 0) {
|
||||||
|
int e = errno;
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Error opening %s\n", stemp);
|
||||||
|
dw_printf ("%s\n", strerror(e));
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
char hilo[8];
|
||||||
|
if (ptt_invert[ch]) {
|
||||||
|
strcpy (hilo, "high");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
strcpy (hilo, "low");
|
||||||
|
}
|
||||||
|
if (write (fd, hilo, strlen(hilo)) != strlen(hilo)) {
|
||||||
|
int e = errno;
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Error writing initial state to %s\n", stemp);
|
||||||
|
dw_printf ("%s\n", strerror(e));
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
close (fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* Why doesn't it transmit? Probably forgot to specify PTT option. */
|
||||||
|
|
||||||
|
for (ch=0; ch<ptt_num_channels; ch++) {
|
||||||
|
if(ptt_method[ch] == PTT_METHOD_NONE) {
|
||||||
|
text_color_set(DW_COLOR_INFO);
|
||||||
|
dw_printf ("Note: PTT not configured for channel %d.\n", ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} /* end ptt_init */
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: ptt_set
|
||||||
|
*
|
||||||
|
* Purpose: Turn transmitter on or off.
|
||||||
|
*
|
||||||
|
* Inputs: chan channel, 0 .. (number of channels)-1
|
||||||
|
*
|
||||||
|
* ptt 1 for transmit, 0 for receive.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Assumption: ptt_init was called first.
|
||||||
|
*
|
||||||
|
* Description: Set the RTS or DTR line or GPIO pin.
|
||||||
|
* More positive output means transmit unless invert is set.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
void ptt_set (int chan, int ptt)
|
||||||
|
{
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("ptt_set ( %d, %d )\n", chan, ptt);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Inverted output?
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (ptt_invert[chan]) {
|
||||||
|
ptt = ! ptt;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert (chan >= 0 && chan < MAX_CHANS);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Using serial port?
|
||||||
|
*/
|
||||||
|
if (ptt_method[chan] == PTT_METHOD_SERIAL &&
|
||||||
|
ptt_fd[chan] != INVALID_HANDLE_VALUE) {
|
||||||
|
|
||||||
|
if (ptt_line[chan] == PTT_LINE_RTS) {
|
||||||
|
|
||||||
|
if (ptt) {
|
||||||
|
RTS_ON(ptt_fd[chan]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
RTS_OFF(ptt_fd[chan]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ptt_line[chan] == PTT_LINE_DTR) {
|
||||||
|
|
||||||
|
if (ptt) {
|
||||||
|
DTR_ON(ptt_fd[chan]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
DTR_OFF(ptt_fd[chan]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Using GPIO?
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
#else
|
||||||
|
|
||||||
|
if (ptt_method[chan] == PTT_METHOD_GPIO) {
|
||||||
|
int fd;
|
||||||
|
char stemp[80];
|
||||||
|
|
||||||
|
sprintf (stemp, "/sys/class/gpio/gpio%d/value", ptt_gpio[chan]);
|
||||||
|
|
||||||
|
fd = open(stemp, O_WRONLY);
|
||||||
|
if (fd < 0) {
|
||||||
|
int e = errno;
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Error opening %s to set PTT signal.\n", stemp);
|
||||||
|
dw_printf ("%s\n", strerror(e));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf (stemp, "%d", ptt);
|
||||||
|
|
||||||
|
if (write (fd, stemp, 1) != 1) {
|
||||||
|
int e = errno;
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Error setting GPIO %d for PTT\n", ptt_gpio[chan]);
|
||||||
|
dw_printf ("%s\n", strerror(e));
|
||||||
|
}
|
||||||
|
close (fd);
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
} /* end ptt_set */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: ptt_term
|
||||||
|
*
|
||||||
|
* Purpose: Make sure PTT is turned off when we exit.
|
||||||
|
*
|
||||||
|
* Inputs: none
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
void ptt_term (void)
|
||||||
|
{
|
||||||
|
int n;
|
||||||
|
|
||||||
|
for (n = 0; n < ptt_num_channels; n++) {
|
||||||
|
ptt_set (n, 0);
|
||||||
|
if (ptt_fd[n] != INVALID_HANDLE_VALUE) {
|
||||||
|
#if __WIN32__
|
||||||
|
CloseHandle (ptt_fd[n]);
|
||||||
|
#else
|
||||||
|
close(ptt_fd[n]);
|
||||||
|
#endif
|
||||||
|
ptt_fd[n] = INVALID_HANDLE_VALUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Quick stand-alone test for above.
|
||||||
|
*
|
||||||
|
* gcc -DTEST -o ptest ptt.c ; ./ptest
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#if TEST
|
||||||
|
|
||||||
|
void text_color_set (dw_color_t c) { }
|
||||||
|
|
||||||
|
main ()
|
||||||
|
{
|
||||||
|
struct audio_s modem;
|
||||||
|
int n;
|
||||||
|
int chan;
|
||||||
|
|
||||||
|
memset (&modem, 0, sizeof(modem));
|
||||||
|
|
||||||
|
modem.num_channels = 2;
|
||||||
|
|
||||||
|
modem.ptt_method[0] = PTT_METHOD_SERIAL;
|
||||||
|
//strcpy (modem.ptt_device[0], "COM1");
|
||||||
|
strcpy (modem.ptt_device[0], "/dev/ttyUSB0");
|
||||||
|
modem.ptt_line[0] = PTT_LINE_RTS;
|
||||||
|
|
||||||
|
modem.ptt_method[1] = PTT_METHOD_SERIAL;
|
||||||
|
//strcpy (modem.ptt_device[1], "COM1");
|
||||||
|
strcpy (modem.ptt_device[1], "/dev/ttyUSB0");
|
||||||
|
modem.ptt_line[1] = PTT_LINE_DTR;
|
||||||
|
|
||||||
|
|
||||||
|
/* initialize - both off */
|
||||||
|
|
||||||
|
ptt_init (&modem);
|
||||||
|
|
||||||
|
SLEEP_SEC(2);
|
||||||
|
|
||||||
|
/* flash each a few times. */
|
||||||
|
|
||||||
|
dw_printf ("turn on RTS a few times...\n");
|
||||||
|
|
||||||
|
chan = 0;
|
||||||
|
for (n=0; n<3; n++) {
|
||||||
|
ptt_set (chan, 1);
|
||||||
|
SLEEP_SEC(1);
|
||||||
|
ptt_set (chan, 0);
|
||||||
|
SLEEP_SEC(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
dw_printf ("turn on DTR a few times...\n");
|
||||||
|
|
||||||
|
chan = 1;
|
||||||
|
for (n=0; n<3; n++) {
|
||||||
|
ptt_set (chan, 1);
|
||||||
|
SLEEP_SEC(1);
|
||||||
|
ptt_set (chan, 0);
|
||||||
|
SLEEP_SEC(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ptt_term();
|
||||||
|
|
||||||
|
/* Same thing again but invert RTS. */
|
||||||
|
|
||||||
|
modem.ptt_invert[0] = 1;
|
||||||
|
|
||||||
|
ptt_init (&modem);
|
||||||
|
|
||||||
|
SLEEP_SEC(2);
|
||||||
|
|
||||||
|
dw_printf ("INVERTED - RTS a few times...\n");
|
||||||
|
|
||||||
|
chan = 0;
|
||||||
|
for (n=0; n<3; n++) {
|
||||||
|
ptt_set (chan, 1);
|
||||||
|
SLEEP_SEC(1);
|
||||||
|
ptt_set (chan, 0);
|
||||||
|
SLEEP_SEC(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
dw_printf ("turn on DTR a few times...\n");
|
||||||
|
|
||||||
|
chan = 1;
|
||||||
|
for (n=0; n<3; n++) {
|
||||||
|
ptt_set (chan, 1);
|
||||||
|
SLEEP_SEC(1);
|
||||||
|
ptt_set (chan, 0);
|
||||||
|
SLEEP_SEC(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ptt_term ();
|
||||||
|
|
||||||
|
|
||||||
|
/* Test GPIO */
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
#else
|
||||||
|
|
||||||
|
memset (&modem, 0, sizeof(modem));
|
||||||
|
modem.num_channels = 1;
|
||||||
|
modem.ptt_method[0] = PTT_METHOD_GPIO;
|
||||||
|
modem.ptt_gpio[0] = 25;
|
||||||
|
|
||||||
|
dw_printf ("Try GPIO %d a few times...\n", modem.ptt_gpio[0]);
|
||||||
|
|
||||||
|
ptt_init (&modem);
|
||||||
|
|
||||||
|
SLEEP_SEC(2);
|
||||||
|
chan = 0;
|
||||||
|
for (n=0; n<3; n++) {
|
||||||
|
ptt_set (chan, 1);
|
||||||
|
SLEEP_SEC(1);
|
||||||
|
ptt_set (chan, 0);
|
||||||
|
SLEEP_SEC(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ptt_term ();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* end ptt.c */
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef PTT_H
|
||||||
|
#define PTT_H 1
|
||||||
|
|
||||||
|
|
||||||
|
#include "audio.h" /* for struct audio_s */
|
||||||
|
|
||||||
|
|
||||||
|
void ptt_init (struct audio_s *p_modem);
|
||||||
|
|
||||||
|
void ptt_set (int chan, int ptt);
|
||||||
|
|
||||||
|
void ptt_term (void);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* end ptt.h */
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,287 @@
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Module: pttest.c
|
||||||
|
*
|
||||||
|
* Purpose: Test for pseudo terminal.
|
||||||
|
*
|
||||||
|
* Input:
|
||||||
|
*
|
||||||
|
* Outputs:
|
||||||
|
*
|
||||||
|
* Description: The protocol name is an acronym for Keep it Simple Stupid.
|
||||||
|
* You would expect it to be simple but this caused a lot
|
||||||
|
* of effort on Linux. The problem is that writes to a pseudo
|
||||||
|
* terminal eventually block if nothing at the other end
|
||||||
|
* is removing the data. This causes the application to
|
||||||
|
* hang and stop receiving after a while.
|
||||||
|
*
|
||||||
|
* This is an attempt to demonstrate the problem in a small
|
||||||
|
* test case and, hopefully, find a solution.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Instructions:
|
||||||
|
* First compile like this:
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Run it, noting the name of pseudo terminal,
|
||||||
|
* typically /dev/pts/1.
|
||||||
|
*
|
||||||
|
* In another window, type:
|
||||||
|
*
|
||||||
|
* cat /dev/pts/1
|
||||||
|
*
|
||||||
|
* This should run "forever" as long as something is
|
||||||
|
* reading from the slave side of the pseudo terminal.
|
||||||
|
*
|
||||||
|
* If nothing is removing the data, this runs for a while
|
||||||
|
* and then blocks on the write.
|
||||||
|
* For this particular application we just want to discard
|
||||||
|
* excess data if no one is listening.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Failed experiments:
|
||||||
|
*
|
||||||
|
* Notice that ??? always returns 0 for amount of data
|
||||||
|
* in the queue.
|
||||||
|
* Define TEST1 to make the device non-blocking.
|
||||||
|
* Write fails entirely.
|
||||||
|
*
|
||||||
|
* Define TEST2 to use a different method.
|
||||||
|
* Also fails in the same way.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*---------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
|
||||||
|
#define __USE_XOPEN2KXSI 1
|
||||||
|
#define __USE_XOPEN 1
|
||||||
|
//#define __USE_POSIX 1
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/errno.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
//#include "direwolf.h"
|
||||||
|
//#include "tq.h"
|
||||||
|
//#include "ax25_pad.h"
|
||||||
|
//#include "textcolor.h"
|
||||||
|
//#include "kiss.h"
|
||||||
|
//#include "xmit.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static MYFDTYPE pt_master_fd = -1; /* File descriptor for my end. */
|
||||||
|
|
||||||
|
static MYFDTYPE pt_slave_fd = -1; /* File descriptor for pseudo terminal */
|
||||||
|
/* for use by application. */
|
||||||
|
static int msg_number;
|
||||||
|
static int total_bytes;
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: kiss_init
|
||||||
|
*
|
||||||
|
* Purpose: Set up a pseudo terminal acting as a virtual KISS TNC.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Inputs: mc->nullmodem - name of device for our end of nullmodem.
|
||||||
|
*
|
||||||
|
* Outputs:
|
||||||
|
*
|
||||||
|
* Description: (1) Create a pseudo terminal for the client to use.
|
||||||
|
* (2) Start a new thread to listen for commands from client app
|
||||||
|
* so the main application doesn't block while we wait.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
static int kiss_open_pt (void);
|
||||||
|
|
||||||
|
|
||||||
|
main (int argc, char *argv)
|
||||||
|
{
|
||||||
|
|
||||||
|
pt_master_fd = kiss_open_pt ();
|
||||||
|
printf ("msg total qcount\n");
|
||||||
|
|
||||||
|
msg_number = 0;
|
||||||
|
total_bytes = 0;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns fd for master side of pseudo terminal or MYFDERROR for error.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
static int kiss_open_pt (void)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
char *slave_device;
|
||||||
|
struct termios ts;
|
||||||
|
int e;
|
||||||
|
int flags;
|
||||||
|
|
||||||
|
fd = posix_openpt(O_RDWR|O_NOCTTY);
|
||||||
|
|
||||||
|
if (fd == -1
|
||||||
|
|| grantpt (fd) == -1
|
||||||
|
|| unlockpt (fd) == -1
|
||||||
|
|| (slave_device = ptsname (fd)) == NULL) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
printf ("ERROR - Could not create pseudo terminal.\n");
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
e = tcgetattr (fd, &ts);
|
||||||
|
if (e != 0) {
|
||||||
|
printf ("Can't get pseudo terminal attributes, err=%d\n", e);
|
||||||
|
perror ("pt tcgetattr");
|
||||||
|
}
|
||||||
|
|
||||||
|
cfmakeraw (&ts);
|
||||||
|
|
||||||
|
ts.c_cc[VMIN] = 1; /* wait for at least one character */
|
||||||
|
ts.c_cc[VTIME] = 0; /* no fancy timing. */
|
||||||
|
|
||||||
|
|
||||||
|
e = tcsetattr (fd, TCSANOW, &ts);
|
||||||
|
if (e != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
printf ("Can't set pseudo terminal attributes, err=%d\n", e);
|
||||||
|
perror ("pt tcsetattr");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* After running for a while on Linux, the write eventually
|
||||||
|
* blocks if no one is reading from the other side of
|
||||||
|
* the pseudo terminal. We get stuck on the kiss data
|
||||||
|
* write and reception stops.
|
||||||
|
*
|
||||||
|
* I tried using ioctl(,TIOCOUTQ,) to see how much was in
|
||||||
|
* the queue but that always returned zero. (Ubuntu)
|
||||||
|
*
|
||||||
|
* Let's try using non-blocking writes and see if we get
|
||||||
|
* the EWOULDBLOCK status instead of hanging.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if TEST1
|
||||||
|
// this is worse. all writes fail. errno = ? bad file descriptor
|
||||||
|
flags = fcntl(fd, F_GETFL, 0);
|
||||||
|
e = fcntl (fd, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
if (e != 0) {
|
||||||
|
printf ("Can't set pseudo terminal to nonblocking, fcntl returns %d, errno = %d\n", e, errno);
|
||||||
|
perror ("pt fcntl");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if TEST2
|
||||||
|
// same
|
||||||
|
flags = 1;
|
||||||
|
e = ioctl (fd, FIONBIO, &flags);
|
||||||
|
if (e != 0) {
|
||||||
|
printf ("Can't set pseudo terminal to nonblocking, ioctl returns %d, errno = %d\n", e, errno);
|
||||||
|
perror ("pt ioctl");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
printf("Virtual KISS TNC is available on %s\n", slave_device);
|
||||||
|
|
||||||
|
|
||||||
|
// Sample code shows this. Why would we open it here?
|
||||||
|
|
||||||
|
// pt_slave_fd = open(slave_device, O_RDWR|O_NOCTTY);
|
||||||
|
|
||||||
|
|
||||||
|
return (fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: kiss_send_rec_packet
|
||||||
|
*
|
||||||
|
* Purpose: Send a received packet to the client app.
|
||||||
|
*
|
||||||
|
* Inputs: chan - Channel number where packet was received.
|
||||||
|
* 0 = first, 1 = second if any.
|
||||||
|
*
|
||||||
|
* pp - Identifier for packet object.
|
||||||
|
*
|
||||||
|
* fbuf - Address of raw received frame buffer.
|
||||||
|
* flen - Length of raw received frame.
|
||||||
|
* Not including the FCS.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Description: Send message to client.
|
||||||
|
* We really don't care if anyone is listening or not.
|
||||||
|
* I don't even know if we can find out.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
void kiss_send_rec_packet (void)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
char kiss_buff[100];
|
||||||
|
|
||||||
|
int kiss_len;
|
||||||
|
int q_count = 123;
|
||||||
|
|
||||||
|
|
||||||
|
int j;
|
||||||
|
|
||||||
|
strcpy (kiss_buff, "The quick brown fox jumps over the lazy dog.\n");
|
||||||
|
kiss_len = strlen(kiss_buff);
|
||||||
|
|
||||||
|
|
||||||
|
if (pt_master_fd != MYFDERROR) {
|
||||||
|
int err;
|
||||||
|
|
||||||
|
msg_number++;
|
||||||
|
total_bytes += kiss_len;
|
||||||
|
|
||||||
|
//#if DEBUG
|
||||||
|
printf ("%3d %5d %5d\n", msg_number, total_bytes, q_count);
|
||||||
|
//#endif
|
||||||
|
err = write (pt_master_fd, kiss_buff, kiss_len);
|
||||||
|
|
||||||
|
if (err == -1 && errno == EWOULDBLOCK) {
|
||||||
|
//#if DEBUG
|
||||||
|
printf ("Discarding message because write would block.\n");
|
||||||
|
//#endif
|
||||||
|
}
|
||||||
|
else if (err != kiss_len)
|
||||||
|
{
|
||||||
|
printf ("\nError sending message on pseudo terminal. len=%d, write returned %d, errno = %d\n\n",
|
||||||
|
kiss_len, err, errno);
|
||||||
|
perror ("pt write");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
//#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* end pttest.c */
|
|
@ -0,0 +1,453 @@
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011,2012 John Langner, WB2OSZ
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Module: rdq.c
|
||||||
|
*
|
||||||
|
* Purpose: Retry later decode queue for frames with bad FCS.
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
*
|
||||||
|
*---------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "ax25_pad.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
#include "audio.h"
|
||||||
|
#include "rdq.h"
|
||||||
|
#include "dedupe.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static rrbb_t queue_head; /* Head of linked list for queue. */
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
|
||||||
|
static CRITICAL_SECTION rdq_cs; /* Critical section for updating queues. */
|
||||||
|
|
||||||
|
static HANDLE wake_up_event; /* Notify try decode again thread when queue not empty. */
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
static pthread_mutex_t rdq_mutex; /* Critical section for updating queues. */
|
||||||
|
|
||||||
|
static pthread_cond_t wake_up_cond; /* Notify try decode again thread when queue not empty. */
|
||||||
|
|
||||||
|
static pthread_mutex_t wake_up_mutex; /* Required by cond_wait. */
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: rdq_init
|
||||||
|
*
|
||||||
|
* Purpose: Initialize the receive decode again queue.
|
||||||
|
*
|
||||||
|
* Inputs: None. Only single queue for all channels.
|
||||||
|
*
|
||||||
|
* Outputs:
|
||||||
|
*
|
||||||
|
* Description: Initialize the queue to be empty and set up other
|
||||||
|
* mechanisms for sharing it between different threads.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
void rdq_init (void)
|
||||||
|
{
|
||||||
|
//int c, p;
|
||||||
|
#if __WIN32__
|
||||||
|
#else
|
||||||
|
int err;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("rdq_init ( )\n");
|
||||||
|
dw_printf ("rdq_init: pthread_mutex_init...\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
InitializeCriticalSection (&rdq_cs);
|
||||||
|
#else
|
||||||
|
err = pthread_mutex_init (&wake_up_mutex, NULL);
|
||||||
|
err = pthread_mutex_init (&rdq_mutex, NULL);
|
||||||
|
if (err != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("rdq_init: pthread_mutex_init err=%d", err);
|
||||||
|
perror ("");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("rdq_init: pthread_cond_init...\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
|
||||||
|
wake_up_event = CreateEvent (NULL, 0, 0, NULL);
|
||||||
|
|
||||||
|
if (wake_up_event == NULL) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("rdq_init: pthread_cond_init: can't create decode wake up event");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
err = pthread_cond_init (&wake_up_cond, NULL);
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("rdq_init: pthread_cond_init returns %d\n", err);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
if (err != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("rdq_init: pthread_cond_init err=%d", err);
|
||||||
|
perror ("");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
} /* end rdq_init */
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: rdq_append
|
||||||
|
*
|
||||||
|
* Purpose: Add a packet to the end of the queue.
|
||||||
|
*
|
||||||
|
* Inputs: pp - Address of raw received bit buffer.
|
||||||
|
* Caller should NOT make any references to
|
||||||
|
* it after this point because it could
|
||||||
|
* be deleted at any time.
|
||||||
|
*
|
||||||
|
* Outputs:
|
||||||
|
*
|
||||||
|
* Description: Add buffer to end of linked list.
|
||||||
|
* Signal the decode thread if the queue was formerly empty.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
void rdq_append (rrbb_t rrbb)
|
||||||
|
{
|
||||||
|
//int was_empty;
|
||||||
|
rrbb_t plast;
|
||||||
|
rrbb_t pnext;
|
||||||
|
#ifndef __WIN32__
|
||||||
|
int err;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("rdq_append (rrbb=%p)\n", rrbb);
|
||||||
|
dw_printf ("rdq_append: enter critical section\n");
|
||||||
|
#endif
|
||||||
|
#if __WIN32__
|
||||||
|
EnterCriticalSection (&rdq_cs);
|
||||||
|
#else
|
||||||
|
err = pthread_mutex_lock (&rdq_mutex);
|
||||||
|
if (err != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("rdq_append: pthread_mutex_lock err=%d", err);
|
||||||
|
perror ("");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//was_empty = 1;
|
||||||
|
//if (queue_head != NULL) {
|
||||||
|
//was_empty = 0;
|
||||||
|
//}
|
||||||
|
if (queue_head == NULL) {
|
||||||
|
queue_head = rrbb;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
plast = queue_head;
|
||||||
|
while ((pnext = rrbb_get_nextp(plast)) != NULL) {
|
||||||
|
plast = pnext;
|
||||||
|
}
|
||||||
|
rrbb_set_nextp (plast, rrbb);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
LeaveCriticalSection (&rdq_cs);
|
||||||
|
#else
|
||||||
|
err = pthread_mutex_unlock (&rdq_mutex);
|
||||||
|
if (err != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("rdq_append: pthread_mutex_unlock err=%d", err);
|
||||||
|
perror ("");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("rdq_append: left critical section\n");
|
||||||
|
dw_printf ("rdq_append (): about to wake up retry decode thread.\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
SetEvent (wake_up_event);
|
||||||
|
#else
|
||||||
|
err = pthread_mutex_lock (&wake_up_mutex);
|
||||||
|
if (err != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("rdq_append: pthread_mutex_lock wu err=%d", err);
|
||||||
|
perror ("");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pthread_cond_signal (&wake_up_cond);
|
||||||
|
if (err != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("rdq_append: pthread_cond_signal err=%d", err);
|
||||||
|
perror ("");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pthread_mutex_unlock (&wake_up_mutex);
|
||||||
|
if (err != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("rdq_append: pthread_mutex_unlock wu err=%d", err);
|
||||||
|
perror ("");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: rdq_wait_while_empty
|
||||||
|
*
|
||||||
|
* Purpose: Sleep while the queue is empty rather than
|
||||||
|
* polling periodically.
|
||||||
|
*
|
||||||
|
* Inputs: None.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
void rdq_wait_while_empty (void)
|
||||||
|
{
|
||||||
|
int is_empty;
|
||||||
|
#ifndef __WIN32__
|
||||||
|
int err;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("rdq_wait_while_empty () : enter critical section\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
EnterCriticalSection (&rdq_cs);
|
||||||
|
#else
|
||||||
|
err = pthread_mutex_lock (&rdq_mutex);
|
||||||
|
if (err != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("rdq_wait_while_empty: pthread_mutex_lock err=%d", err);
|
||||||
|
perror ("");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
//text_color_set(DW_COLOR_DEBUG);
|
||||||
|
//dw_printf ("rdq_wait_while_empty (): after pthread_mutex_lock\n");
|
||||||
|
#endif
|
||||||
|
is_empty = 1;
|
||||||
|
if (queue_head != NULL)
|
||||||
|
is_empty = 0;
|
||||||
|
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
LeaveCriticalSection (&rdq_cs);
|
||||||
|
#else
|
||||||
|
err = pthread_mutex_unlock (&rdq_mutex);
|
||||||
|
if (err != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("rdq_wait_while_empty: pthread_mutex_unlock err=%d", err);
|
||||||
|
perror ("");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("rdq_wait_while_empty () : left critical section\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("rdq_wait_while_empty (): is_empty = %d\n", is_empty);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (is_empty) {
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("rdq_wait_while_empty (): SLEEP - about to call cond wait\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
WaitForSingleObject (wake_up_event, INFINITE);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("rdq_wait_while_empty (): returned from wait\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#else
|
||||||
|
err = pthread_mutex_lock (&wake_up_mutex);
|
||||||
|
if (err != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("rdq_wait_while_empty: pthread_mutex_lock wu err=%d", err);
|
||||||
|
perror ("");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pthread_cond_wait (&wake_up_cond, &wake_up_mutex);
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("rdq_wait_while_empty (): WOKE UP - returned from cond wait, err = %d\n", err);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (err != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("rdq_wait_while_empty: pthread_cond_wait err=%d", err);
|
||||||
|
perror ("");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pthread_mutex_unlock (&wake_up_mutex);
|
||||||
|
if (err != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("rdq_wait_while_empty: pthread_mutex_unlock wu err=%d", err);
|
||||||
|
perror ("");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("rdq_wait_while_empty () returns\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: rdq_remove
|
||||||
|
*
|
||||||
|
* Purpose: Remove raw bit buffer from the head of the queue.
|
||||||
|
*
|
||||||
|
* Inputs: none
|
||||||
|
*
|
||||||
|
* Returns: Pointer to rrbb object.
|
||||||
|
* Caller should destroy it with rrbb_delete when finished with it.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
rrbb_t rdq_remove (void)
|
||||||
|
{
|
||||||
|
|
||||||
|
rrbb_t result_p;
|
||||||
|
#ifndef __WIN32__
|
||||||
|
int err;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("rdq_remove() enter critical section\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
EnterCriticalSection (&rdq_cs);
|
||||||
|
#else
|
||||||
|
err = pthread_mutex_lock (&rdq_mutex);
|
||||||
|
if (err != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("rdq_remove: pthread_mutex_lock err=%d", err);
|
||||||
|
perror ("");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (queue_head == NULL) {
|
||||||
|
result_p = NULL;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
result_p = queue_head;
|
||||||
|
queue_head = rrbb_get_nextp(result_p);
|
||||||
|
rrbb_set_nextp (result_p, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
LeaveCriticalSection (&rdq_cs);
|
||||||
|
#else
|
||||||
|
err = pthread_mutex_unlock (&rdq_mutex);
|
||||||
|
if (err != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("rdq_remove: pthread_mutex_unlock err=%d", err);
|
||||||
|
perror ("");
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("rdq_remove() leave critical section, returns %p\n", result_p);
|
||||||
|
#endif
|
||||||
|
return (result_p);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* end rdq.c */
|
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Module: rdq.h
|
||||||
|
*
|
||||||
|
* Purpose: Retry decode queue - Hold raw received frames with errors
|
||||||
|
* for retrying the decoding later.
|
||||||
|
*
|
||||||
|
*---------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#ifndef RDQ_H
|
||||||
|
#define RDQ_H 1
|
||||||
|
|
||||||
|
#include "rrbb.h"
|
||||||
|
//#include "audio.h"
|
||||||
|
|
||||||
|
void rdq_init (void);
|
||||||
|
|
||||||
|
void rdq_append (rrbb_t rrbb);
|
||||||
|
|
||||||
|
void rdq_wait_while_empty (void);
|
||||||
|
|
||||||
|
rrbb_t rdq_remove (void);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* end rdq.h */
|
|
@ -0,0 +1,252 @@
|
||||||
|
//
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011,2012,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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Module: redecode.c
|
||||||
|
*
|
||||||
|
* Purpose: Retry decoding frames that have a bad FCS.
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Usage: (1) The main application calls redecode_init.
|
||||||
|
*
|
||||||
|
* This will initialize the retry decoding queue
|
||||||
|
* and create a thread to work on contents of the queue.
|
||||||
|
*
|
||||||
|
* (2) The application queues up frames by calling rdq_append.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* (3) redecode_thread removes raw frames from the queue and
|
||||||
|
* tries to recover from errors.
|
||||||
|
*
|
||||||
|
*---------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
//#include <sys/time.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
#include "ax25_pad.h"
|
||||||
|
#include "textcolor.h"
|
||||||
|
#include "audio.h"
|
||||||
|
#include "rdq.h"
|
||||||
|
#include "redecode.h"
|
||||||
|
#include "hdlc_send.h"
|
||||||
|
#include "hdlc_rec2.h"
|
||||||
|
#include "ptt.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
static unsigned redecode_thread (void *arg);
|
||||||
|
#else
|
||||||
|
static void * redecode_thread (void *arg);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: redecode_init
|
||||||
|
*
|
||||||
|
* Purpose: Initialize the process to try fixing bits in frames with bad FCS.
|
||||||
|
*
|
||||||
|
* Inputs: none.
|
||||||
|
*
|
||||||
|
* Outputs: none.
|
||||||
|
*
|
||||||
|
* Description: Initialize the queue to be empty and set up other
|
||||||
|
* mechanisms for sharing it between different threads.
|
||||||
|
*
|
||||||
|
* Start up redecode_thread to actually process the
|
||||||
|
* raw frames from the queue.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void redecode_init (void)
|
||||||
|
{
|
||||||
|
//int j;
|
||||||
|
#if __WIN32__
|
||||||
|
HANDLE redecode_th;
|
||||||
|
#else
|
||||||
|
pthread_t redecode_tid;
|
||||||
|
int e;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("redecode_init ( ... )\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("redecode_init: about to call rdq_init \n");
|
||||||
|
#endif
|
||||||
|
rdq_init ();
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("redecode_init: about to create thread \n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
redecode_th = _beginthreadex (NULL, 0, redecode_thread, NULL, 0, NULL);
|
||||||
|
if (redecode_th == NULL) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Could not create redecode thread\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
|
||||||
|
//TODO: Give thread lower priority.
|
||||||
|
|
||||||
|
e = pthread_create (&redecode_tid, NULL, redecode_thread, (void *)0);
|
||||||
|
if (e != 0) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
perror("Could not create redecode thread");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("redecode_init: finished \n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
} /* end redecode_init */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: redecode_thread
|
||||||
|
*
|
||||||
|
* Purpose: Try to decode frames with a bad FCS.
|
||||||
|
*
|
||||||
|
* Inputs: None.
|
||||||
|
*
|
||||||
|
* Outputs:
|
||||||
|
*
|
||||||
|
* Description: Initialize the queue to be empty and set up other
|
||||||
|
* mechanisms for sharing it between different threads.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
static unsigned redecode_thread (void *arg)
|
||||||
|
#else
|
||||||
|
static void * redecode_thread (void *arg)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
rrbb_t block;
|
||||||
|
int blen;
|
||||||
|
int chan, subchan;
|
||||||
|
int alevel;
|
||||||
|
|
||||||
|
|
||||||
|
#if __WIN32__
|
||||||
|
HANDLE tid = GetCurrentThread();
|
||||||
|
//int tp;
|
||||||
|
|
||||||
|
//tp = GetThreadPriority (tid);
|
||||||
|
//text_color_set(DW_COLOR_DEBUG);
|
||||||
|
//dw_printf ("Starting redecode thread priority=%d\n", tp);
|
||||||
|
SetThreadPriority (tid, THREAD_PRIORITY_LOWEST);
|
||||||
|
//tp = GetThreadPriority (tid);
|
||||||
|
//dw_printf ("New redecode thread priority=%d\n", tp);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
|
||||||
|
rdq_wait_while_empty ();
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("redecode_thread: woke up\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
block = rdq_remove ();
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("redecode_thread: rdq_remove() returned %p\n", block);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Don't expect null ever but be safe. */
|
||||||
|
|
||||||
|
if (block != NULL) {
|
||||||
|
|
||||||
|
chan = rrbb_get_chan(block);
|
||||||
|
subchan = rrbb_get_subchan(block);
|
||||||
|
alevel = rrbb_get_audio_level(block);
|
||||||
|
blen = rrbb_get_len(block);
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("redecode_thread: begin processing %p, from channel %d, blen=%d\n", block, chan, blen);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
hdlc_rec2_try_to_fix_later (block, chan, subchan, alevel);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
text_color_set(DW_COLOR_DEBUG);
|
||||||
|
dw_printf ("redecode_thread: finished processing %p\n", block);
|
||||||
|
#endif
|
||||||
|
rrbb_delete (block);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
} /* end redecode_thread */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* end redecode.c */
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef REDECODE_H
|
||||||
|
#define REDECODE_H 1
|
||||||
|
|
||||||
|
#include "rrbb.h"
|
||||||
|
|
||||||
|
|
||||||
|
extern void redecode_init (void);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* end redecode.h */
|
||||||
|
|
|
@ -0,0 +1,340 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||||
|
59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library 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
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
convey the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
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, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) year name of author
|
||||||
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, the commands you use may
|
||||||
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||||
|
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
This General Public License does not permit incorporating your program into
|
||||||
|
proprietary programs. If your program is a subroutine library, you may
|
||||||
|
consider it more useful to permit linking proprietary applications with the
|
||||||
|
library. If this is what you want to do, use the GNU Library General
|
||||||
|
Public License instead of this License.
|
|
@ -0,0 +1,463 @@
|
||||||
|
Installing the GNU C Library
|
||||||
|
****************************
|
||||||
|
|
||||||
|
Before you do anything else, you should read the file `FAQ' located at
|
||||||
|
the top level of the source tree. This file answers common questions
|
||||||
|
and describes problems you may experience with compilation and
|
||||||
|
installation. It is updated more frequently than this manual.
|
||||||
|
|
||||||
|
Features can be added to GNU Libc via "add-on" bundles. These are
|
||||||
|
separate tar files, which you unpack into the top level of the source
|
||||||
|
tree. Then you give `configure' the `--enable-add-ons' option to
|
||||||
|
activate them, and they will be compiled into the library.
|
||||||
|
|
||||||
|
You will need recent versions of several GNU tools: definitely GCC
|
||||||
|
and GNU Make, and possibly others. *Note Tools for Compilation::,
|
||||||
|
below.
|
||||||
|
|
||||||
|
Configuring and compiling GNU Libc
|
||||||
|
==================================
|
||||||
|
|
||||||
|
GNU libc cannot be compiled in the source directory. You must build it
|
||||||
|
in a separate build directory. For example, if you have unpacked the
|
||||||
|
glibc sources in `/src/gnu/glibc-2.4', create a directory
|
||||||
|
`/src/gnu/glibc-build' to put the object files in. This allows
|
||||||
|
removing the whole build directory in case an error occurs, which is
|
||||||
|
the safest way to get a fresh start and should always be done.
|
||||||
|
|
||||||
|
From your object directory, run the shell script `configure' located
|
||||||
|
at the top level of the source tree. In the scenario above, you'd type
|
||||||
|
|
||||||
|
$ ../glibc-2.4/configure ARGS...
|
||||||
|
|
||||||
|
Please note that even though you're building in a separate build
|
||||||
|
directory, the compilation needs to modify a few files in the source
|
||||||
|
directory, especially some files in the manual subdirectory.
|
||||||
|
|
||||||
|
`configure' takes many options, but the only one that is usually
|
||||||
|
mandatory is `--prefix'. This option tells `configure' where you want
|
||||||
|
glibc installed. This defaults to `/usr/local', but the normal setting
|
||||||
|
to install as the standard system library is `--prefix=/usr' for
|
||||||
|
GNU/Linux systems and `--prefix=' (an empty prefix) for GNU/Hurd
|
||||||
|
systems.
|
||||||
|
|
||||||
|
It may also be useful to set the CC and CFLAGS variables in the
|
||||||
|
environment when running `configure'. CC selects the C compiler that
|
||||||
|
will be used, and CFLAGS sets optimization options for the compiler.
|
||||||
|
|
||||||
|
The following list describes all of the available options for
|
||||||
|
`configure':
|
||||||
|
|
||||||
|
`--prefix=DIRECTORY'
|
||||||
|
Install machine-independent data files in subdirectories of
|
||||||
|
`DIRECTORY'. The default is to install in `/usr/local'.
|
||||||
|
|
||||||
|
`--exec-prefix=DIRECTORY'
|
||||||
|
Install the library and other machine-dependent files in
|
||||||
|
subdirectories of `DIRECTORY'. The default is to the `--prefix'
|
||||||
|
directory if that option is specified, or `/usr/local' otherwise.
|
||||||
|
|
||||||
|
`--with-headers=DIRECTORY'
|
||||||
|
Look for kernel header files in DIRECTORY, not `/usr/include'.
|
||||||
|
Glibc needs information from the kernel's private header files.
|
||||||
|
Glibc will normally look in `/usr/include' for them, but if you
|
||||||
|
specify this option, it will look in DIRECTORY instead.
|
||||||
|
|
||||||
|
This option is primarily of use on a system where the headers in
|
||||||
|
`/usr/include' come from an older version of glibc. Conflicts can
|
||||||
|
occasionally happen in this case. Note that Linux libc5 qualifies
|
||||||
|
as an older version of glibc. You can also use this option if you
|
||||||
|
want to compile glibc with a newer set of kernel headers than the
|
||||||
|
ones found in `/usr/include'.
|
||||||
|
|
||||||
|
`--enable-add-ons[=LIST]'
|
||||||
|
Specify add-on packages to include in the build. If this option is
|
||||||
|
specified with no list, it enables all the add-on packages it
|
||||||
|
finds in the main source directory; this is the default behavior.
|
||||||
|
You may specify an explicit list of add-ons to use in LIST,
|
||||||
|
separated by spaces or commas (if you use spaces, remember to
|
||||||
|
quote them from the shell). Each add-on in LIST can be an
|
||||||
|
absolute directory name or can be a directory name relative to the
|
||||||
|
main source directory, or relative to the build directory (that
|
||||||
|
is, the current working directory). For example,
|
||||||
|
`--enable-add-ons=nptl,../glibc-libidn-2.4'.
|
||||||
|
|
||||||
|
`--enable-kernel=VERSION'
|
||||||
|
This option is currently only useful on GNU/Linux systems. The
|
||||||
|
VERSION parameter should have the form X.Y.Z and describes the
|
||||||
|
smallest version of the Linux kernel the generated library is
|
||||||
|
expected to support. The higher the VERSION number is, the less
|
||||||
|
compatibility code is added, and the faster the code gets.
|
||||||
|
|
||||||
|
`--with-binutils=DIRECTORY'
|
||||||
|
Use the binutils (assembler and linker) in `DIRECTORY', not the
|
||||||
|
ones the C compiler would default to. You can use this option if
|
||||||
|
the default binutils on your system cannot deal with all the
|
||||||
|
constructs in the GNU C library. In that case, `configure' will
|
||||||
|
detect the problem and suppress these constructs, so that the
|
||||||
|
library will still be usable, but functionality may be lost--for
|
||||||
|
example, you can't build a shared libc with old binutils.
|
||||||
|
|
||||||
|
`--without-fp'
|
||||||
|
Use this option if your computer lacks hardware floating-point
|
||||||
|
support and your operating system does not emulate an FPU.
|
||||||
|
|
||||||
|
these
|
||||||
|
|
||||||
|
`--disable-shared'
|
||||||
|
Don't build shared libraries even if it is possible. Not all
|
||||||
|
systems support shared libraries; you need ELF support and
|
||||||
|
(currently) the GNU linker.
|
||||||
|
|
||||||
|
`--disable-profile'
|
||||||
|
Don't build libraries with profiling information. You may want to
|
||||||
|
use this option if you don't plan to do profiling.
|
||||||
|
|
||||||
|
`--enable-omitfp'
|
||||||
|
Use maximum optimization for the normal (static and shared)
|
||||||
|
libraries, and compile separate static libraries with debugging
|
||||||
|
information and no optimization. We recommend not doing this.
|
||||||
|
The extra optimization doesn't gain you much, it may provoke
|
||||||
|
compiler bugs, and you won't be able to trace bugs through the C
|
||||||
|
library.
|
||||||
|
|
||||||
|
`--disable-versioning'
|
||||||
|
Don't compile the shared libraries with symbol version information.
|
||||||
|
Doing this will make the resulting library incompatible with old
|
||||||
|
binaries, so it's not recommended.
|
||||||
|
|
||||||
|
`--enable-static-nss'
|
||||||
|
Compile static versions of the NSS (Name Service Switch) libraries.
|
||||||
|
This is not recommended because it defeats the purpose of NSS; a
|
||||||
|
program linked statically with the NSS libraries cannot be
|
||||||
|
dynamically reconfigured to use a different name database.
|
||||||
|
|
||||||
|
`--without-tls'
|
||||||
|
By default the C library is built with support for thread-local
|
||||||
|
storage if the used tools support it. By using `--without-tls'
|
||||||
|
this can be prevented though there generally is no reason since it
|
||||||
|
creates compatibility problems.
|
||||||
|
|
||||||
|
`--build=BUILD-SYSTEM'
|
||||||
|
`--host=HOST-SYSTEM'
|
||||||
|
These options are for cross-compiling. If you specify both
|
||||||
|
options and BUILD-SYSTEM is different from HOST-SYSTEM, `configure'
|
||||||
|
will prepare to cross-compile glibc from BUILD-SYSTEM to be used
|
||||||
|
on HOST-SYSTEM. You'll probably need the `--with-headers' option
|
||||||
|
too, and you may have to override CONFIGURE's selection of the
|
||||||
|
compiler and/or binutils.
|
||||||
|
|
||||||
|
If you only specify `--host', `configure' will prepare for a
|
||||||
|
native compile but use what you specify instead of guessing what
|
||||||
|
your system is. This is most useful to change the CPU submodel.
|
||||||
|
For example, if `configure' guesses your machine as
|
||||||
|
`i586-pc-linux-gnu' but you want to compile a library for 386es,
|
||||||
|
give `--host=i386-pc-linux-gnu' or just `--host=i386-linux' and add
|
||||||
|
the appropriate compiler flags (`-mcpu=i386' will do the trick) to
|
||||||
|
CFLAGS.
|
||||||
|
|
||||||
|
If you specify just `--build', `configure' will get confused.
|
||||||
|
|
||||||
|
To build the library and related programs, type `make'. This will
|
||||||
|
produce a lot of output, some of which may look like errors from `make'
|
||||||
|
but isn't. Look for error messages from `make' containing `***'.
|
||||||
|
Those indicate that something is seriously wrong.
|
||||||
|
|
||||||
|
The compilation process can take a long time, depending on the
|
||||||
|
configuration and the speed of your machine. Some complex modules may
|
||||||
|
take a very long time to compile, as much as several minutes on slower
|
||||||
|
machines. Do not panic if the compiler appears to hang.
|
||||||
|
|
||||||
|
If you want to run a parallel make, simply pass the `-j' option with
|
||||||
|
an appropriate numeric parameter to `make'. You need a recent GNU
|
||||||
|
`make' version, though.
|
||||||
|
|
||||||
|
To build and run test programs which exercise some of the library
|
||||||
|
facilities, type `make check'. If it does not complete successfully,
|
||||||
|
do not use the built library, and report a bug after verifying that the
|
||||||
|
problem is not already known. *Note Reporting Bugs::, for instructions
|
||||||
|
on reporting bugs. Note that some of the tests assume they are not
|
||||||
|
being run by `root'. We recommend you compile and test glibc as an
|
||||||
|
unprivileged user.
|
||||||
|
|
||||||
|
Before reporting bugs make sure there is no problem with your system.
|
||||||
|
The tests (and later installation) use some pre-existing files of the
|
||||||
|
system such as `/etc/passwd', `/etc/nsswitch.conf' and others. These
|
||||||
|
files must all contain correct and sensible content.
|
||||||
|
|
||||||
|
To format the `GNU C Library Reference Manual' for printing, type
|
||||||
|
`make dvi'. You need a working TeX installation to do this. The
|
||||||
|
distribution already includes the on-line formatted version of the
|
||||||
|
manual, as Info files. You can regenerate those with `make info', but
|
||||||
|
it shouldn't be necessary.
|
||||||
|
|
||||||
|
The library has a number of special-purpose configuration parameters
|
||||||
|
which you can find in `Makeconfig'. These can be overwritten with the
|
||||||
|
file `configparms'. To change them, create a `configparms' in your
|
||||||
|
build directory and add values as appropriate for your system. The
|
||||||
|
file is included and parsed by `make' and has to follow the conventions
|
||||||
|
for makefiles.
|
||||||
|
|
||||||
|
It is easy to configure the GNU C library for cross-compilation by
|
||||||
|
setting a few variables in `configparms'. Set `CC' to the
|
||||||
|
cross-compiler for the target you configured the library for; it is
|
||||||
|
important to use this same `CC' value when running `configure', like
|
||||||
|
this: `CC=TARGET-gcc configure TARGET'. Set `BUILD_CC' to the compiler
|
||||||
|
to use for programs run on the build system as part of compiling the
|
||||||
|
library. You may need to set `AR' and `RANLIB' to cross-compiling
|
||||||
|
versions of `ar' and `ranlib' if the native tools are not configured to
|
||||||
|
work with object files for the target you configured for.
|
||||||
|
|
||||||
|
Installing the C Library
|
||||||
|
========================
|
||||||
|
|
||||||
|
To install the library and its header files, and the Info files of the
|
||||||
|
manual, type `env LANGUAGE=C LC_ALL=C make install'. This will build
|
||||||
|
things, if necessary, before installing them; however, you should still
|
||||||
|
compile everything first. If you are installing glibc as your primary
|
||||||
|
C library, we recommend that you shut the system down to single-user
|
||||||
|
mode first, and reboot afterward. This minimizes the risk of breaking
|
||||||
|
things when the library changes out from underneath.
|
||||||
|
|
||||||
|
If you're upgrading from Linux libc5 or some other C library, you
|
||||||
|
need to replace the `/usr/include' with a fresh directory before
|
||||||
|
installing it. The new `/usr/include' should contain the Linux
|
||||||
|
headers, but nothing else.
|
||||||
|
|
||||||
|
You must first build the library (`make'), optionally check it
|
||||||
|
(`make check'), switch the include directories and then install (`make
|
||||||
|
install'). The steps must be done in this order. Not moving the
|
||||||
|
directory before install will result in an unusable mixture of header
|
||||||
|
files from both libraries, but configuring, building, and checking the
|
||||||
|
library requires the ability to compile and run programs against the old
|
||||||
|
library.
|
||||||
|
|
||||||
|
If you are upgrading from a previous installation of glibc 2.0 or
|
||||||
|
2.1, `make install' will do the entire job. You do not need to remove
|
||||||
|
the old includes - if you want to do so anyway you must then follow the
|
||||||
|
order given above.
|
||||||
|
|
||||||
|
You may also need to reconfigure GCC to work with the new library.
|
||||||
|
The easiest way to do that is to figure out the compiler switches to
|
||||||
|
make it work again (`-Wl,--dynamic-linker=/lib/ld-linux.so.2' should
|
||||||
|
work on GNU/Linux systems) and use them to recompile gcc. You can also
|
||||||
|
edit the specs file (`/usr/lib/gcc-lib/TARGET/VERSION/specs'), but that
|
||||||
|
is a bit of a black art.
|
||||||
|
|
||||||
|
You can install glibc somewhere other than where you configured it
|
||||||
|
to go by setting the `install_root' variable on the command line for
|
||||||
|
`make install'. The value of this variable is prepended to all the
|
||||||
|
paths for installation. This is useful when setting up a chroot
|
||||||
|
environment or preparing a binary distribution. The directory should be
|
||||||
|
specified with an absolute file name.
|
||||||
|
|
||||||
|
Glibc 2.2 includes a daemon called `nscd', which you may or may not
|
||||||
|
want to run. `nscd' caches name service lookups; it can dramatically
|
||||||
|
improve performance with NIS+, and may help with DNS as well.
|
||||||
|
|
||||||
|
One auxiliary program, `/usr/libexec/pt_chown', is installed setuid
|
||||||
|
`root'. This program is invoked by the `grantpt' function; it sets the
|
||||||
|
permissions on a pseudoterminal so it can be used by the calling
|
||||||
|
process. This means programs like `xterm' and `screen' do not have to
|
||||||
|
be setuid to get a pty. (There may be other reasons why they need
|
||||||
|
privileges.) If you are using a 2.1 or newer Linux kernel with the
|
||||||
|
`devptsfs' or `devfs' filesystems providing pty slaves, you don't need
|
||||||
|
this program; otherwise you do. The source for `pt_chown' is in
|
||||||
|
`login/programs/pt_chown.c'.
|
||||||
|
|
||||||
|
After installation you might want to configure the timezone and
|
||||||
|
locale installation of your system. The GNU C library comes with a
|
||||||
|
locale database which gets configured with `localedef'. For example, to
|
||||||
|
set up a German locale with name `de_DE', simply issue the command
|
||||||
|
`localedef -i de_DE -f ISO-8859-1 de_DE'. To configure all locales
|
||||||
|
that are supported by glibc, you can issue from your build directory the
|
||||||
|
command `make localedata/install-locales'.
|
||||||
|
|
||||||
|
To configure the locally used timezone, set the `TZ' environment
|
||||||
|
variable. The script `tzselect' helps you to select the right value.
|
||||||
|
As an example, for Germany, `tzselect' would tell you to use
|
||||||
|
`TZ='Europe/Berlin''. For a system wide installation (the given paths
|
||||||
|
are for an installation with `--prefix=/usr'), link the timezone file
|
||||||
|
which is in `/usr/share/zoneinfo' to the file `/etc/localtime'. For
|
||||||
|
Germany, you might execute `ln -s /usr/share/zoneinfo/Europe/Berlin
|
||||||
|
/etc/localtime'.
|
||||||
|
|
||||||
|
Recommended Tools for Compilation
|
||||||
|
=================================
|
||||||
|
|
||||||
|
We recommend installing the following GNU tools before attempting to
|
||||||
|
build the GNU C library:
|
||||||
|
|
||||||
|
* GNU `make' 3.79 or newer
|
||||||
|
|
||||||
|
You need the latest version of GNU `make'. Modifying the GNU C
|
||||||
|
Library to work with other `make' programs would be so difficult
|
||||||
|
that we recommend you port GNU `make' instead. *Really.* We
|
||||||
|
recommend GNU `make' version 3.79. All earlier versions have
|
||||||
|
severe bugs or lack features.
|
||||||
|
|
||||||
|
* GCC 3.4 or newer, GCC 4.1 recommended
|
||||||
|
|
||||||
|
The GNU C library can only be compiled with the GNU C compiler
|
||||||
|
family. For the 2.3 releases, GCC 3.2 or higher is required; GCC
|
||||||
|
3.4 is the compiler we advise to use for 2.3 versions. For the
|
||||||
|
2.4 release, GCC 3.4 or higher is required; as of this writing,
|
||||||
|
GCC 4.1 is the compiler we advise to use for current versions. On
|
||||||
|
certain machines including `powerpc64', compilers prior to GCC 4.0
|
||||||
|
have bugs that prevent them compiling the C library code in the
|
||||||
|
2.4 release. On other machines, GCC 4.1 is required to build the C
|
||||||
|
library with support for the correct `long double' type format;
|
||||||
|
these include `powerpc' (32 bit), `s390' and `s390x'.
|
||||||
|
|
||||||
|
You can use whatever compiler you like to compile programs that
|
||||||
|
use GNU libc, but be aware that both GCC 2.7 and 2.8 have bugs in
|
||||||
|
their floating-point support that may be triggered by the math
|
||||||
|
library.
|
||||||
|
|
||||||
|
Check the FAQ for any special compiler issues on particular
|
||||||
|
platforms.
|
||||||
|
|
||||||
|
* GNU `binutils' 2.15 or later
|
||||||
|
|
||||||
|
You must use GNU `binutils' (as and ld) to build the GNU C library.
|
||||||
|
No other assembler or linker has the necessary functionality at the
|
||||||
|
moment.
|
||||||
|
|
||||||
|
* GNU `texinfo' 3.12f
|
||||||
|
|
||||||
|
To correctly translate and install the Texinfo documentation you
|
||||||
|
need this version of the `texinfo' package. Earlier versions do
|
||||||
|
not understand all the tags used in the document, and the
|
||||||
|
installation mechanism for the info files is not present or works
|
||||||
|
differently.
|
||||||
|
|
||||||
|
* GNU `awk' 3.0, or higher
|
||||||
|
|
||||||
|
`Awk' is used in several places to generate files. `gawk' 3.0 is
|
||||||
|
known to work.
|
||||||
|
|
||||||
|
* Perl 5
|
||||||
|
|
||||||
|
Perl is not required, but it is used if present to test the
|
||||||
|
installation. We may decide to use it elsewhere in the future.
|
||||||
|
|
||||||
|
* GNU `sed' 3.02 or newer
|
||||||
|
|
||||||
|
`Sed' is used in several places to generate files. Most scripts
|
||||||
|
work with any version of `sed'. The known exception is the script
|
||||||
|
`po2test.sed' in the `intl' subdirectory which is used to generate
|
||||||
|
`msgs.h' for the test suite. This script works correctly only
|
||||||
|
with GNU `sed' 3.02. If you like to run the test suite, you
|
||||||
|
should definitely upgrade `sed'.
|
||||||
|
|
||||||
|
|
||||||
|
If you change any of the `configure.in' files you will also need
|
||||||
|
|
||||||
|
* GNU `autoconf' 2.53 or higher
|
||||||
|
|
||||||
|
and if you change any of the message translation files you will need
|
||||||
|
|
||||||
|
* GNU `gettext' 0.10.36 or later
|
||||||
|
|
||||||
|
You may also need these packages if you upgrade your source tree using
|
||||||
|
patches, although we try to avoid this.
|
||||||
|
|
||||||
|
Specific advice for GNU/Linux systems
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
If you are installing GNU libc on a GNU/Linux system, you need to have
|
||||||
|
the header files from a 2.2 or newer kernel around for reference. For
|
||||||
|
some architectures, like ia64, sh and hppa, you need at least headers
|
||||||
|
from kernel 2.3.99 (sh and hppa) or 2.4.0 (ia64). You do not need to
|
||||||
|
use that kernel, just have its headers where glibc can access at them.
|
||||||
|
The easiest way to do this is to unpack it in a directory such as
|
||||||
|
`/usr/src/linux-2.2.1'. In that directory, run `make config' and
|
||||||
|
accept all the defaults. Then run `make include/linux/version.h'.
|
||||||
|
Finally, configure glibc with the option
|
||||||
|
`--with-headers=/usr/src/linux-2.2.1/include'. Use the most recent
|
||||||
|
kernel you can get your hands on.
|
||||||
|
|
||||||
|
An alternate tactic is to unpack the 2.2 kernel and run `make
|
||||||
|
config' as above; then, rename or delete `/usr/include', create a new
|
||||||
|
`/usr/include', and make symbolic links of `/usr/include/linux' and
|
||||||
|
`/usr/include/asm' into the kernel sources. You can then configure
|
||||||
|
glibc with no special options. This tactic is recommended if you are
|
||||||
|
upgrading from libc5, since you need to get rid of the old header files
|
||||||
|
anyway.
|
||||||
|
|
||||||
|
After installing GNU libc, you may need to remove or rename
|
||||||
|
`/usr/include/linux' and `/usr/include/asm', and replace them with
|
||||||
|
copies of `include/linux' and `include/asm-$ARCHITECTURE' taken from
|
||||||
|
the Linux source package which supplied kernel headers for building the
|
||||||
|
library. ARCHITECTURE will be the machine architecture for which the
|
||||||
|
library was built, such as `i386' or `alpha'. You do not need to do
|
||||||
|
this if you did not specify an alternate kernel header source using
|
||||||
|
`--with-headers'. The intent here is that these directories should be
|
||||||
|
copies of, *not* symlinks to, the kernel headers used to build the
|
||||||
|
library.
|
||||||
|
|
||||||
|
Note that `/usr/include/net' and `/usr/include/scsi' should *not* be
|
||||||
|
symlinks into the kernel sources. GNU libc provides its own versions
|
||||||
|
of these files.
|
||||||
|
|
||||||
|
GNU/Linux expects some components of the libc installation to be in
|
||||||
|
`/lib' and some in `/usr/lib'. This is handled automatically if you
|
||||||
|
configure glibc with `--prefix=/usr'. If you set some other prefix or
|
||||||
|
allow it to default to `/usr/local', then all the components are
|
||||||
|
installed there.
|
||||||
|
|
||||||
|
If you are upgrading from libc5, you need to recompile every shared
|
||||||
|
library on your system against the new library for the sake of new code,
|
||||||
|
but keep the old libraries around for old binaries to use. This is
|
||||||
|
complicated and difficult. Consult the Glibc2 HOWTO at
|
||||||
|
`http://www.imaxx.net/~thrytis/glibc' for details.
|
||||||
|
|
||||||
|
You cannot use `nscd' with 2.0 kernels, due to bugs in the
|
||||||
|
kernel-side thread support. `nscd' happens to hit these bugs
|
||||||
|
particularly hard, but you might have problems with any threaded
|
||||||
|
program.
|
||||||
|
|
||||||
|
Reporting Bugs
|
||||||
|
==============
|
||||||
|
|
||||||
|
There are probably bugs in the GNU C library. There are certainly
|
||||||
|
errors and omissions in this manual. If you report them, they will get
|
||||||
|
fixed. If you don't, no one will ever know about them and they will
|
||||||
|
remain unfixed for all eternity, if not longer.
|
||||||
|
|
||||||
|
It is a good idea to verify that the problem has not already been
|
||||||
|
reported. Bugs are documented in two places: The file `BUGS' describes
|
||||||
|
a number of well known bugs and the bug tracking system has a WWW
|
||||||
|
interface at `http://sources.redhat.com/bugzilla/'. The WWW interface
|
||||||
|
gives you access to open and closed reports. A closed report normally
|
||||||
|
includes a patch or a hint on solving the problem.
|
||||||
|
|
||||||
|
To report a bug, first you must find it. With any luck, this will
|
||||||
|
be the hard part. Once you've found a bug, make sure it's really a
|
||||||
|
bug. A good way to do this is to see if the GNU C library behaves the
|
||||||
|
same way some other C library does. If so, probably you are wrong and
|
||||||
|
the libraries are right (but not necessarily). If not, one of the
|
||||||
|
libraries is probably wrong. It might not be the GNU library. Many
|
||||||
|
historical Unix C libraries permit things that we don't, such as
|
||||||
|
closing a file twice.
|
||||||
|
|
||||||
|
If you think you have found some way in which the GNU C library does
|
||||||
|
not conform to the ISO and POSIX standards (*note Standards and
|
||||||
|
Portability::), that is definitely a bug. Report it!
|
||||||
|
|
||||||
|
Once you're sure you've found a bug, try to narrow it down to the
|
||||||
|
smallest test case that reproduces the problem. In the case of a C
|
||||||
|
library, you really only need to narrow it down to one library function
|
||||||
|
call, if possible. This should not be too difficult.
|
||||||
|
|
||||||
|
The final step when you have a simple test case is to report the bug.
|
||||||
|
Do this using the WWW interface to the bug database.
|
||||||
|
|
||||||
|
If you are not sure how a function should behave, and this manual
|
||||||
|
doesn't tell you, that's a bug in the manual. Report that too! If the
|
||||||
|
function's behavior disagrees with the manual, then either the library
|
||||||
|
or the manual has a bug, so report the disagreement. If you find any
|
||||||
|
errors or omissions in this manual, please report them to the bug
|
||||||
|
database. If you refer to specific sections of the manual, please
|
||||||
|
include the section names for easier identification.
|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
This file contains the copying permission notices for various files in the
|
||||||
|
GNU C Library distribution that have copyright owners other than the Free
|
||||||
|
Software Foundation. These notices all require that a copy of the notice
|
||||||
|
be included in the accompanying documentation and be distributed with
|
||||||
|
binary distributions of the code, so be sure to include this file along
|
||||||
|
with any binary distributions derived from the GNU C Library.
|
||||||
|
|
||||||
|
|
||||||
|
All code incorporated from 4.4 BSD is distributed under the following
|
||||||
|
license:
|
||||||
|
|
||||||
|
Copyright (C) 1991 Regents of the University of California.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
3. [This condition was removed.]
|
||||||
|
4. Neither the name of the University nor the names of its contributors
|
||||||
|
may be used to endorse or promote products derived from this software
|
||||||
|
without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGE.
|
||||||
|
|
||||||
|
The DNS resolver code, taken from BIND 4.9.5, is copyrighted both by
|
||||||
|
UC Berkeley and by Digital Equipment Corporation. The DEC portions
|
||||||
|
are under the following license:
|
||||||
|
|
||||||
|
Portions Copyright (C) 1993 by Digital Equipment Corporation.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies, and
|
||||||
|
that the name of Digital Equipment Corporation not be used in
|
||||||
|
advertising or publicity pertaining to distribution of the document or
|
||||||
|
software without specific, written prior permission.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED ``AS IS'' AND DIGITAL EQUIPMENT CORP.
|
||||||
|
DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
|
||||||
|
DIGITAL EQUIPMENT CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
|
||||||
|
FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
|
||||||
|
WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
The Sun RPC support (from rpcsrc-4.0) is covered by the following
|
||||||
|
license:
|
||||||
|
|
||||||
|
Copyright (C) 1984, Sun Microsystems, Inc.
|
||||||
|
|
||||||
|
Sun RPC is a product of Sun Microsystems, Inc. and is provided for
|
||||||
|
unrestricted use provided that this legend is included on all tape media
|
||||||
|
and as a part of the software program in whole or part. Users may copy
|
||||||
|
or modify Sun RPC without charge, but are not authorized to license or
|
||||||
|
distribute it to anyone else except as part of a product or program
|
||||||
|
developed by the user.
|
||||||
|
|
||||||
|
SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
|
||||||
|
WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
|
||||||
|
|
||||||
|
Sun RPC is provided with no support and without any obligation on the
|
||||||
|
part of Sun Microsystems, Inc. to assist in its use, correction,
|
||||||
|
modification or enhancement.
|
||||||
|
|
||||||
|
SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
|
||||||
|
INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
|
||||||
|
OR ANY PART THEREOF.
|
||||||
|
|
||||||
|
In no event will Sun Microsystems, Inc. be liable for any lost revenue
|
||||||
|
or profits or other special, indirect and consequential damages, even if
|
||||||
|
Sun has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
|
||||||
|
The following CMU license covers some of the support code for Mach,
|
||||||
|
derived from Mach 3.0:
|
||||||
|
|
||||||
|
Mach Operating System
|
||||||
|
Copyright (C) 1991,1990,1989 Carnegie Mellon University
|
||||||
|
All Rights Reserved.
|
||||||
|
|
||||||
|
Permission to use, copy, modify and distribute this software and its
|
||||||
|
documentation is hereby granted, provided that both the copyright
|
||||||
|
notice and this permission notice appear in all copies of the
|
||||||
|
software, derivative works or modified versions, and any portions
|
||||||
|
thereof, and that both notices appear in supporting documentation.
|
||||||
|
|
||||||
|
CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS ``AS IS''
|
||||||
|
CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
|
||||||
|
ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
Carnegie Mellon requests users of this software to return to
|
||||||
|
|
||||||
|
Software Distribution Coordinator
|
||||||
|
School of Computer Science
|
||||||
|
Carnegie Mellon University
|
||||||
|
Pittsburgh PA 15213-3890
|
||||||
|
|
||||||
|
or Software.Distribution@CS.CMU.EDU any improvements or
|
||||||
|
extensions that they make and grant Carnegie Mellon the rights to
|
||||||
|
redistribute these changes.
|
||||||
|
|
||||||
|
The file if_ppp.h is under the following CMU license:
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
3. Neither the name of the University nor the names of its contributors
|
||||||
|
may be used to endorse or promote products derived from this software
|
||||||
|
without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY CARNEGIE MELLON UNIVERSITY AND
|
||||||
|
CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
IN NO EVENT SHALL THE UNIVERSITY OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||||
|
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
||||||
|
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||||||
|
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
The following license covers the files from Intel's "Highly Optimized
|
||||||
|
Mathematical Functions for Itanium" collection:
|
||||||
|
|
||||||
|
Intel License Agreement
|
||||||
|
|
||||||
|
Copyright (c) 2000, Intel Corporation
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* The name of Intel Corporation may not be used to endorse or promote
|
||||||
|
products derived from this software without specific prior written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR
|
||||||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
The files inet/getnameinfo.c and sysdeps/posix/getaddrinfo.c are copyright
|
||||||
|
(C) by Craig Metz and are distributed under the following license:
|
||||||
|
|
||||||
|
/* The Inner Net License, Version 2.00
|
||||||
|
|
||||||
|
The author(s) grant permission for redistribution and use in source and
|
||||||
|
binary forms, with or without modification, of the software and documentation
|
||||||
|
provided that the following conditions are met:
|
||||||
|
|
||||||
|
0. If you receive a version of the software that is specifically labelled
|
||||||
|
as not being for redistribution (check the version message and/or README),
|
||||||
|
you are not permitted to redistribute that version of the software in any
|
||||||
|
way or form.
|
||||||
|
1. All terms of the all other applicable copyrights and licenses must be
|
||||||
|
followed.
|
||||||
|
2. Redistributions of source code must retain the authors' copyright
|
||||||
|
notice(s), this list of conditions, and the following disclaimer.
|
||||||
|
3. Redistributions in binary form must reproduce the authors' copyright
|
||||||
|
notice(s), this list of conditions, and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
4. [The copyright holder has authorized the removal of this clause.]
|
||||||
|
5. Neither the name(s) of the author(s) nor the names of its contributors
|
||||||
|
may be used to endorse or promote products derived from this software
|
||||||
|
without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY ITS AUTHORS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||||
|
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
If these license terms cause you a real problem, contact the author. */
|
|
@ -0,0 +1,6 @@
|
||||||
|
For Linux and Cygwin, we use the built-in regular expression library.
|
||||||
|
For the Windows version, we need to include our own version.
|
||||||
|
|
||||||
|
The source was obtained from:
|
||||||
|
|
||||||
|
http://gnuwin32.sourceforge.net/packages/regex.htm
|
|
@ -0,0 +1,26 @@
|
||||||
|
/* Copyright (C) 1996 Free Software Foundation, Inc.
|
||||||
|
This file is part of the GNU C Library.
|
||||||
|
|
||||||
|
The GNU C Library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
The GNU C Library 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
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with the GNU C Library; if not, write to the Free
|
||||||
|
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||||
|
02111-1307 USA. */
|
||||||
|
|
||||||
|
#ifndef _RE_COMP_H
|
||||||
|
#define _RE_COMP_H 1
|
||||||
|
|
||||||
|
/* This is only a wrapper around the <regex.h> file. XPG4.2 mentions
|
||||||
|
this name. */
|
||||||
|
#include <regex.h>
|
||||||
|
|
||||||
|
#endif /* re_comp.h */
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue