diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 82c129b0..0cc4d34d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -80,15 +80,7 @@ jobs:
build_type: 'Release',
cmake_extra_flags: ''
}
- - {
- name: 'Ubuntu 18.04',
- os: ubuntu-18.04,
- cc: 'gcc',
- cxx: 'g++',
- arch: 'x86_64',
- build_type: 'Release',
- cmake_extra_flags: ''
- }
+
steps:
- name: checkout
uses: actions/checkout@v2
diff --git a/.github/workflows/codeql-analysis-python.yml b/.github/workflows/codeql-analysis-python.yml
new file mode 100644
index 00000000..a47a8f8d
--- /dev/null
+++ b/.github/workflows/codeql-analysis-python.yml
@@ -0,0 +1,64 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL - Python"
+
+on:
+ push:
+ branches: [ dev ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ dev ]
+ schedule:
+ - cron: '25 8 * * 4'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'python' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
+ # Learn more about CodeQL language support at https://git.io/codeql-language-support
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ with:
+ languages: ${{ matrix.language }}
+ setup-python-dependencies: true
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v3
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 https://git.io/JvXDl
+
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 7134f213..a86300f3 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -9,7 +9,7 @@
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
-name: "CodeQL"
+name: "CodeQL - CPP"
on:
push:
@@ -32,19 +32,20 @@ jobs:
strategy:
fail-fast: false
matrix:
- language: [ 'cpp', 'python' ]
+ language: [ 'cpp' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@v1
+ uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
+ setup-python-dependencies: true
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
@@ -53,7 +54,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
- uses: github/codeql-action/autobuild@v1
+ uses: github/codeql-action/autobuild@v3
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -70,4 +71,4 @@ jobs:
make test
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
+ uses: github/codeql-action/analyze@v3
diff --git a/.gitignore b/.gitignore
index 659c845b..b917a7ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -109,5 +109,5 @@ $RECYCLE.BIN/
*.dSYM
# cmake
-build/
+build*/
tmp/
\ No newline at end of file
diff --git a/CHANGES.md b/CHANGES.md
index ba28d1d2..0903e9ea 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -2,20 +2,45 @@
# Revision History #
-## Version 1.7 -- Under Development ('dev' branch) ##
+## Version 1.8 -- Development Version
+
+### New Features: ###
+
+- [http://www.aprs.org/aprs11/tocalls.txt](http://www.aprs.org/aprs11/tocalls.txt) has been abandoned since the end of 2021. [https://github.com/aprsorg/aprs-deviceid](https://github.com/aprsorg/aprs-deviceid) is now considered to be the authoritative source of truth for the vendor/model encoding.
+
+## Version 1.7 -- October 2023 ##
+
+
+### New Documentation: ###
+
+Additional documentation location to slow down growth of main repository. [https://github.com/wb2osz/direwolf-doc](https://github.com/wb2osz/direwolf-doc) . These are more oriented toward achieving a goal and understanding, as opposed to the User Guide which describes the functionality.
+
+- ***APRS Digipeaters***
+
+- ***Internal Packet Routing***
+
+- ***Radio Interface Guide***
+
+- ***Successful IGate Operation***
+
+- ***Understanding APRS Packets***
### New Features: ###
-- Additional documentation location to slow down growth of main repository. [https://github.com/wb2osz/direwolf-doc](https://github.com/wb2osz/direwolf-doc)
+
- New ICHANNEL configuration option to map a KISS client application channel to APRS-IS. Packets from APRS-IS will be presented to client applications as the specified channel. Packets sent, by client applications, to that channel will go to APRS-IS rather than a radio channel. Details in ***Internal-Packet-Routing.pdf***.
- New variable speed option for gen_packets. For example, "-v 5,0.1" would generate packets from 5% too slow to 5% too fast with increments of 0.1. Some implementations might have imprecise timing. Use this to test how well TNCs tolerate sloppy timing.
-- Improved Layer 2 Protocol [(IL2P)](https://en.wikipedia.org/wiki/FX.25_Forward_Error_Correction). Use "-I 1" on command line to enable transmit for first channel. Compatible with Nino TNC for 1200 and 9600 bps.
+- Improved Layer 2 Protocol [(IL2P)](https://en.wikipedia.org/wiki/FX.25_Forward_Error_Correction). Compatible with Nino TNC for 1200 and 9600 bps. Use "-I 1" on command line to enable transmit for first channel. For more general case, add to config file (simplified version, see User Guide for more details):
-- Limited support for CM109/CM119 GPIO PTT on Windows.
+ > After: "CHANNEL 1" (or other channel)
+ >
+ > Add: "IL2PTX 1"
+
+- Limited support for CM108/CM119 GPIO PTT on Windows.
- Dire Wolf now advertises itself using DNS Service Discovery. This allows suitable APRS / Packet Radio applications to find a network KISS TNC without knowing the IP address or TCP port. Thanks to Hessu for providing this. Currently available only for Linux and Mac OSX. [Read all about it here.](https://github.com/hessu/aprs-specs/blob/master/TCP-KISS-DNS-SD.md)
@@ -30,6 +55,21 @@
> Add: "FX25TX 1" (or 16 or 32 or 64)
+
+### Bugs Fixed: ###
+
+- The t/m packet filter incorrectly included bulletins. It now allows only "messages" to specific stations. Use of t/m is discouraged. i/180 is the preferred filter for messages to users recently heard locally.
+
+- Packet filtering now skips over any third party header before classifying packet types.
+
+- Fixed build for Alpine Linux.
+
+### Notes: ###
+
+The Windows binary distribution now uses gcc (MinGW) version 11.3.0.
+The Windows version is built for both 32 and 64 bit operating systems.
+Use the 64 bit version if possible; it runs considerably faster.
+
## Version 1.6 -- October 2020 ##
### New Build Procedure: ###
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e44f99b4..58fcb09b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,10 +1,10 @@
-cmake_minimum_required(VERSION 3.1.0)
+cmake_minimum_required(VERSION 3.5.0)
project(direwolf)
# configure version
set(direwolf_VERSION_MAJOR "1")
-set(direwolf_VERSION_MINOR "7")
+set(direwolf_VERSION_MINOR "8")
set(direwolf_VERSION_PATCH "0")
set(direwolf_VERSION_SUFFIX "Development")
@@ -265,9 +265,33 @@ endif(WIN32 OR CYGWIN)
# requirements
include(CheckSymbolExists)
+
# Some platforms provide their own strlcpy & strlcat. (BSD, MacOSX)
-# Others don't so we provide our own. (Most, but not all Linux)
-# Define the preprocessor macro so libgps does not supply its own version.
+# Others don't so we provide our own. (Windows, most, but not all Linux)
+# Here we detect whether these are provided by the OS and set a symbol
+# so that:
+# (1) libgps does not supply its own version.
+# (2) we know whether we need to supply our own copy.
+#
+# This was all working fine until these were added to the gnu c library 2.38.
+# References:
+# - https://www.gnu.org/software/libc/sources.html
+# - https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=NEWS;hb=HEAD
+#
+# This test is not detecting them for glibc 2.38 resulting in a conflict.
+# Why? Are they declared in a different file or in some strange way?
+#
+# This is how they are declared in include/string.h:
+#
+# extern __typeof (strlcpy) __strlcpy;
+# libc_hidden_proto (__strlcpy)
+# extern __typeof (strlcat) __strlcat;
+# libc_hidden_proto (__strlcat)
+#
+# Apparently cmake does not recognize this style.
+# Keep this here for BSD type systems where it behaves as expected.
+# We will need to add a hack in direwolf.h to define these if glibc version >= 2.38.
+
check_symbol_exists(strlcpy string.h HAVE_STRLCPY)
if(HAVE_STRLCPY)
add_compile_options(-DHAVE_STRLCPY)
@@ -296,6 +320,14 @@ else()
set(HAMLIB_LIBRARIES "")
endif()
+find_package(gpiod)
+if(GPIOD_FOUND)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_GPIOD")
+else()
+ set(GPIOD_INCLUDE_DIRS "")
+ set(GPIOD_LIBRARIES "")
+endif()
+
if(LINUX)
find_package(ALSA REQUIRED)
if(ALSA_FOUND)
diff --git a/README.md b/README.md
index 0bf92daf..3006a1ef 100644
--- a/README.md
+++ b/README.md
@@ -9,11 +9,11 @@ Why waste $200 and settle for mediocre receive performance from a 1980's technol

-Dire Wolf now includes [FX.25](https://en.wikipedia.org/wiki/FX.25_Forward_Error_Correction) which adds Forward Error Correction (FEC) in a way that is completely compatible with existing systems. If both ends are capable of FX.25, your information will continue to get through under conditions where regular AX.25 is completely useless. This was originally developed for satellites and is now seeing widespread use on HF.
+Dire Wolf includes [FX.25](https://en.wikipedia.org/wiki/FX.25_Forward_Error_Correction) which adds Forward Error Correction (FEC) in a way that is completely compatible with existing systems. If both ends are capable of FX.25, your information will continue to get through under conditions where regular AX.25 is completely useless. This was originally developed for satellites and is now seeing widespread use on HF.

-Version 1.7 adds [IL2P](https://en.wikipedia.org/wiki/Improved_Layer_2_Protocol), a different method of FEC with less overhead.
+Version 1.7 adds [IL2P](https://en.wikipedia.org/wiki/Improved_Layer_2_Protocol), a different method of FEC with less overhead but it is not compatible with AX.25.
@@ -114,12 +114,16 @@ It can also be used as a virtual TNC for other applications such as [APRSIS32](h
## Documentation ##
+
[Stable Version](https://github.com/wb2osz/direwolf/tree/master/doc)
-[Latest Development Version](https://github.com/wb2osz/direwolf/tree/dev/doc)
+[Latest Development Version ("dev" branch)](https://github.com/wb2osz/direwolf/tree/dev/doc)
+
+[Additional Topics](https://github.com/wb2osz/direwolf-doc)
-[Power Point presentation](https://github.com/wb2osz/direwolf-presentation) -- Why not give a talk at a local club meeting?
+[Power Point presentations](https://github.com/wb2osz/direwolf-presentation) -- Why not give a talk at a local club meeting?
+Youtube has many interesting and helpful videos. Searching for [direwolf tnc](https://www.youtube.com/results?search_query=direwolf+tnc) or [direwolf aprs](https://www.youtube.com/results?search_query=direwolf+aprs) will produce the most relevant results.
## Installation ##
diff --git a/cmake/modules/FindCompiler.cmake b/cmake/modules/FindCompiler.cmake
index fe036e4b..91e1b89c 100644
--- a/cmake/modules/FindCompiler.cmake
+++ b/cmake/modules/FindCompiler.cmake
@@ -5,9 +5,9 @@ elseif(NOT DEFINED C_GCC AND CMAKE_CXX_COMPILER_ID MATCHES "GNU")
set(C_GCC 1)
elseif(NOT DEFINED C_MSVC AND CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
set(C_MSVC 1)
- if(MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS_EQUAL 1929)
+ if(MSVC_VERSION GREATER 1919 AND MSVC_VERSION LESS 1926)
set(VS2019 ON)
- elseif(MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS_EQUAL 1919)
+ elseif(MSVC_VERSION GREATER 1910 AND MSVC_VERSION LESS 1919)
set(VS2017 ON)
elseif(MSVC_VERSION GREATER 1899 AND MSVC_VERSION LESS 1910)
set(VS2015 ON)
diff --git a/cmake/modules/Findgpiod.cmake b/cmake/modules/Findgpiod.cmake
new file mode 100644
index 00000000..bf5be305
--- /dev/null
+++ b/cmake/modules/Findgpiod.cmake
@@ -0,0 +1,23 @@
+# - Try to find libgpiod
+# Once done this will define
+# GPIOD_FOUND - System has libgpiod
+# GPIOD_INCLUDE_DIRS - The libgpiod include directories
+# GPIOD_LIBRARIES - The libraries needed to use libgpiod
+# GPIOD_DEFINITIONS - Compiler switches required for using libgpiod
+
+find_package(PkgConfig)
+pkg_check_modules(PC_GPIOD QUIET gpiod)
+
+find_path(GPIOD_INCLUDE_DIR gpiod.h)
+find_library(GPIOD_LIBRARY NAMES gpiod)
+
+include(FindPackageHandleStandardArgs)
+# handle the QUIETLY and REQUIRED arguments and set GPIOD_FOUND to TRUE
+# if all listed variables are TRUE
+find_package_handle_standard_args(gpiod DEFAULT_MSG
+ GPIOD_LIBRARY GPIOD_INCLUDE_DIR)
+
+mark_as_advanced(GPIOD_INCLUDE_DIR GPIOD_LIBRARY)
+
+set(GPIOD_LIBRARIES ${GPIOD_LIBRARY})
+set(GPIOD_INCLUDE_DIRS ${GPIOD_INCLUDE_DIR})
diff --git a/conf/99-direwolf-cmedia.rules b/conf/99-direwolf-cmedia.rules
index 24e7c160..94e1828f 100644
--- a/conf/99-direwolf-cmedia.rules
+++ b/conf/99-direwolf-cmedia.rules
@@ -28,3 +28,9 @@ SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0d8c", GROUP="audio", MODE="0660"
#
# Read the User Guide and run the "cm108" application for more information.
#
+
+#
+# Same thing for the "All In One Cable."
+#
+
+SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="7388", GROUP="audio", MODE="0660"
diff --git a/conf/generic.conf b/conf/generic.conf
index 887dc229..9a19d8a2 100644
--- a/conf/generic.conf
+++ b/conf/generic.conf
@@ -29,16 +29,21 @@
%C%# Extensive documentation can be found here:
%C%# Stable release - https://github.com/wb2osz/direwolf/tree/master/doc
%C%# Latest development - https://github.com/wb2osz/direwolf/tree/dev/doc
+%C%# Additional topics - https://github.com/wb2osz/direwolf-doc
%C%#
-%W%# The complete documentation set can also be found in the doc folder.
-%L%# The complete documentation set can also be found in
+%W%# The basic documentation set can also be found in the doc folder.
+%L%# The basic documentation set can also be found in
%L%# /usr/local/share/doc/direwolf/ or /usr/share/doc/direwolf/
%L%# Concise "man" pages are also available for Linux.
%M%# /usr/local/share/doc/direwolf/ or /usr/share/doc/direwolf/
%M%# Concise "man" pages are also available for Mac OSX.
%C%#
+%C%# Questions??? Join the discussion forum: https://groups.io/g/direwolf
+%C%#
+%C%#
%C%# This sample file does not have examples for all of the possibilities.
-%C%# Consult the User Guide for more details on configuration options.%C%#
+%C%# Consult the User Guide for more details on configuration options
+%C%# and other documents for more details for different uses.
%C%#
%C%# These are the most likely settings you might change:
%C%#
@@ -93,6 +98,11 @@
%C%# Many people will simply use the default sound device.
%C%# Some might want to use an alternative device by choosing it here.
%C%#
+%C%#
+%C%# Many examples of radio interfaces and PTT options can be found in:
+%C%# https://github.com/wb2osz/direwolf-doc/blob/main/Radio-Interface-Guide.pdf
+%C%#
+%C%#
%R% ---------- Windows ----------
%R%
%W%# When the Windows version starts up, it displays something like
@@ -231,14 +241,14 @@
%C%# It can be up to 6 letters and digits with an optional ssid.
%C%# The APRS specification requires that it be upper case.
%C%#
-%C%# Example (don't use this unless you are me): MYCALL WB2OSZ-5
+%C%# Example (don't use this unless you are me): MYCALL WB2OSZ-5
%C%#
%C%
%C%MYCALL N0CALL
%C%
%C%#
%C%# Pick a suitable modem speed based on your situation.
-%C%# 1200 Most common for VHF/UHF. Default if not specified.
+%C%# 1200 Most common for VHF/UHF. This is the default if not specified.
%C%# 2400 QPSK compatible with MFJ-2400, and probably PK232-2400 & KPC-2400.
%C%# 300 Low speed for HF SSB. Default tones 1600 & 1800.
%C%# EAS Emergency Alert System (EAS) Specific Area Message Encoding (SAME).
@@ -263,6 +273,10 @@
%C%
%C%#DTMF
%C%
+%C%# Push to Talk (PTT) can be confusing because there are so many different cases.
+%C%# Radio-Interface-Guide.pdf in https://github.com/wb2osz/direwolf-doc
+%C%# goes into detail about the various options.
+%C%
%L%# If using a C-Media CM108/CM119 or similar USB Audio Adapter,
%L%# you can use a GPIO pin for PTT control. This is very convenient
%L%# because a single USB connection is used for both audio and PTT.
@@ -290,6 +304,7 @@
%C%#PTT COM1 RTS
%C%#PTT COM1 RTS -DTR
%L%#PTT /dev/ttyUSB0 RTS
+%L%#PTT /dev/ttyUSB0 RTS -DTR
%C%
%L%#
%L%# On Linux, you can also use general purpose I/O pins if
@@ -301,7 +316,7 @@
%L%
%L%#PTT GPIO 25
%L%
-%C%# The Data Carrier Detect (DCD) signal can be sent to the same places
+%C%# The Data Carrier Detect (DCD) signal can be sent to most of the same places
%C%# as the PTT signal. This could be used to light up an LED like a normal TNC.
%C%
%C%#DCD COM1 -DTR
@@ -372,9 +387,8 @@
%C%# This is not a global setting.
%C%# It applies only the the most recent CHANNEL specified.
%C%#
-%C%# 0 - Don't try to repair.
-%C%# 1 - Attempt to fix single bit error. (default)
-%C%# ... see User Guide for more values and in-depth discussion.
+%C%# 0 - Don't try to repair. (default)
+%C%# 1 - Attempt to fix single bit error.
%C%#
%C%
%C%#FIX_BITS 0
@@ -399,15 +413,21 @@
%C%# Example:
%C%#
%C%# This results in a broadcast once every 10 minutes.
-%C%# Every half hour, it can travel via two digipeater hops.
+%C%# Every half hour, it can travel via one digipeater hop.
%C%# The others are kept local.
%C%#
%C%
-%C%#PBEACON delay=1 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
+%C%#PBEACON delay=1 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
%C%#PBEACON delay=11 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA"
%C%#PBEACON delay=21 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA"
%C%
-%C%
+%C%#
+%C%# Did you know that APRS comments and messages can contain UTF-8 characters, not only plain ASCII?
+%C%#
+%C%#PBEACON delay=1 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W comment=" Did you know that APRS comments and messages can contain UTF-8 characters? \xe0\xb8\xa7\xe0\xb8\xb4\xe0\xb8\x97\xe0\xb8\xa2\xe0\xb8\xb8\xe0\xb8\xaa\xe0\xb8\xa1\xe0\xb8\xb1\xe0\xb8\x84\xe0\xb8\xa3\xe0\xb9\x80\xe0\xb8\xa5\xe0\xb9\x88\xe0\xb8\x99"
+%C%#PBEACON delay=11 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W comment=" Did you know that APRS comments and messages can contain UTF-8 characters? \xce\xa1\xce\xb1\xce\xb4\xce\xb9\xce\xbf\xce\xb5\xcf\x81\xce\xb1\xcf\x83\xce\xb9\xcf\x84\xce\xb5\xcf\x87\xce\xbd\xce\xb9\xcf\x83\xce\xbc\xcf\x8c\xcf\x82"
+%C%#PBEACON delay=21 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W comment=" Did you know that APRS comments and messages can contain UTF-8 characters? \xe3\x82\xa2\xe3\x83\x9e\xe3\x83\x81\xe3\x83\xa5\xe3\x82\xa2\xe7\x84\xa1\xe7\xb7\x9a"
+%C%#
%C%# With UTM coordinates instead of latitude and longitude.
%C%
%C%#PBEACON delay=1 every=10 overlay=S symbol="digi" zone=19T easting=307477 northing=4720178
@@ -443,17 +463,11 @@
%C%# the "#" from the beginning of the line below.
%C%#
%C%
-%C%#DIGIPEAT 0 0 ^WIDE[3-7]-[1-7]$|^TEST$ ^WIDE[12]-[12]$ TRACE
+%C%#DIGIPEAT 0 0 ^WIDE[3-7]-[1-7]$|^TEST$ ^WIDE[12]-[12]$
%C%
-%C%# See User Guide for more explanation of what this means and how
-%C%# it can be customized for your particular needs.
+%C%# See User Guide and "APRS-Digipeaters.pdf" for more explanation of what
+%C%# this means and how it can be customized for your particular needs.
%C%
-%C%# Filtering can be used to limit was is digipeated.
-%C%# For example, only weather weather reports, received on channel 0,
-%C%# will be retransmitted on channel 1.
-%C%#
-%C%
-%C%#FILTER 0 1 t/wn
%C%
%C%# Traditional connected mode packet radio uses a different
%C%# type of digipeating. See User Guide for details.
@@ -487,6 +501,7 @@
%C%# without sending it over the air and relying on someone else to
%C%# forward it to an IGate server. This is done by using sendto=IG rather
%C%# than a radio channel number. Overlay R for receive only, T for two way.
+%C%# There is no need to send it as often as you would over the radio.
%C%
%C%#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=R lat=42^37.14N long=071^20.83W
%C%#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=T lat=42^37.14N long=071^20.83W
@@ -495,13 +510,14 @@
%C%# To relay messages from the Internet to radio, you need to add
%C%# one more option with the transmit channel number and a VIA path.
%C%
-%C%#IGTXVIA 0 WIDE1-1
+%C%#IGTXVIA 0 WIDE1-1,WIDE2-1
%C%
%C%
%C%# Finally, we don't want to flood the radio channel.
%C%# The IGate function will limit the number of packets transmitted
%C%# during 1 minute and 5 minute intervals. If a limit would
%C%# be exceeded, the packet is dropped and message is displayed in red.
+%C%# This might be low for APRS Thursday when there is abnormally high activity.
%C%
%C%IGTXLIMIT 6 10
%C%
@@ -518,82 +534,4 @@
%C%#
%C%# See separate "APRStt-Implementation-Notes" document for details.
%C%#
-%C%
-%C%#
-%C%# Sample gateway configuration based on:
-%C%#
-%C%# http://www.aprs.org/aprstt/aprstt-coding24.txt
-%C%# http://www.aprs.org/aprs-jamboree-2013.html
-%C%#
-%C%
-%C%# Define specific points.
-%C%
-%C%TTPOINT B01 37^55.37N 81^7.86W
-%C%TTPOINT B7495088 42.605237 -71.34456
-%C%TTPOINT B934 42.605237 -71.34456
-%C%
-%C%TTPOINT B901 42.661279 -71.364452
-%C%TTPOINT B902 42.660411 -71.364419
-%C%TTPOINT B903 42.659046 -71.364452
-%C%TTPOINT B904 42.657578 -71.364602
-%C%
-%C%
-%C%# For location at given bearing and distance from starting point.
-%C%
-%C%TTVECTOR B5bbbddd 37^55.37N 81^7.86W 0.01 mi
-%C%
-%C%# For location specified by x, y coordinates.
-%C%
-%C%TTGRID Byyyxxx 37^50.00N 81^00.00W 37^59.99N 81^09.99W
-%C%
-%C%# UTM location for Lowell-Dracut-Tyngsborough State Forest.
-%C%
-%C%TTUTM B6xxxyyy 19T 10 300000 4720000
-%C%
-%C%
-%C%
-%C%# Location for the corral.
-%C%
-%C%TTCORRAL 37^55.50N 81^7.00W 0^0.02N
-%C%
-%C%# Compact messages - Fixed locations xx and object yyy where
-%C%# Object numbers 100 - 199 = bicycle
-%C%# Object numbers 200 - 299 = fire truck
-%C%# Others = dog
-%C%
-%C%TTMACRO xx1yy B9xx*AB166*AA2B4C5B3B0A1yy
-%C%TTMACRO xx2yy B9xx*AB170*AA3C4C7C3B0A2yy
-%C%TTMACRO xxyyy B9xx*AB180*AA3A6C4A0Ayyy
-%C%
-%C%TTMACRO z Cz
-%C%
-%C%# Receive on channel 0, Transmit object reports on channel 1 with optional via path.
-%C%# You probably want to put in a transmit delay on the APRStt channel so it
-%C%# it doesn't start sending a response before the user releases PTT.
-%C%# This is in 10 ms units so 100 means 1000 ms = 1 second.
-%C%
-%C%#TTOBJ 0 1 WIDE1-1
-%C%#CHANNEL 0
-%C%#DWAIT 100
-%C%
-%C%# Advertise gateway position with beacon.
-%C%
-%C%# 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"
-%C%
-%C%
-%C%# Sample speech responses.
-%C%# Default is Morse code "R" for received OK and "?" for all errors.
-%C%
-%C%#TTERR OK SPEECH Message Received.
-%C%#TTERR D_MSG SPEECH D not implemented.
-%C%#TTERR INTERNAL SPEECH Internal error.
-%C%#TTERR MACRO_NOMATCH SPEECH No definition for digit sequence.
-%C%#TTERR BAD_CHECKSUM SPEECH Bad checksum on call.
-%C%#TTERR INVALID_CALL SPEECH Invalid callsign.
-%C%#TTERR INVALID_OBJNAME SPEECH Invalid object name.
-%C%#TTERR INVALID_SYMBOL SPEECH Invalid symbol.
-%C%#TTERR INVALID_LOC SPEECH Invalid location.
-%C%#TTERR NO_CALL SPEECH No call or object name.
-%C%#TTERR SATSQ SPEECH Satellite square must be 4 digits.
-%C%#TTERR SUFFIX_NO_CALL SPEECH Send full call before using suffix.
-%C%
\ No newline at end of file
+
diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt
index 9f7c40e4..11a82a43 100644
--- a/data/CMakeLists.txt
+++ b/data/CMakeLists.txt
@@ -1,94 +1,41 @@
#
+# Update: 1 May 2023 (still 1.7 dev version)
+#
+# The original intention was to allow an easy way to download the most
+# recent versions of some files.
+#
+# "update-data" would only work once.
+#
+# These locations are no longer being maintained:
+# http://www.aprs.org/aprs11/tocalls.txt -- 14 Dec 2021
+# http://www.aprs.org/symbols/symbols-new.txt -- 17 Mar 2021
+# http://www.aprs.org/symbols/symbolsX.txt -- 25 Nov 2015
+# so there is no reason to provide a capability grab the latest version.
+#
+# Rather than fixing an obsolete capability, it will just be removed.
+#
# The destination field is often used to identify the manufacturer/model.
# These are not hardcoded into Dire Wolf. Instead they are read from
-# a file called tocalls.txt at application start up time.
+# a file called tocalls.yaml at application start up time.
#
# The original permanent symbols are built in but the "new" symbols,
# using overlays, are often updated. These are also read from files.
#
-# You can obtain an updated copy by typing "make data-update".
-# This is not part of the normal build process. You have to do this explicitly.
-#
-# The locations below appear to be the most recent.
-# The copy at http://www.aprs.org/tocalls.txt is out of date.
-#
+
include(ExternalProject)
-set(TOCALLS_TXT "tocalls.txt")
-set(TOCALLS_TXT_BKP "tocalls.txt.old")
-set(TOCALLS_URL "http://www.aprs.org/aprs11/tocalls.txt")
+set(TOCALLS_YAML "tocalls.yaml")
set(SYMBOLS-NEW_TXT "symbols-new.txt")
-set(SYMBOLS-NEW_TXT_BKP "symbols-new.txt.old")
-set(SYMBOLS-NEW_URL "http://www.aprs.org/symbols/symbols-new.txt")
set(SYMBOLSX_TXT "symbolsX.txt")
-set(SYMBOLSX_TXT_BKP "symbolsX.txt.old")
-set(SYMBOLSX_URL "http://www.aprs.org/symbols/symbolsX.txt")
set(CUSTOM_BINARY_DATA_DIR "${CMAKE_BINARY_DIR}/data")
# we can also move to a separate cmake file and use file(download)
# see conf/install_conf.cmake as example
-file(COPY "${CUSTOM_DATA_DIR}/${TOCALLS_TXT}" DESTINATION "${CUSTOM_BINARY_DATA_DIR}")
+file(COPY "${CUSTOM_DATA_DIR}/${TOCALLS_YAML}" DESTINATION "${CUSTOM_BINARY_DATA_DIR}")
file(COPY "${CUSTOM_DATA_DIR}/${SYMBOLS-NEW_TXT}" DESTINATION "${CUSTOM_BINARY_DATA_DIR}")
file(COPY "${CUSTOM_DATA_DIR}/${SYMBOLSX_TXT}" DESTINATION "${CUSTOM_BINARY_DATA_DIR}")
-add_custom_target(data_rename
- COMMAND ${CMAKE_COMMAND} -E rename "${CUSTOM_BINARY_DATA_DIR}/${TOCALLS_TXT}" "${CUSTOM_BINARY_DATA_DIR}/${TOCALLS_TXT_BKP}"
- COMMAND ${CMAKE_COMMAND} -E rename "${CUSTOM_BINARY_DATA_DIR}/${SYMBOLS-NEW_TXT}" "${CUSTOM_BINARY_DATA_DIR}/${SYMBOLS-NEW_TXT_BKP}"
- COMMAND ${CMAKE_COMMAND} -E rename "${CUSTOM_BINARY_DATA_DIR}/${SYMBOLSX_TXT}" "${CUSTOM_BINARY_DATA_DIR}/${SYMBOLSX_TXT_BKP}"
- )
-
-ExternalProject_Add(download_tocalls
- DEPENDS data_rename
- URL ${TOCALLS_URL}
- PREFIX ""
- DOWNLOAD_DIR "${CUSTOM_BINARY_DATA_DIR}"
- DOWNLOAD_NAME "${TOCALLS_TXT}"
- DOWNLOAD_NO_EXTRACT 0
- EXCLUDE_FROM_ALL 1
- UPDATE_COMMAND ""
- PATCH_COMMAND ""
- CONFIGURE_COMMAND ""
- BUILD_COMMAND ""
- INSTALL_COMMAND ""
- TEST_COMMAND ""
- )
-
-ExternalProject_Add(download_symbols-new
- DEPENDS data_rename
- URL ${SYMBOLS-NEW_URL}
- PREFIX ""
- DOWNLOAD_DIR "${CUSTOM_BINARY_DATA_DIR}"
- DOWNLOAD_NAME "${SYMBOLS-NEW_TXT}"
- DOWNLOAD_NO_EXTRACT 0
- EXCLUDE_FROM_ALL 1
- UPDATE_COMMAND ""
- PATCH_COMMAND ""
- CONFIGURE_COMMAND ""
- BUILD_COMMAND ""
- INSTALL_COMMAND ""
- TEST_COMMAND ""
- )
-
-ExternalProject_Add(download_symbolsx
- DEPENDS data_rename
- URL ${SYMBOLSX_URL}
- PREFIX ""
- DOWNLOAD_DIR "${CUSTOM_BINARY_DATA_DIR}"
- DOWNLOAD_NAME "${SYMBOLSX_TXT}"
- DOWNLOAD_NO_EXTRACT 0
- EXCLUDE_FROM_ALL 1
- UPDATE_COMMAND ""
- PATCH_COMMAND ""
- CONFIGURE_COMMAND ""
- BUILD_COMMAND ""
- INSTALL_COMMAND ""
- TEST_COMMAND ""
- )
-
-add_custom_target(update-data)
-add_dependencies(update-data data_rename download_tocalls download_symbols-new download_symbolsx)
-
-install(FILES "${CUSTOM_BINARY_DATA_DIR}/${TOCALLS_TXT}" DESTINATION ${INSTALL_DATA_DIR})
+install(FILES "${CUSTOM_BINARY_DATA_DIR}/${TOCALLS_YAML}" DESTINATION ${INSTALL_DATA_DIR})
install(FILES "${CUSTOM_BINARY_DATA_DIR}/${SYMBOLS-NEW_TXT}" DESTINATION ${INSTALL_DATA_DIR})
install(FILES "${CUSTOM_BINARY_DATA_DIR}/${SYMBOLSX_TXT}" DESTINATION ${INSTALL_DATA_DIR})
diff --git a/data/README.txt b/data/README.txt
new file mode 100644
index 00000000..9d4da43c
--- /dev/null
+++ b/data/README.txt
@@ -0,0 +1,18 @@
+
+tocalls.yaml contains the encoding for the device/system/software
+identifier which created the packet.
+Knowing what generated the packet is very useful for troubleshooting.
+TNCs, digipeaters, and IGates must not change this.
+
+For MIC-E format, well... it's complicated.
+See Understanding-APRS-Packets.pdf. Too long to repeat here.
+
+For all other packet types, the AX.25 destination, or "tocall" field
+contains a code for what generated the packet.
+This is of the form AP????. For example, APDW18 for direwolf 1.8.
+
+The database of identifiers is currently maintained by Hessu, OH7LZB.
+
+You can update your local copy by running:
+
+wget https://raw.githubusercontent.com/aprsorg/aprs-deviceid/main/tocalls.yaml
diff --git a/data/tocalls.txt b/data/tocalls.txt
deleted file mode 100644
index 692d6b40..00000000
--- a/data/tocalls.txt
+++ /dev/null
@@ -1,323 +0,0 @@
-
-APRS TO-CALL VERSION NUMBERS 14 Dec 2021
----------------------------------------------------------------------
- WB4APR
-
-
-14 Dec 21 Added APATAR ATA-R APRS Digipeater by TA7W/OH2UDS and TA6AEU
-26 Sep 21 Added APRRDZ EPS32 https://github.com/dl9rdz/rdz_ttgo_sonde
-18 Sep 21 Added APCSS for AMSAT Cubesat Simulator https://cubesatsim.org
-16 Sep 21 Added APY05D for Yaesu FT5D series
-04 Sep 21 APLOxx LoRa KISS TNC/Tracker https://github.com/SQ9MDD/TTGO-T-Beam-LoRa-APRS
-24 Aug 21 Added APLSxx SARIMESH http://www.sarimesh.net
-22 Aug 21 Added APE2Ax for VA3NNW's Email-2-APRS ap
-30 Jun 21 Added APCNxx for carNET by DG5OAW
-14 Jun 21 Added APN2xx for NOSaprs JNOS 2.0 - VE4KLM
-24 Apr 21 Added APMPAD for DF1JSL's WXBot clone and extension
-20 Apr 21 Added APLCxx for APRScube by DL3DCW
-19 Apr 21 Added APVMxx for DRCC-DVM Voice (Digital Radio China Club)
-13 Apr 21 Added APIxxx for all Dstar ICOMS (APRS via DPRS)
-23 MAr 20 Added APW9xx For 9A9Y Weather Tracker
-16 Feb 21 Added API970 for I com 9700
-
-2020 Added APHBLx,APIZCI,APLGxx,APLTxx,APNVxx,APY300,APESPG,APESPW
- APGDTx,APOSWx,APOSBx,APBT62,APCLUB,APMQxx
-2019 Added APTPNx,APJ8xx,APBSDx,APNKMX,APAT51,APMGxx,APTCMA,
- APATxx,APQTHx,APLIGx
-2018 added APRARX,APELKx,APGBLN,APBKxx,APERSx,APTCHE
-2017 Added APHWxx,APDVxx,APPICO,APBMxx,APP6xx,APTAxx,APOCSG,APCSMS,
- APPMxx,APOFF,APDTMF,APRSON,APDIGI,APSAT,APTBxx,APIExx,
- APSFxx
-2016 added APYSxx,APINxx,APNICx,APTKPT,APK004,APFPRS,APCDS0,APDNOx
-2015 Added APSTPO,APAND1,APDRxx,APZ247,APHTxx,APMTxx,APZMAJ
- APB2MF,APR2MF,APAVT5
-
-
-
-
-In APRS, the AX.25 Destination address is not used for packet
-routing as is normally done in AX.25. So APRS uses it for two
-things. The initial APxxxx is used as a group identifier to make
-APRS packets instanantly recognizable on shared channels. Most
-applicaitons ignore all non APRS packets. The remaining 4 xxxx
-bytes of the field are available to indicate the software version
-number or application. The following applications have requested
-a TOCALL number series:
-
-Authors with similar alphabetic requirements are encouraged to share
-their address space with other software. Work out agreements amongst
-yourselves and keep me informed.
-
-
-
-
- APn 3rd digit is a number
- AP1WWX TAPR T-238+ WX station
- AP1MAJ Martyn M1MAJ DeLorme inReach Tracker
- AP4Rxy APRS4R software interface
- APnnnD Painter Engineering uSmartDigi D-Gate DSTAR Gateway
- APnnnU Painter Engineering uSmartDigi Digipeater
- APA APAFxx AFilter.
- APAGxx AGATE
- APAGWx SV2AGW's AGWtracker
- APALxx Alinco DR-620/635 internal TNC digis. "Hachi" ,JF1AJE
- APAXxx AFilterX.
- APAHxx AHub
- APAND1 APRSdroid (pre-release) http://aprsdroid.org/
- APAMxx Altus Metrum GPS trackers
- APATAR ATA-R APRS Digipeater by TA7W/OH2UDS and TA6AEU
- APAT8x for Anytone. 81 for 878 HT
- APAT51 for Anytone AT-D578UV APRS mobile radio
- APAVT5 SainSonic AP510 which is a 1watt tracker
- APAWxx AGWPE
- APB APBxxx Beacons or Rabbit TCPIP micros?
- APB2MF DL2MF - MF2APRS Radiosonde for balloons
- APBLxx BigRedBee BeeLine
- APBLO MOdel Rocketry K7RKT
- APBKxx PY5BK Bravo Tracker in Brazil
- APBPQx John G8BPQ Digipeater/IGate
- APBMxx BrandMeister DMR Server for R3ABM
- APBSDx HamBSD https://hambsd.org/
- APBT62 BTech DMR 6x2
- APC APCxxx Cellular applications
- APCBBx VE7UDP Blackberry Applications
- APCDS0 Leon Lessing ZS6LMG's cell tracker
- APCLEY EYTraker GPRS/GSM tracker by ZS6EY
- APCLEZ Telit EZ10 GSM application ZS6CEY
- APCLUB Brazil APRS network
- APCLWX EYWeather GPRS/GSM WX station by ZS6EY
- APCNxx for carNET by DG5OAW
- APCSMS for Cosmos (used for sending commands @USNA)
- APCSS for AMSAT cubesats https://cubesatsim.org
- APCWP8 John GM7HHB, WinphoneAPRS
- APCYxx Cybiko applications
- APD APD4xx UP4DAR platform
- APDDxx DV-RPTR Modem and Control Center Software
- APDFxx Automatic DF units
- APDGxx D-Star Gateways by G4KLX ircDDB
- APDHxx WinDV (DUTCH*Star DV Node for Windows)
- APDInn DIXPRS - Bela, HA5DI
- APDIGI Used by PSAT2 to indicate the digi is ON
- APDIGI digi ON for PSAT2 and QIKCOM-2
- APDKxx KI4LKF g2_ircddb Dstar gateway software
- APDNOx APRSduino by DO3SWW
- APDOxx ON8JL Standalone DStar Node
- APDPRS D-Star originated posits
- APDRxx APRSdroid Android App http://aprsdroid.org/
- APDSXX SP9UOB for dsDigi and ds-tracker
- APDTxx APRStouch Tone (DTMF)
- APDTMF digi off mode on QIKCOM2 and DTMF ON
- APDUxx U2APRS by JA7UDE
- APDVxx OE6PLD's SSTV with APRS status exchange
- APDWxx DireWolf, WB2OSZ
- APE APExxx Telemetry devices
- APE2Ax VA3NNW's Email-2-APRS ap
- APECAN Pecan Pico APRS Balloon Tracker
- APELKx WB8ELK balloons
- APERXQ Experimental tracker by PE1RXQ
- APERSx Runner tracking by Jason,KG7YKZ
- APESPG ESP SmartBeacon APRS-IS Client
- APESPW ESP Weather Station APRS-IS Client
- APF APFxxx Firenet
- APFGxx Flood Gage (KP4DJT)
- APFIxx for APRS.FI OH7LZB, Hessu
- APFPRS for FreeDV by Jeroen PE1RXQ
- APG APGxxx Gates, etc
- APGOxx for AA3NJ PDA application
- APGBLN for NW5W's GoBalloon
- APGDTx for VK4FAST's Graphic Data Terminal
- APH APHKxx for LA1BR tracker/digipeater
- APHAXn SM2APRS by PY2UEP
- APHBLx for DMR Gateway by Eric - KF7EEL
- APHTxx HMTracker by IU0AAC
- APHWxx for use in "HamWAN
- API API282 for ICOM IC-2820
- API31 for ICOM ID-31
- API410 for ICOM ID-4100
- API51 for ICOM ID-51
- API510 for ICOM ID-5100
- API710 for ICOM IC-7100
- API80 for ICOM IC-80
- API880 for ICOM ID-880
- API910 for ICOM IC-9100
- API92 for ICOM IC-92
- API970 for ICOM 9700
- APICQx for ICQ
- APICxx HA9MCQ's Pic IGate
- APIExx W7KMV's PiAPRS system
- APINxx PinPoint by AB0WV
- APIZCI hymTR IZCI Tracker by TA7W/OH2UDS and TA6AEU
- APJ APJ8xx Jordan / KN4CRD JS8Call application
- APJAxx JavAPRS
- APJExx JeAPRS
- APJIxx jAPRSIgate
- APJSxx javAPRSSrvr
- APJYnn KA2DDO Yet another APRS system
- APK APK0xx Kenwood TH-D7's
- APK003 Kenwood TH-D72
- APK004 Kenwood TH-D74
- APK1xx Kenwood D700's
- APK102 Kenwood D710
- APKRAM KRAMstuff.com - Mark. G7LEU
- APL APLCxx APRScube by DL3DCW
- APLGxx LoRa Gateway/Digipeater OE5BPA
- APLIGx LightAPRS - TA2MUN and TA9OHC
- APLOxx LoRa KISS TNC/Tracker
- APLQRU Charlie - QRU Server
- APLMxx WA0TQG transceiver controller
- APLSxx SARIMESH ( http://www.sarimesh.net )
- APLTxx LoRa Tracker - OE5BPA
- APM APMxxx MacAPRS,
- APMGxx PiCrumbs and MiniGate - Alex, AB0TJ
- APMIxx SQ3PLX http://microsat.com.pl/
- APMPAD DF1JSL's WXBot clone and extension
- APMQxx Ham Radio of Things WB2OSZ
- APMTxx LZ1PPL for tracker
- APN APNxxx Network nodes, digis, etc
- APN2xx NOSaprs for JNOS 2.0 - VE4KLM
- APN3xx Kantronics KPC-3 rom versions
- APN9xx Kantronics KPC-9612 Roms
- APNAxx WB6ZSU's APRServe
- APNDxx DIGI_NED
- APNICx SQ5EKU http://sq5eku.blogspot.com/
- APNK01 Kenwood D700 (APK101) type
- APNK80 KAM version 8.0
- APNKMP KAM+
- APNKMX KAM-XL
- APNMxx MJF TNC roms
- APNPxx Paccom TNC roms
- APNTxx SV2AGW's TNT tnc as a digi
- APNUxx UIdigi
- APNVxx SQ8L's VP digi and Nodes
- APNXxx TNC-X (K6DBG)
- APNWxx SQ3FYK.com WX/Digi and SQ3PLX http://microsat.com.pl/
- APO APRSpoint
- APOFF Used by PSAT and PSAT2 to indicate the digi is OFF
- APOLUx for OSCAR satellites for AMSAT-LU by LU9DO
- APOAxx OpenAPRS - Greg Carter
- APOCSG For N0AGI's APRS to POCSAG project
- APOD1w Open Track with 1 wire WX
- APOSBx openSPOT3 by HA2NON at sharkrf.com
- APOSWx openSPOT2
- APOTxx Open Track
- APOU2k Open Track for Ultimeter
- APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO
- APP APP6xx for APRSlib
- APPICx DB1NTO' PicoAPRS
- APPMxx DL1MX's RTL-SDR pytohon Igate
- APPTxx KetaiTracker by JF6LZE, Takeki (msg capable)
- APQ APQxxx Earthquake data
- APQTHx W8WJB's QTH.app
- APR APR8xx APRSdos versions 800+
- APR2MF DL2MF - MF2APRS Radiosonde WX reporting
- APRARX VK5QI's radiosonde tracking
- APRDxx APRSdata, APRSdr
- APRGxx aprsg igate software, OH2GVE
- APRHH2 HamHud 2
- APRKxx APRStk
- APRNOW W5GGW ipad application
- APRRTx RPC electronics
- APRS Generic, (obsolete. Digis should use APNxxx instead)
- APRSON Used by PSAT to indicate the DIGI is ON
- APRXxx >40 APRSmax
- APRXxx <39 for OH2MQK's igate
- APRTLM used in MIM's and Mic-lites, etc
- APRtfc APRStraffic
- APRSTx APRStt (Touch tone)
- APS APSxxx APRS+SA, etc
- APSARx ZL4FOX's SARTRACK
- APSAT digi ON for QIKCOM-1
- APSCxx aprsc APRS-IS core server (OH7LZB, OH2MQK)
- APSFxx F5OPV embedded devices - was APZ40
- APSK63 APRS Messenger -over-PSK63
- APSK25 APRS Messenger GMSK-250
- APSMSx Paul Dufresne's SMSGTE - SMS Gateway
- APSTMx for W7QO's Balloon trackers
- APSTPO for N0AGI Satellite Tracking and Operations
- APT APT2xx Tiny Track II
- APT3xx Tiny Track III
- APTAxx K4ATM's tiny track
- APTBxx TinyAPRS by BG5HHP Was APTAxx till Sep 2017
- APTCHE PU3IKE in Brazil TcheTracker/Tcheduino
- APTCMA CAPI tracker - PU1CMA Brazil
- APTIGR TigerTrack
- APTKPT TrackPoint N0LP
- APTPNx TARPN Packet Node Tracker by KN4ORB http://tarpn.net/
- APTTxx Tiny Track
- APTWxx Byons WXTrac
- APTVxx for ATV/APRN and SSTV applications
- APU APU1xx UIview 16 bit applications
- APU2xx UIview 32 bit apps
- APU3xx UIview terminal program
- APUDRx NW Digital Radio's UDR (APRS/Dstar)
- APV APVxxx Voice over Internet applications
- APVMxx DRCC-DVM Digital Voice (Digital Radio China Club)
- APVRxx for IRLP
- APVLxx for I-LINK
- APVExx for ECHO link
- APW APWxxx WinAPRS, etc
- APW9xx 9A9Y Weather Tracker
- APWAxx APRSISCE Android version
- APWSxx DF4IAN's WS2300 WX station
- APWMxx APRSISCE KJ4ERJ
- APWWxx APRSISCE win32 version
- APX APXnnn Xastir
- APXRnn Xrouter
- APY APYxxx Yaesu Radios
- APY008 Yaesu VX-8 series
- APY01D Yaesu FT1D series
- APY02D Yaesu FT2D series
- APY03D Yaesu FT3D series
- APY05D Yaesu FT5D series
- APY100 Yaesu FTM-100D series
- APY300 Yaesu FTM-300D series
- APY350 Yaesu FTM-350 series
- APY400 Yaesu FTM-400D series
- APZ APZxxx Experimental
- APZ200 old versions of JNOS
- APZ247 for UPRS NR0Q
- APZ0xx Xastir (old versions. See APX)
- APZMAJ Martyn M1MAJ DeLorme inReach Tracker
- APZMDR for HaMDR trackers - hessu * hes.iki.fi]
- APZPAD Smart Palm
- APZTKP TrackPoint, Nick N0LP (Balloon tracking)(depricated)
- APZWIT MAP27 radio (Mountain Rescue) EI7IG
- APZWKR GM1WKR NetSked application
-
-
-
-
-
-
-REGISTERED TOCALL ALTNETS:
---------------------------
-
-ALTNETS are uses of the AX-25 tocall to distinguish specialized
-traffic that may be flowing on the APRS-IS, but that are not intended
-to be part of normal APRS distribution to all normal APRS software
-operating in normal (default) modes. Proper APRS software that
-honors this design are supposed to IGNORE all ALTNETS unless the
-particular operator has selected an ALTNET to monitor for.
-
-An example is when testing; an author may want to transmit objects
-all over his map for on-air testing, but does not want these to
-clutter everyone's maps or databases. He could use the ALTNET of
-"TEST" and client APRS software that respects the ALTNET concept
-should ignore these packets.
-
-An ALTNET is defined to be ANY AX.25 TOCALL that is NOT one of the
-normal APRS TOCALL's. The normal TOCALL's that APRS is supposed to
-process are: ALL, BEACON, CQ, QST, GPSxxx and of course APxxxx.
-
-The following is a list of ALTNETS that may be of interest to other
-users. This list is by no means complete, since ANY combination of
-characters other than APxxxx are considered an ALTNET. But this list
-can give consisntecy to ALTNETS that may be using the global APRS-IS
-and need some special recognition. Here are some ideas:
-
-
-
- SATERN - Salvation Army Altnet
- AFMARS - Airforce Mars
- AMARS - Army Mars
-
\ No newline at end of file
diff --git a/data/tocalls.yaml b/data/tocalls.yaml
new file mode 100644
index 00000000..dfc5fd08
--- /dev/null
+++ b/data/tocalls.yaml
@@ -0,0 +1,1590 @@
+#
+# This is a machine-readable index of APRS device and software
+# identification strings. For easy manual editing and validation, the
+# master file is in YAML format. A conversion tool and pre-converted
+# versions in XML and JSON are also provided for environments where those
+# are more convenient to parse.
+#
+# This list is maintained by Hessu, OH7LZB, for the aprs.fi service.
+# It is licensed under the CC BY-SA 2.0 license, so you're free to use
+# it in any of your applications. For free. Just mention the source
+# somewhere in the small print.
+# http://creativecommons.org/licenses/by-sa/2.0/
+#
+
+---
+
+#
+# English shown names and descriptions for device classes
+#
+classes:
+ - class: wx
+ shown: Weather station
+ description: Dedicated weather station
+
+ - class: tracker
+ shown: Tracker
+ description: Tracker device
+
+ - class: rig
+ shown: Rig
+ description: Mobile or desktop radio
+
+ - class: ht
+ shown: HT
+ description: Hand-held radio
+
+ - class: app
+ shown: Mobile app
+ description: Mobile phone or tablet app
+
+ - class: software
+ shown: Software
+ description: Desktop software
+
+ - class: digi
+ shown: Digipeater
+ description: Digipeater software
+
+ - class: igate
+ shown: iGate
+ description: iGate software
+
+ - class: dstar
+ shown: D-Star
+ description: D-Star radio
+
+ - class: satellite
+ shown: Satellite
+ description: Satellite-based station
+
+ - class: service
+ shown: Service
+ description: Software running as a web service
+
+#
+# mic-e device identifier index for new-style 2-character device
+# suffixes. The first prefix byte indicates messaging capability.
+#
+mice:
+ - suffix: "_ "
+ vendor: Yaesu
+ model: VX-8
+ class: ht
+
+ - suffix: "_\""
+ vendor: Yaesu
+ model: FTM-350
+ class: rig
+
+ - suffix: "_#"
+ vendor: Yaesu
+ model: VX-8G
+ class: ht
+
+ - suffix: "_$"
+ vendor: Yaesu
+ model: FT1D
+ class: ht
+
+ - suffix: "_("
+ vendor: Yaesu
+ model: FT2D
+ class: ht
+
+ - suffix: "_0"
+ vendor: Yaesu
+ model: FT3D
+ class: ht
+
+ - suffix: "_3"
+ vendor: Yaesu
+ model: FT5D
+ class: ht
+
+ - suffix: "_1"
+ vendor: Yaesu
+ model: FTM-300D
+ class: rig
+
+ - suffix: "_)"
+ vendor: Yaesu
+ model: FTM-100D
+ class: rig
+
+ - suffix: "_%"
+ vendor: Yaesu
+ model: FTM-400DR
+ class: rig
+
+ - suffix: "(5"
+ vendor: Anytone
+ model: D578UV
+ class: ht
+
+ - suffix: "(8"
+ vendor: Anytone
+ model: D878UV
+ class: ht
+
+ - suffix: "|3"
+ vendor: Byonics
+ model: TinyTrak3
+ class: tracker
+
+ - suffix: "|4"
+ vendor: Byonics
+ model: TinyTrak4
+ class: tracker
+
+ - suffix: "^v"
+ vendor: HinzTec
+ model: anyfrog
+
+ - suffix: "*v"
+ vendor: KissOZ
+ model: Tracker
+ class: tracker
+
+ - suffix: ":2"
+ vendor: SQ8L
+ model: VP-Tracker
+ class: tracker
+
+#
+# mic-e legacy devices, with an unique comment suffix and prefix
+#
+
+micelegacy:
+ - prefix: ">"
+ vendor: Kenwood
+ model: TH-D7A
+ class: ht
+ features:
+ - messaging
+
+ - prefix: ">"
+ suffix: "="
+ vendor: Kenwood
+ model: TH-D72
+ class: ht
+ features:
+ - messaging
+
+ - prefix: ">"
+ suffix: "^"
+ vendor: Kenwood
+ model: TH-D74
+ class: ht
+ features:
+ - messaging
+
+ - prefix: ">"
+ suffix: "&"
+ vendor: Kenwood
+ model: TH-D75
+ class: ht
+ features:
+ - messaging
+
+ - prefix: "]"
+ vendor: Kenwood
+ model: TM-D700
+ class: rig
+ features:
+ - messaging
+
+ - prefix: "]"
+ suffix: "="
+ vendor: Kenwood
+ model: TM-D710
+ class: rig
+ features:
+ - messaging
+
+#
+# TOCALL index
+#
+tocalls:
+ - tocall: AP1WWX
+ vendor: TAPR
+ model: T-238+
+ class: wx
+
+ - tocall: AP4R??
+ vendor: Open Source
+ model: APRS4R
+ class: software
+
+ - tocall: APAEP1
+ vendor: Paraguay Space Agency (AEP)
+ model: "EIRUAPRSDIGIS&FV1"
+ class: satellite
+
+ - tocall: APAF??
+ model: AFilter
+
+ - tocall: APAG??
+ model: AGate
+
+ - tocall: APAGW
+ vendor: SV2AGW
+ model: AGWtracker
+ class: software
+ os: Windows
+
+ - tocall: APAGW?
+ vendor: SV2AGW
+ model: AGWtracker
+ class: software
+ os: Windows
+
+ - tocall: APAH??
+ model: AHub
+
+ - tocall: APAIOR
+ vendor: J. Angelo Racoma DU2XXR/N2RAC
+ model: APRSPH net bot based on Ioreth
+ class: service
+ os: linux
+ contact: info@aprsph.net
+ features:
+ - messaging
+
+ - tocall: APAM??
+ vendor: Altus Metrum
+ model: AltOS
+ class: tracker
+
+ - tocall: APAND?
+ vendor: Open Source
+ model: APRSdroid
+ os: Android
+ class: app
+
+ - tocall: APAR??
+ vendor: Øyvind, LA7ECA
+ model: Arctic Tracker
+ class: tracker
+ os: embedded
+
+ - tocall: APAT51
+ vendor: Anytone
+ model: AT-D578
+ class: rig
+
+ - tocall: APAT81
+ vendor: Anytone
+ model: AT-D878
+ class: ht
+
+ - tocall: APAT??
+ vendor: Anytone
+
+ - tocall: APATAR
+ vendor: TA7W/OH2UDS Baris Dinc and TA6AEU
+ model: ATA-R APRS Digipeater
+ class: digi
+
+ - tocall: APAVT5
+ vendor: SainSonic
+ model: AP510
+ class: tracker
+
+ - tocall: APAW??
+ vendor: SV2AGW
+ model: AGWPE
+ class: software
+ os: Windows
+
+ - tocall: APAX??
+ model: AFilterX
+
+ - tocall: APB2MF
+ vendor: Mike, DL2MF
+ model: MF2APRS Radiosonde tracking tool
+ class: software
+ os: Windows
+
+ - tocall: APBK??
+ vendor: PY5BK
+ model: Bravo Tracker
+ class: tracker
+
+ - tocall: APBL??
+ vendor: BigRedBee
+ model: BeeLine GPS
+ class: tracker
+
+ - tocall: APBM??
+ vendor: R3ABM
+ model: BrandMeister DMR
+
+ - tocall: APBPQ?
+ vendor: John Wiseman, G8BPQ
+ model: BPQ32
+ class: software
+ os: Windows
+
+ - tocall: APBSD?
+ vendor: hambsd.org
+ model: HamBSD
+
+ - tocall: APBT62
+ vendor: BTech
+ model: DMR 6x2
+
+ - tocall: APC???
+ vendor: Rob Wittner, KZ5RW
+ model: APRS/CE
+ class: app
+
+ - tocall: APCDS0
+ vendor: ZS6LMG
+ model: cell tracker
+ class: tracker
+
+ - tocall: APCLEY
+ vendor: ZS6EY
+ model: EYTraker
+ class: tracker
+
+ - tocall: APCLEZ
+ vendor: ZS6EY
+ model: Telit EZ10 GSM application
+ class: tracker
+
+ - tocall: APCLUB
+ model: Brazil APRS network
+
+ - tocall: APCLWX
+ vendor: ZS6EY
+ model: EYWeather
+ class: wx
+
+ - tocall: APCN??
+ vendor: DG5OAW
+ model: carNET
+
+ - tocall: APCSMS
+ vendor: USNA
+ model: Cosmos
+
+ - tocall: APCSS
+ vendor: AMSAT
+ model: CubeSatSim CubeSat Simulator
+
+ - tocall: APCTLK
+ vendor: Open Source
+ model: Codec2Talkie
+ class: app
+
+ - tocall: APCWP8
+ vendor: GM7HHB
+ model: WinphoneAPRS
+ class: app
+
+ - tocall: APD5T?
+ vendor: Geoffrey, F4FXL
+ model: Open Source DStarGateway
+ class: dstar
+ contact: f4fxl@dstargateway.digital
+
+ - tocall: APDF??
+ model: Automatic DF units
+
+ - tocall: APDG??
+ vendor: Jonathan, G4KLX
+ model: ircDDB Gateway
+ class: dstar
+
+ - tocall: APDI??
+ vendor: Bela, HA5DI
+ model: DIXPRS
+ class: software
+
+ - tocall: APDNO?
+ vendor: DO3SWW
+ model: APRSduino
+ class: tracker
+ os: embedded
+
+ - tocall: APDPRS
+ vendor: unknown
+ model: D-Star APDPRS
+ class: dstar
+
+ - tocall: APDR??
+ vendor: Open Source
+ model: APRSdroid
+ os: Android
+ class: app
+
+ - tocall: APDS??
+ vendor: SP9UOB
+ model: dsDIGI
+ os: embedded
+
+ - tocall: APDST?
+ vendor: SP9UOB
+ model: dsTracker
+ os: embedded
+
+ - tocall: APDT??
+ vendor: unknown
+ model: APRStouch Tone (DTMF)
+
+ - tocall: APDU??
+ vendor: JA7UDE
+ model: U2APRS
+ class: app
+ os: Android
+
+ - tocall: APDV??
+ vendor: OE6PLD
+ model: SSTV with APRS
+ class: software
+
+ - tocall: APDW??
+ vendor: WB2OSZ
+ model: DireWolf
+
+ - tocall: APDnnn
+ vendor: Open Source
+ model: aprsd
+ class: software
+ os: Linux/Unix
+
+ - tocall: APE2A?
+ vendor: NoseyNick, VA3NNW
+ model: Email-2-APRS gateway
+ class: software
+ os: Linux/Unix
+
+ - tocall: APE???
+ model: Telemetry devices
+
+ - tocall: APECAN
+ vendor: KT5TK/DL7AD
+ model: Pecan Pico APRS Balloon Tracker
+ class: tracker
+
+ - tocall: APELK?
+ vendor: WB8ELK
+ model: Balloon tracker
+ class: tracker
+
+ - tocall: APEML?
+ vendor: Leszek, SP9MLI
+ model: SP9MLI for WX, Telemetry
+ class: software
+ contact: sp9mli@gmail.com
+
+ - tocall: APEP??
+ vendor: Patrick EGLOFF, TK5EP
+ model: LoRa WX station
+ class: wx
+ os: embedded
+ contact: pegloff@gmail.com
+
+ - tocall: APERS?
+ vendor: Jason, KG7YKZ
+ model: Runner tracking
+ class: tracker
+
+ - tocall: APERXQ
+ vendor: PE1RXQ
+ model: PE1RXQ APRS Tracker
+ class: tracker
+
+ - tocall: APESP?
+ vendor: LY3PH
+ model: APRS-ESP
+ os: embedded
+
+ - tocall: APFG??
+ vendor: KP4DJT
+ model: Flood Gage
+ class: software
+
+ - tocall: APFI??
+ vendor: aprs.fi
+ class: app
+
+ - tocall: APFII?
+ model: iPhone/iPad app
+ vendor: aprs.fi
+ os: ios
+ class: app
+
+ - tocall: APGBLN
+ vendor: NW5W
+ model: GoBalloon
+ class: tracker
+
+ - tocall: APGO??
+ vendor: AA3NJ
+ model: APRS-Go
+ class: app
+
+ - tocall: APHAX?
+ vendor: PY2UEP
+ model: SM2APRS SondeMonitor
+ class: software
+ os: Windows
+
+ - tocall: APHBL?
+ vendor: KF7EEL
+ model: HBLink D-APRS Gateway
+ class: software
+
+ - tocall: APHH?
+ vendor: Steven D. Bragg, KA9MVA
+ model: HamHud
+ class: tracker
+
+ - tocall: APHK??
+ vendor: LA1BR
+ model: Digipeater/tracker
+
+ - tocall: APHMEY
+ vendor: Tapio Heiskanen, OH2TH
+ model: APRS-IS Client for Athom Homey
+ contact: oh2th@iki.fi
+
+ - tocall: APHPIA
+ vendor: HP3ICC
+ model: Arduino APRS
+
+ - tocall: APHPIB
+ vendor: HP3ICC
+ model: Python APRS Beacon
+
+ - tocall: APHPIW
+ vendor: HP3ICC
+ model: Python APRS WX
+
+ - tocall: APHRM?
+ vendor: Giovanni, IW1CGW
+ model: Meteo
+ class: wx
+ contact: iw1cgw@libero.it
+
+ - tocall: APHRT?
+ vendor: Giovanni, IW1CGW
+ model: Telemetry
+ contact: iw1cgw@libero.it
+
+ - tocall: APHT??
+ vendor: IU0AAC
+ model: HMTracker
+ class: tracker
+
+ - tocall: APHW??
+ vendor: HamWAN
+
+ - tocall: API282
+ vendor: Icom
+ model: IC-2820
+ class: dstar
+
+ - tocall: API31
+ vendor: Icom
+ model: IC-31
+ class: dstar
+
+ - tocall: API410
+ vendor: Icom
+ model: IC-4100
+ class: dstar
+
+ - tocall: API51
+ vendor: Icom
+ model: IC-51
+ class: dstar
+
+ - tocall: API510
+ vendor: Icom
+ model: IC-5100
+ class: dstar
+
+ - tocall: API710
+ vendor: Icom
+ model: IC-7100
+ class: dstar
+
+ - tocall: API80
+ vendor: Icom
+ model: IC-80
+ class: dstar
+
+ - tocall: API880
+ vendor: Icom
+ model: IC-880
+ class: dstar
+
+ - tocall: API910
+ vendor: Icom
+ model: IC-9100
+ class: dstar
+
+ - tocall: API92
+ vendor: Icom
+ model: IC-92
+ class: dstar
+
+ - tocall: API970
+ vendor: Icom
+ model: IC-9700
+ class: dstar
+
+ - tocall: API???
+ vendor: Icom
+ model: unknown
+ class: dstar
+
+ - tocall: APIC??
+ vendor: HA9MCQ
+ model: PICiGATE
+
+ - tocall: APIE??
+ vendor: W7KMV
+ model: PiAPRS
+
+ - tocall: APIN??
+ vendor: AB0WV
+ model: PinPoint
+
+ - tocall: APIZCI
+ vendor: TA7W/OH2UDS and TA6AEU
+ model: hymTR IZCI Tracker
+ class: tracker
+ os: embedded
+
+ - tocall: APJ8??
+ vendor: KN4CRD
+ model: JS8Call
+ class: software
+
+ - tocall: APJA??
+ vendor: K4HG & AE5PL
+ model: JavAPRS
+
+ - tocall: APJE??
+ vendor: Gregg Wonderly, W5GGW
+ model: JeAPRS
+
+ - tocall: APJI??
+ vendor: Peter Loveall, AE5PL
+ model: jAPRSIgate
+ class: software
+
+ - tocall: APJID2
+ vendor: Peter Loveall, AE5PL
+ model: D-Star APJID2
+ class: dstar
+
+ - tocall: APJS??
+ vendor: Peter Loveall, AE5PL
+ model: javAPRSSrvr
+
+ - tocall: APJY??
+ vendor: KA2DDO
+ model: YAAC
+ class: software
+
+ - tocall: APK003
+ vendor: Kenwood
+ model: TH-D72
+ class: ht
+
+ - tocall: APK004
+ vendor: Kenwood
+ model: TH-D74
+ class: ht
+
+ - tocall: APK005
+ vendor: Kenwood
+ model: TH-D75
+ class: ht
+
+ - tocall: APK0??
+ vendor: Kenwood
+ model: TH-D7
+ class: ht
+
+ - tocall: APK1??
+ vendor: Kenwood
+ model: TM-D700
+ class: rig
+
+ - tocall: APKHTW
+ vendor: Kip, W3SN
+ model: Tempest Weather Bridge
+ class: wx
+ os: embedded
+ contact: w3sn@moxracing.33mail.com
+
+ - tocall: APKRAM
+ vendor: kramstuff.com
+ model: Ham Tracker
+ class: app
+ os: ios
+
+ - tocall: APLC??
+ vendor: DL3DCW
+ model: APRScube
+
+ - tocall: APLDI?
+ vendor: David, OK2DDS
+ model: LoRa IGate/Digipeater
+ class: digi
+
+ - tocall: APLDM?
+ vendor: David, OK2DDS
+ model: LoRa Meteostation
+ class: wx
+
+ - tocall: APLETK
+ vendor: DL5TKL
+ model: T-Echo
+ class: tracker
+ os: embedded
+ contact: cfr34k-git@tkolb.de
+
+ - tocall: APLFM?
+ vendor: DO1MA
+ model: FemtoAPRS
+ class: tracker
+ os: embedded
+
+ - tocall: APLG??
+ vendor: OE5BPA
+ model: LoRa Gateway/Digipeater
+ class: digi
+
+ - tocall: APLHI?
+ vendor: Giovanni, IW1CGW
+ model: LoRa IGate/Digipeater/Telemetry
+ class: digi
+ contact: iw1cgw@libero.it
+
+ - tocall: APLHM?
+ vendor: Giovanni, IW1CGW
+ model: LoRa Meteostation
+ class: wx
+ contact: iw1cgw@libero.it
+
+ - tocall: APLIG?
+ vendor: TA2MUN/TA9OHC
+ model: LightAPRS Tracker
+ class: tracker
+
+ - tocall: APLM??
+ vendor: WA0TQG
+ class: software
+
+ - tocall: APLO??
+ vendor: SQ9MDD
+ model: LoRa KISS TNC/Tracker
+ class: tracker
+
+ - tocall: APLP0?
+ vendor: SQ9P
+ model: fajne digi
+ class: digi
+ os: embedded
+ contact: sq9p.peter@gmail.com
+
+ - tocall: APLP1?
+ vendor: SQ9P
+ model: LORA/FSK/AFSK fajny tracker
+ class: tracker
+ os: embedded
+ contact: sq9p.peter@gmail.com
+
+ - tocall: APLRG?
+ vendor: Ricardo, CA2RXU
+ model: ESP32 LoRa iGate
+ class: igate
+ os: embedded
+ contact: richonguzman@gmail.com
+
+ - tocall: APLRT?
+ vendor: Ricardo, CA2RXU
+ model: ESP32 LoRa Tracker
+ class: tracker
+ os: embedded
+ contact: richonguzman@gmail.com
+
+ - tocall: APLS??
+ vendor: SARIMESH
+ model: SARIMESH
+ class: software
+
+ - tocall: APLT??
+ vendor: OE5BPA
+ model: LoRa Tracker
+ class: tracker
+
+ - tocall: APLU0?
+ vendor: SP9UP
+ model: ESP32/SX12xx LoRa iGate / Digi
+ class: digi
+ os: embedded
+ contact: wajdzik.m@gmail.com
+
+ - tocall: APLU1?
+ vendor: SP9UP
+ model: ESP32/SX12xx LoRa Tracker
+ class: tracker
+ os: embedded
+ contact: wajdzik.m@gmail.com
+
+ - tocall: APMG??
+ vendor: Alex, AB0TJ
+ model: PiCrumbs and MiniGate
+ class: software
+
+ - tocall: APMI01
+ vendor: Microsat
+ os: embedded
+ model: WX3in1
+
+ - tocall: APMI02
+ vendor: Microsat
+ os: embedded
+ model: WXEth
+
+ - tocall: APMI03
+ vendor: Microsat
+ os: embedded
+ model: PLXDigi
+
+ - tocall: APMI04
+ vendor: Microsat
+ os: embedded
+ model: WX3in1 Mini
+
+ - tocall: APMI05
+ vendor: Microsat
+ os: embedded
+ model: PLXTracker
+
+ - tocall: APMI06
+ vendor: Microsat
+ os: embedded
+ model: WX3in1 Plus 2.0
+
+ - tocall: APMI??
+ vendor: Microsat
+ os: embedded
+
+ - tocall: APMON?
+ vendor: Amon Schumann, DL9AS
+ model: APRS Balloon Tracker
+ class: tracker
+ os: embedded
+
+ - tocall: APMPAD
+ vendor: DF1JSL
+ model: Multi-Purpose APRS Daemon
+ class: service
+ contact: joerg.schultze.lutter@gmail.com
+ features:
+ - messaging
+
+ - tocall: APMQ??
+ vendor: WB2OSZ
+ model: Ham Radio of Things
+
+ - tocall: APMT??
+ vendor: LZ1PPL
+ model: Micro APRS Tracker
+ class: tracker
+
+ - tocall: APN102
+ vendor: Gregg Wonderly, W5GGW
+ model: APRSNow
+ class: app
+ os: ipad
+
+ - tocall: APN2??
+ vendor: VE4KLM
+ model: NOSaprs for JNOS 2.0
+
+ - tocall: APN3??
+ vendor: Kantronics
+ model: KPC-3
+
+ - tocall: APN9??
+ vendor: Kantronics
+ model: KPC-9612
+
+ - tocall: APNCM
+ vendor: Keith Kaiser, WA0TJT
+ model: Net Control Manager
+ class: software
+ os: browser
+ contact: wa0tjt@gmail.com
+
+ - tocall: APND??
+ vendor: PE1MEW
+ model: DIGI_NED
+
+ - tocall: APNIC4
+ vendor: SQ5EKU
+ model: BidaTrak
+ class: tracker
+ os: embedded
+
+ - tocall: APNJS?
+ vendor: Julien Sansonnens, HB9HRD
+ model: Web messaging service
+ class: service
+ contact: julien.owls@gmail.com
+ features:
+ - messaging
+
+ - tocall: APNK01
+ vendor: Kenwood
+ model: TM-D700
+ class: rig
+ features:
+ - messaging
+
+ - tocall: APNK80
+ vendor: Kantronics
+ model: KAM
+
+ - tocall: APNKMP
+ vendor: Kantronics
+ model: KAM+
+
+ - tocall: APNKMX
+ vendor: Kantronics
+ model: KAM-XL
+
+ - tocall: APNM??
+ vendor: MFJ
+ model: TNC
+
+ - tocall: APNP??
+ vendor: PacComm
+ model: TNC
+
+ - tocall: APNT??
+ vendor: SV2AGW
+ model: TNT TNC as a digipeater
+ class: digi
+
+ - tocall: APNU??
+ vendor: IW3FQG
+ model: UIdigi
+ class: digi
+
+ - tocall: APNV0?
+ vendor: SQ8L
+ model: VP-Digi
+ os: embedded
+ class: digi
+
+ - tocall: APNV1?
+ vendor: SQ8L
+ model: VP-Node
+ os: embedded
+
+ - tocall: APNV2?
+ vendor: SQ8L
+ model: VP-Tracker
+ class: tracker
+
+ - tocall: APNV??
+ vendor: SQ8L
+
+ - tocall: APNW??
+ vendor: SQ3FYK
+ model: WX3in1
+ os: embedded
+
+ - tocall: APNX??
+ vendor: K6DBG
+ model: TNC-X
+
+ - tocall: APOA??
+ vendor: OpenAPRS
+ model: app
+ class: app
+ os: ios
+
+ - tocall: APOCSG
+ vendor: N0AGI
+ model: POCSAG
+
+ - tocall: APODOT
+ vendor: Mike, NA7Q
+ model: Oregon Department of Transportion Traffic Alerts
+ class: service
+
+ - tocall: APOG7?
+ vendor: OpenGD77
+ model: OpenGD77
+ os: embedded
+ contact: Roger VK3KYY/G4KYF
+
+ - tocall: APOLU?
+ vendor: AMSAT-LU
+ model: Oscar
+ class: satellite
+
+ - tocall: APOPYT
+ vendor: Mike, NA7Q
+ model: NA7Q Messenger
+ class: software
+ contact: mike.ph4@gmail.com
+
+ - tocall: APOSAT
+ vendor: Mike, NA7Q
+ model: Open Source Satellite Gateway
+ class: service
+ contact: mike.ph4@gmail.com
+
+ - tocall: APOSMS
+ vendor: Mike, NA7Q
+ model: Open Source SMS Gateway
+ class: service
+ contact: mike.ph4@gmail.com
+ features:
+ - messaging
+
+ - tocall: APOT??
+ vendor: Argent Data Systems
+ model: OpenTracker
+ class: tracker
+
+ - tocall: APOVU?
+ vendor: K J Somaiya Institute
+ model: BeliefSat
+
+ - tocall: APOZ??
+ vendor: OZ1EKD, OZ7HVO
+ model: KissOZ
+ class: tracker
+
+ - tocall: APP6??
+ model: APRSlib
+
+ - tocall: APPCO?
+ vendor: RadCommSoft, LLC
+ model: PicoAPRSTracker
+ class: tracker
+ os: embedded
+ contact: ab4mw@radcommsoft.com
+
+ - tocall: APPIC?
+ vendor: DB1NTO
+ model: PicoAPRS
+ class: tracker
+
+ - tocall: APPM??
+ vendor: DL1MX
+ model: rtl-sdr Python iGate
+ class: software
+
+ - tocall: APPRIS
+ vendor: DF1JSL
+ model: Apprise APRS plugin
+ class: service
+ contact: joerg.schultze.lutter@gmail.com
+ features:
+ - messaging
+
+ - tocall: APPS??
+ vendor: Øyvind, LA7ECA (for the Norwegian Radio Relay League)
+ model: Polaric Server
+ class: software
+ os: Linux
+
+ - tocall: APPT??
+ vendor: JF6LZE
+ model: KetaiTracker
+ class: tracker
+
+ - tocall: APQTH?
+ vendor: Weston Bustraan, W8WJB
+ model: QTH.app
+ class: software
+ os: macOS
+ features:
+ - messaging
+
+ - tocall: APR2MF
+ vendor: Mike, DL2MF
+ model: MF2wxAPRS Tinkerforge gateway
+ class: wx
+ os: Windows
+
+ - tocall: APR8??
+ vendor: Bob Bruninga, WB4APR
+ model: APRSdos
+ class: software
+
+ - tocall: APRARX
+ vendor: Open Source
+ model: radiosonde_auto_rx
+ class: software
+ os: Linux/Unix
+
+ - tocall: APRFG?
+ vendor: RF.Guru
+ contact: info@rf.guru
+
+ - tocall: APRFGB
+ vendor: RF.Guru
+ model: APRS LoRa Pager
+ os: embedded
+ contact: info@rf.guru
+
+ - tocall: APRFGD
+ vendor: RF.Guru
+ model: APRS Digipeater
+ class: digi
+ os: embedded
+ contact: info@rf.guru
+
+ - tocall: APRFGH
+ vendor: RF.Guru
+ model: Hotspot
+ class: rig
+ os: embedded
+ contact: info@rf.guru
+
+ - tocall: APRFGI
+ vendor: RF.Guru
+ model: LoRa APRS iGate
+ class: igate
+ os: embedded
+ contact: info@rf.guru
+
+ - tocall: APRFGL
+ vendor: RF.Guru
+ model: Lora APRS Digipeater
+ class: digi
+ os: embedded
+ contact: info@rf.guru
+
+ - tocall: APRFGM
+ vendor: RF.Guru
+ model: Mobile Radio
+ class: rig
+ os: embedded
+ contact: info@rf.guru
+
+ - tocall: APRFGP
+ vendor: RF.Guru
+ model: Portable Radio
+ class: ht
+ os: embedded
+ contact: info@rf.guru
+
+ - tocall: APRFGR
+ vendor: RF.Guru
+ model: Repeater
+ class: rig
+ os: embedded
+ contact: info@rf.guru
+
+ - tocall: APRFGT
+ vendor: RF.Guru
+ model: LoRa APRS Tracker
+ class: tracker
+ os: embedded
+ contact: info@rf.guru
+
+ - tocall: APRFGW
+ vendor: RF.Guru
+ model: LoRa APRS Weather Station
+ class: wx
+ os: embedded
+ contact: info@rf.guru
+
+ - tocall: APRG??
+ vendor: OH2GVE
+ model: aprsg
+ class: software
+ os: Linux/Unix
+
+ - tocall: APRHH?
+ vendor: Steven D. Bragg, KA9MVA
+ model: HamHud
+ class: tracker
+
+ - tocall: APRNOW
+ vendor: Gregg Wonderly, W5GGW
+ model: APRSNow
+ class: app
+ os: ipad
+
+ - tocall: APRPR?
+ vendor: Robert DM4RW, Peter DL6MAA
+ model: Teensy RPR TNC
+ class: tracker
+ os: embedded
+ contact: dm4rw@skywaves.de
+
+ - tocall: APRRDZ
+ model: rdzTTGOsonde
+ vendor: DL9RDZ
+ class: tracker
+
+ - tocall: APRRF?
+ vendor: Jean-Francois Huet F1EVM
+ model: Tracker for RRF
+ class: tracker
+ os: embedded
+ contact: f1evm@f1evm.fr
+ features:
+ - messaging
+
+ - tocall: APRRT?
+ vendor: RPC Electronics
+ model: RTrak
+ class: tracker
+
+ - tocall: APRS
+ vendor: Unknown
+ model: Unknown
+
+ - tocall: APRX??
+ vendor: Kenneth W. Finnegan, W6KWF
+ model: Aprx
+ class: igate
+ os: Linux/Unix
+
+ - tocall: APS???
+ vendor: Brent Hildebrand, KH2Z
+ model: APRS+SA
+ class: software
+
+ - tocall: APSAR
+ vendor: ZL4FOX
+ model: SARTrack
+ class: software
+ os: Windows
+
+ - tocall: APSC??
+ vendor: OH2MQK, OH7LZB
+ model: aprsc
+ class: software
+
+ - tocall: APSF??
+ vendor: F5OPV, SFCP_LABS
+ model: embedded APRS devices
+ os: embedded
+
+ - tocall: APSFLG
+ vendor: F5OPV, SFCP_LABS
+ model: LoRa/APRS Gateway
+ class: digi
+ os: embedded
+
+ - tocall: APSFRP
+ vendor: F5OPV, SFCP_LABS
+ model: VHF/UHF Repeater
+ os: embedded
+
+ - tocall: APSFTL
+ vendor: F5OPV, SFCP_LABS
+ model: LoRa/APRS Telemetry Reporter
+ os: embedded
+
+ - tocall: APSFWX
+ vendor: F5OPV, SFCP_LABS
+ model: embedded Weather Station
+ class: wx
+ os: embedded
+
+ - tocall: APSK63
+ vendor: Chris Moulding, G4HYG
+ model: APRS Messenger
+ class: software
+ os: Windows
+
+ - tocall: APSMS?
+ vendor: Paul Dufresne
+ model: SMS gateway
+ class: software
+
+ - tocall: APSRF?
+ vendor: SoftRF
+ model: Ham Edition
+ class: tracker
+ os: embedded
+
+ - tocall: APSTM?
+ vendor: W7QO
+ model: Balloon tracker
+ class: tracker
+
+ - tocall: APSTPO
+ vendor: N0AGI
+ model: Satellite Tracking and Operations
+ class: software
+
+ - tocall: APT2??
+ vendor: Byonics
+ model: TinyTrak2
+ class: tracker
+
+ - tocall: APT3??
+ vendor: Byonics
+ model: TinyTrak3
+ class: tracker
+
+ - tocall: APT4??
+ vendor: Byonics
+ model: TinyTrak4
+ class: tracker
+
+ - tocall: APTB??
+ vendor: BG5HHP
+ model: TinyAPRS
+
+ - tocall: APTCHE
+ vendor: PU3IKE
+ model: TcheTracker, Tcheduino
+ class: tracker
+
+ - tocall: APTCMA
+ vendor: Cleber, PU1CMA
+ model: CAPI Tracker
+ class: tracker
+
+ - tocall: APTEMP
+ vendor: KL7AF
+ model: APRS-Tempest Weather Gateway
+ class: wx
+ os: Linux/Unix
+ contact: kl7af@foghaven.net
+
+ - tocall: APTHUR
+ model: APRSThursday weekly event mapbot daemon
+ contact: harihend1973@gmail.com
+ vendor: YD0BCX
+ class: service
+ os: linux/unix
+ features:
+ - messaging
+
+ - tocall: APTKJ?
+ vendor: W9JAJ
+ model: ATTiny APRS Tracker
+ os: embedded
+
+ - tocall: APTLVC
+ vendor: TA5LVC
+ model: TR80 APRS Tracker
+ class: tracker
+
+ - tocall: APTNG?
+ vendor: Filip YU1TTN
+ model: Tango Tracker
+ class: tracker
+
+ - tocall: APTPN?
+ vendor: KN4ORB
+ model: TARPN Packet Node Tracker
+ class: tracker
+
+ - tocall: APTR??
+ vendor: Motorola
+ model: MotoTRBO
+
+ - tocall: APTT*
+ vendor: Byonics
+ model: TinyTrak
+ class: tracker
+
+ - tocall: APTW??
+ vendor: Byonics
+ model: WXTrak
+ class: wx
+
+ - tocall: APU1??
+ vendor: Roger Barker, G4IDE
+ model: UI-View16
+ class: software
+ os: Windows
+
+ - tocall: APU2*
+ vendor: Roger Barker, G4IDE
+ model: UI-View32
+ class: software
+ os: Windows
+
+ - tocall: APUDR?
+ vendor: NW Digital Radio
+ model: UDR
+
+ - tocall: APVE??
+ vendor: unknown
+ model: EchoLink
+
+ - tocall: APVM??
+ vendor: Digital Radio China Club
+ model: DRCC-DVM
+ class: igate
+
+ - tocall: APVR??
+ vendor: unknown
+ model: IRLP
+
+ - tocall: APW9??
+ vendor: Mile Strk, 9A9Y
+ model: WX Katarina
+ class: wx
+ os: embedded
+ features:
+ - messaging
+
+ - tocall: APWA??
+ vendor: KJ4ERJ
+ model: APRSISCE
+ class: software
+ os: Android
+
+ - tocall: APWEE?
+ vendor: Tom Keffer and Matthew Wall
+ model: WeeWX Weather Software
+ class: software
+ os: Linux/Unix
+
+ - tocall: APWM??
+ vendor: KJ4ERJ
+ model: APRSISCE
+ class: software
+ os: Windows Mobile
+ features:
+ - messaging
+ - item-in-msg
+
+ - tocall: APWW??
+ vendor: KJ4ERJ
+ model: APRSIS32
+ class: software
+ os: Windows
+ features:
+ - messaging
+ - item-in-msg
+
+ - tocall: APWnnn
+ vendor: Sproul Brothers
+ model: WinAPRS
+ class: software
+ os: Windows
+
+ - tocall: APX???
+ vendor: Open Source
+ model: Xastir
+ class: software
+ os: Linux/Unix
+
+ - tocall: APXR??
+ vendor: G8PZT
+ model: Xrouter
+
+ - tocall: APY01D
+ vendor: Yaesu
+ model: FT1D
+ class: ht
+
+ - tocall: APY02D
+ vendor: Yaesu
+ model: FT2D
+ class: ht
+
+ - tocall: APY05D
+ vendor: Yaesu
+ model: FT5D
+ class: ht
+
+ - tocall: APY300
+ vendor: Yaesu
+ model: FTM-300D
+ class: rig
+
+ - tocall: APY400
+ vendor: Yaesu
+ model: FTM-400
+ class: rig
+
+ - tocall: APYS??
+ vendor: W2GMD
+ model: Python APRS
+ class: software
+
+ - tocall: "APZ*"
+ vendor: Unknown
+ model: Experimental
+
+ - tocall: APZ18
+ vendor: IW3FQG
+ model: UIdigi
+ class: digi
+
+ - tocall: APZ186
+ vendor: IW3FQG
+ model: UIdigi
+ class: digi
+
+ - tocall: APZ19
+ vendor: IW3FQG
+ model: UIdigi
+ class: digi
+
+ - tocall: APZ247
+ model: UPRS
+ vendor: NR0Q
+
+ - tocall: APZG??
+ vendor: OH2GVE
+ model: aprsg
+ class: software
+ os: Linux/Unix
+
+ - tocall: APZMAJ
+ vendor: M1MAJ
+ model: DeLorme inReach Tracker
+
+ - tocall: APZMDR
+ vendor: Open Source
+ model: HaMDR
+ class: tracker
+ os: embedded
+
+ - tocall: APZTKP
+ vendor: Nick Hanks, N0LP
+ model: TrackPoint
+ class: tracker
+ os: embedded
+
+ - tocall: APZWKR
+ vendor: GM1WKR
+ model: NetSked
+ class: software
+
+ - tocall: APnnnD
+ vendor: Painter Engineering
+ model: uSmartDigi D-Gate
+ class: dstar
+
+ - tocall: APnnnU
+ vendor: Painter Engineering
+ model: uSmartDigi Digipeater
+ class: digi
+
+ - tocall: PSKAPR
+ vendor: Open Source
+ model: PSKmail
+ class: software
+
diff --git a/doc/README.md b/doc/README.md
index 40aa77d6..9f44684e 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -1,4 +1,4 @@
-# Documentation for Dire Wolf #
+# Documentation for Dire Wolf #
Click on the document name to view in your web browser or the link following to download the PDF file.
@@ -154,6 +154,14 @@ and a couple things that can be done about it.
Here, we take a closer look at some of the frames on the TNC Test CD in hopes of gaining some insights into why some are easily decoded and others are more difficult.
There are a lot of ugly signals out there. Many can be improved by decreasing the transmit volume. Others are just plain weird and you have to wonder how they are being generated.
+
+## Additional Documentation for Dire Wolf Software TNC #
+
+
+When there was little documentation, it was all added to the source code repository [https://github.com/wb2osz/direwolf/tree/master/doc](https://github.com/wb2osz/direwolf/tree/master/doc)
+
+The growing number of documentation files and revisions are making the source code repository very large which means long download times. Additional documentation, not tied to a specific release, is now being added to [https://github.com/wb2osz/direwolf-doc](https://github.com/wb2osz/direwolf-doc)
+
## Questions? Experiences to share? ##
Here are some good places to ask questions and share your experiences:
diff --git a/doc/Raspberry-Pi-APRS.pdf b/doc/Raspberry-Pi-APRS.pdf
index 7bbff455..344c3de6 100644
Binary files a/doc/Raspberry-Pi-APRS.pdf and b/doc/Raspberry-Pi-APRS.pdf differ
diff --git a/doc/Successful-APRS-IGate-Operation.pdf b/doc/Successful-APRS-IGate-Operation.pdf
index 26f8d241..9a51ef58 100644
Binary files a/doc/Successful-APRS-IGate-Operation.pdf and b/doc/Successful-APRS-IGate-Operation.pdf differ
diff --git a/doc/User-Guide.pdf b/doc/User-Guide.pdf
index d7010a17..319f882f 100644
Binary files a/doc/User-Guide.pdf and b/doc/User-Guide.pdf differ
diff --git a/external/misc/CMakeLists.txt b/external/misc/CMakeLists.txt
index 16125d0a..685b89ad 100644
--- a/external/misc/CMakeLists.txt
+++ b/external/misc/CMakeLists.txt
@@ -32,9 +32,22 @@ if(LINUX)
)
endif()
- add_library(misc STATIC
- ${misc_SOURCES}
- )
+ # Add_library doesn't like to get an empty source file list.
+ # I tried several variations on this theme to test whether the list
+ # was not empty and was not successful in getting it to work
+ # on both Alpine and RPi.
+ #if("${misc_SOURCES}")
+ # This is less elegant and less maintainable but it works.
+
+ if ((NOT HAVE_STRLCPY) OR (NOT HAVE_STRLCAT))
+ add_library(misc STATIC
+ ${misc_SOURCES}
+ )
+ else()
+ set(MISC_LIBRARIES "" CACHE INTERNAL "")
+ endif()
+
+
elseif(WIN32 OR CYGWIN) # windows
diff --git a/external/regex/regex.h b/external/regex/regex.h
index 52b5fede..a84f6a99 100644
--- a/external/regex/regex.h
+++ b/external/regex/regex.h
@@ -208,7 +208,8 @@ typedef unsigned long int reg_syntax_t;
some interfaces). When a regexp is compiled, the syntax used is
stored in the pattern buffer, so changing this does not affect
already-compiled regexps. */
-REGEX_VARIABLE_IMPEXP reg_syntax_t re_syntax_options;
+//REGEX_VARIABLE_IMPEXP reg_syntax_t re_syntax_options;
+extern reg_syntax_t re_syntax_options;
/* Define combinations of the above bits for the standard possibilities.
(The [[[ comments delimit what gets put into the Texinfo file, so
diff --git a/man/atest.1 b/man/atest.1
index a1b554c2..58c90f64 100644
--- a/man/atest.1
+++ b/man/atest.1
@@ -37,6 +37,10 @@ Data rate in bits/sec. Standard values are 300, 1200, 2400, 4800, 9600.
4800 bps uses 8PSK based on V.27 standard.
.P
9600 bps and up uses K9NG/G3RUH standard.
+.P
+AIS for ship Automatic Identification System.
+.P
+EAS for Emergency Alert System (EAS) Specific Area Message Encoding (SAME).
.RE
.RE
.PD
diff --git a/man/gen_packets.1 b/man/gen_packets.1
index ba782fe1..740d4db4 100644
--- a/man/gen_packets.1
+++ b/man/gen_packets.1
@@ -46,6 +46,8 @@ Data rate in bits/sec for first channel. Standard values are 300, 1200, 2400, 4
4800 bps uses 8PSK based on V.27 standard.
.P
9600 bps and up uses K9NG/G3RUH standard.
+.P
+EAS for Emergency Alert System (EAS) Specific Area Message Encoding (SAME).
.RE
.RE
.PD
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a2c3963d..19dada4a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -10,6 +10,7 @@ include_directories(
${PORTAUDIO_INCLUDE_DIRS}
${SNDIO_INCLUDE_DIRS}
${CUSTOM_GEOTRANZ_DIR}
+ ${GPIOD_INCLUDE_DIRS}
${CUSTOM_HIDAPI_DIR}
)
@@ -32,6 +33,7 @@ list(APPEND direwolf_SOURCES
beacon.c
config.c
decode_aprs.c
+ deviceid.c
dedupe.c
demod_9600.c
demod_afsk.c
@@ -155,6 +157,7 @@ target_link_libraries(direwolf
${ALSA_LIBRARIES}
${UDEV_LIBRARIES}
${PORTAUDIO_LIBRARIES}
+ ${GPIOD_LIBRARIES}
${SNDIO_LIBRARIES}
${AVAHI_LIBRARIES}
)
@@ -169,6 +172,7 @@ endif()
# decode_aprs
list(APPEND decode_aprs_SOURCES
decode_aprs.c
+ deviceid.c
ais.c
kiss_frame.c
ax25_pad.c
@@ -353,6 +357,7 @@ list(APPEND atest_SOURCES
ax25_pad.c
ax25_pad2.c
decode_aprs.c
+ deviceid.c
dwgpsnmea.c
dwgps.c
dwgpsd.c
diff --git a/src/atest.c b/src/atest.c
index aec626f2..c5f4ec50 100644
--- a/src/atest.c
+++ b/src/atest.c
@@ -2,7 +2,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
-// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2019, 2021, 2022 John Langner, WB2OSZ
+// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2019, 2021, 2022, 2023 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
@@ -276,12 +276,12 @@ int main (int argc, char *argv[])
case 'B': /* -B for data Bit rate */
/* Also implies modem type based on speed. */
- /* Special case "AIS" rather than number. */
+ /* Special cases AIS, EAS rather than number. */
if (strcasecmp(optarg, "AIS") == 0) {
- B_opt = 12345; // See special case below.
+ B_opt = 0xA15A15; // See special case below.
}
else if (strcasecmp(optarg, "EAS") == 0) {
- B_opt = 23456; // See special case below.
+ B_opt = 0xEA5EA5; // See special case below.
}
else {
B_opt = atoi(optarg);
@@ -425,11 +425,6 @@ int main (int argc, char *argv[])
my_audio_config.achan[0].baud = B_opt;
- if (my_audio_config.achan[0].baud < MIN_BAUD || my_audio_config.achan[0].baud > MAX_BAUD) {
- text_color_set(DW_COLOR_ERROR);
- dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD);
- exit (EXIT_FAILURE);
- }
/* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */
/* that need to be kept in sync. Maybe it could be a common function someday. */
@@ -438,7 +433,6 @@ int main (int argc, char *argv[])
my_audio_config.achan[0].modem_type = MODEM_AFSK;
my_audio_config.achan[0].mark_freq = 1615;
my_audio_config.achan[0].space_freq = 1785;
- //strlcpy (my_audio_config.achan[0].profiles, "A", sizeof(my_audio_config.achan[0].profiles));
}
else if (my_audio_config.achan[0].baud < 600) { // e.g. HF SSB packet
my_audio_config.achan[0].modem_type = MODEM_AFSK;
@@ -446,13 +440,11 @@ int main (int argc, char *argv[])
my_audio_config.achan[0].space_freq = 1800;
// Previously we had a "D" which was fine tuned for 300 bps.
// In v1.7, it's not clear if we should use "B" or just stick with "A".
- //strlcpy (my_audio_config.achan[0].profiles, "B", sizeof(my_audio_config.achan[0].profiles));
}
else if (my_audio_config.achan[0].baud < 1800) { // common 1200
my_audio_config.achan[0].modem_type = MODEM_AFSK;
my_audio_config.achan[0].mark_freq = DEFAULT_MARK_FREQ;
my_audio_config.achan[0].space_freq = DEFAULT_SPACE_FREQ;
- // Should default to E+ or something similar later.
}
else if (my_audio_config.achan[0].baud < 3600) {
my_audio_config.achan[0].modem_type = MODEM_QPSK;
@@ -466,14 +458,14 @@ int main (int argc, char *argv[])
my_audio_config.achan[0].space_freq = 0;
strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles));
}
- else if (my_audio_config.achan[0].baud == 12345) { // Hack for different use of 9600
+ else if (my_audio_config.achan[0].baud == 0xA15A15) { // Hack for different use of 9600
my_audio_config.achan[0].modem_type = MODEM_AIS;
my_audio_config.achan[0].baud = 9600;
my_audio_config.achan[0].mark_freq = 0;
my_audio_config.achan[0].space_freq = 0;
strlcpy (my_audio_config.achan[0].profiles, " ", sizeof(my_audio_config.achan[0].profiles)); // avoid getting default later.
}
- else if (my_audio_config.achan[0].baud == 23456) {
+ else if (my_audio_config.achan[0].baud == 0xEA5EA5) {
my_audio_config.achan[0].modem_type = MODEM_EAS;
my_audio_config.achan[0].baud = 521; // Actually 520.83 but we have an integer field here.
// Will make more precise in afsk demod init.
@@ -488,6 +480,12 @@ int main (int argc, char *argv[])
strlcpy (my_audio_config.achan[0].profiles, " ", sizeof(my_audio_config.achan[0].profiles)); // avoid getting default later.
}
+ if (my_audio_config.achan[0].baud < MIN_BAUD || my_audio_config.achan[0].baud > MAX_BAUD) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD);
+ exit (EXIT_FAILURE);
+ }
+
/*
* -g option means force g3RUH regardless of speed.
*/
@@ -759,7 +757,7 @@ int audio_get (int a)
* This is called when we have a good frame.
*/
-void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, int is_fx25, retry_t retries, char *spectrum)
+void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, fec_type_t fec_type, retry_t retries, char *spectrum)
{
char stemp[500];
@@ -828,29 +826,31 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev
strlcat (heard, ")", sizeof(heard));
}
- if (my_audio_config.achan[chan].fix_bits == RETRY_NONE && my_audio_config.achan[chan].passall == 0) {
- dw_printf ("%s audio level = %s %s\n", heard, alevel_text, spectrum);
- }
- else if (is_fx25) {
- dw_printf ("%s audio level = %s %s\n", heard, alevel_text, spectrum);
- }
- else {
- assert (retries >= RETRY_NONE && retries <= RETRY_MAX);
- dw_printf ("%s audio level = %s [%s] %s\n", heard, alevel_text, retry_text[(int)retries], spectrum);
+ switch (fec_type) {
+
+ case fec_type_fx25:
+ dw_printf ("%s audio level = %s FX.25 %s\n", heard, alevel_text, spectrum);
+ break;
+
+ case fec_type_il2p:
+ dw_printf ("%s audio level = %s IL2P %s\n", heard, alevel_text, spectrum);
+ break;
+
+ case fec_type_none:
+ default:
+ if (my_audio_config.achan[chan].fix_bits == RETRY_NONE && my_audio_config.achan[chan].passall == 0) {
+ // No fix_bits or passall specified.
+ dw_printf ("%s audio level = %s %s\n", heard, alevel_text, spectrum);
+ }
+ else {
+ assert (retries >= RETRY_NONE && retries <= RETRY_MAX); // validate array index.
+ dw_printf ("%s audio level = %s [%s] %s\n", heard, alevel_text, retry_text[(int)retries], spectrum);
+ }
+ break;
}
#endif
-//#if defined(EXPERIMENT_G) || defined(EXPERIMENT_H)
-// int j;
-//
-// for (j=0; j0 for yes. */
+ /* First channel defaults to 2 for yes with default config. */
+ /* 1 means it was defined by user. */
+
+ int copy_from; /* >=0 means copy contents from another audio device. */
+ /* In this case we don't have device names, below. */
+ /* Num channels, samples/sec, and bit/sample are copied from */
+ /* original device and can't be changed. */
+ /* -1 for normal case. */
char adevice_in[80]; /* Name of the audio input device (or file?). */
- /* TODO: Can be "-" to read from stdin. */
+ /* Can be udp:nnn for UDP or "-" 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 samples_per_sec; /* Audio sampling rate. Typically 11025, 22050, 44100, or 48000. */
int bits_per_sample; /* 8 (unsigned char) or 16 (signed short). */
} adev[MAX_ADEVS];
@@ -151,6 +159,17 @@ struct audio_s {
struct achan_param_s {
+ // Currently, we have a fixed mapping from audio sources to channel.
+ //
+ // ADEVICE CHANNEL (mono) (stereo)
+ // 0 0 0, 1
+ // 1 2 2, 3
+ // 2 4 4, 5
+ //
+ // A future feauture might allow the user to specify a different audio source.
+ // This would allow multiple modems (with associated channel) to share an audio source.
+ // int audio_source; // Default would be [0,1,2,3,4,5]
+
// What else should be moved out of structure and enlarged when NETTNC is implemented. ???
char mycall[AX25_MAX_ADDR_LEN]; /* Call associated with this radio channel. */
/* Could all be the same or different. */
@@ -293,6 +312,7 @@ struct audio_s {
/* the case for CubieBoard where it was longer. */
/* This is filled in by ptt_init so we don't have to */
/* recalculate it each time we access it. */
+ /* Also GPIO chip name for GPIOD method. Looks like 'gpiochip4' */
/* This could probably be collapsed into ptt_device instead of being separate. */
@@ -415,7 +435,8 @@ struct audio_s {
#define DEFAULT_BITS_PER_SAMPLE 16
-#define DEFAULT_FIX_BITS RETRY_INVERT_SINGLE
+#define DEFAULT_FIX_BITS RETRY_NONE // Interesting research project but even a single bit fix up
+ // will occasionally let corrupted packets through.
/*
* Standard for AFSK on VHF FM.
@@ -445,11 +466,11 @@ struct audio_s {
*/
#define DEFAULT_DWAIT 0
-#define DEFAULT_SLOTTIME 10
+#define DEFAULT_SLOTTIME 10 // *10mS = 100mS
#define DEFAULT_PERSIST 63
-#define DEFAULT_TXDELAY 30
-#define DEFAULT_TXTAIL 10
-#define DEFAULT_FULLDUP 0
+#define DEFAULT_TXDELAY 30 // *10mS = 300mS
+#define DEFAULT_TXTAIL 10 // *10mS = 100mS
+#define DEFAULT_FULLDUP 0 // false = half duplex
/*
* Note that we have two versions of these in audio.c and audio_win.c.
diff --git a/src/ax25_link.c b/src/ax25_link.c
index ab2875d9..98d6c45e 100644
--- a/src/ax25_link.c
+++ b/src/ax25_link.c
@@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
-// Copyright (C) 2016, 2017, 2018 John Langner, WB2OSZ
+// Copyright (C) 2016, 2017, 2018, 2023 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
@@ -194,14 +194,16 @@
// Debug switches for different types of information.
// Should have command line options instead of changing source and recompiling.
-static int s_debug_protocol_errors = 1; // Less serious Protocol errors.
+static int s_debug_protocol_errors = 0; // Less serious Protocol errors.
// Useful for debugging but unnecessarily alarming other times.
+ // Was it intentially left on for release 1.6?
static int s_debug_client_app = 0; // Interaction with client application.
// dl_connect_request, dl_data_request, dl_data_indication, etc.
static int s_debug_radio = 0; // Received frames and channel busy status.
// lm_data_indication, lm_channel_busy
+
static int s_debug_variables = 0; // Variables, state changes.
static int s_debug_retry = 0; // Related to lost I frames, REJ, SREJ, timeout, resending.
@@ -250,7 +252,7 @@ typedef struct ax25_dlsm_s {
// notifications about state changes.
- char addrs[AX25_MAX_REPEATERS][AX25_MAX_ADDR_LEN];
+ char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
// Up to 10 addresses, same order as in frame.
int num_addr; // Number of addresses. Should be in range 2 .. 10.
@@ -594,6 +596,8 @@ static int AX25MODULO(int n, int m, const char *file, const char *func, int line
#define PAUSE_TM201 pause_tm201(S, __func__, __LINE__)
#define RESUME_TM201 resume_tm201(S, __func__, __LINE__)
+// TODO: add SELECT_T1_VALUE for debugging.
+
static void dl_data_indication (ax25_dlsm_t *S, int pid, char *data, int len);
@@ -1075,12 +1079,31 @@ void dl_disconnect_request (dlq_item_t *E)
case state_1_awaiting_connection:
case state_5_awaiting_v22_connection:
-// TODO: "requeue." Not sure what to do here.
-// If we put it back in the queue we will get it back again probably still in same state.
-// Need a way to defer it until the next state change.
+// Erratum: The protocol spec says "requeue." If we put disconnect req back in the
+// queue we will probably get it back again here while still in same state.
+// I don't think we would want to delay it until the next state transition.
+
+// Suppose someone tried to connect to another station, which is not responding, and decided to cancel
+// before all of the SABMe retries were used up. I think we would want to transmit a DISC, send a disc
+// notice to the user, and go directly into disconnected state, rather than into awaiting release.
+// New code v1.7 dev, May 6 2023
+
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("Stream %d: In progress connection attempt to %s terminated by user.\n", S->stream_id, S->addrs[PEERCALL]);
+ discard_i_queue (S);
+ SET_RC(0);
+ int p1 = 1;
+ int nopid0 = 0;
+ packet_t pp15 = ax25_u_frame (S->addrs, S->num_addr, cr_cmd, frame_type_U_DISC, p1, nopid0, NULL, 0);
+ lm_data_request (S->chan, TQ_PRIO_1_LO, pp15);
+
+ STOP_T1; // started in establish_data_link.
+ STOP_T3; // probably don't need.
+ enter_new_state (S, state_0_disconnected, __func__, __LINE__);
+ server_link_terminated (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], 0);
break;
-
+
case state_2_awaiting_release:
{
// We have previously started the disconnect sequence and are waiting
@@ -1580,13 +1603,49 @@ void dl_unregister_callsign (dlq_item_t *E)
* - Incoming connected data, from application still in the queue.
* - I frames which have been transmitted but not yet acknowledged.
*
+ * Confusion: https://github.com/wb2osz/direwolf/issues/427
+ *
+ * There are different, inconsistent versions of the protocol spec.
+ *
+ * One of them simply has:
+ *
+ * CallFrom is our call
+ * CallTo is the call of the other station
+ *
+ * A more detailed version has the same thing in the table of fields:
+ *
+ * CallFrom 10 bytes Our CallSign
+ * CallTo 10 bytes Other CallSign
+ *
+ * (My first implementation went with that.)
+ *
+ * HOWEVER, shortly after that, is contradictory information:
+ *
+ * Careful must be exercised to fill correctly both the CallFrom
+ * and CallTo fields to match the ones of an existing connection,
+ * otherwise AGWPE won’t return any information at all from this query.
+ *
+ * The order of the CallFrom and CallTo is not trivial, it should
+ * reflect the order used to start the connection, so
+ *
+ * * If we started the connection CallFrom=US and CallTo=THEM
+ * * If the other end started the connection CallFrom=THEM and CallTo=US
+ *
+ * This seems to make everything unnecessarily more complicated.
+ * We should only care about the stream going from the local station to the
+ * remote station. Why would it matter who reqested the link? The state
+ * machine doesn't even contain this information so the TNC doesn't know.
+ * The client app interface needs to behave differently for the two cases.
+ *
+ * The new code, below, May 2023, should handle both of those cases.
+ *
*------------------------------------------------------------------------------*/
void dl_outstanding_frames_request (dlq_item_t *E)
{
ax25_dlsm_t *S;
- int ok_to_create = 0; // must exist already.
-
+ const int ok_to_create = 0; // must exist already.
+ int reversed_addrs = 0;
if (s_debug_client_app) {
text_color_set(DW_COLOR_DEBUG);
@@ -1594,12 +1653,28 @@ void dl_outstanding_frames_request (dlq_item_t *E)
}
S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create);
-
- if (S == NULL) {
- text_color_set(DW_COLOR_ERROR);
- dw_printf ("Can't get outstanding frames for %s -> %s, chan %d\n", E->addrs[OWNCALL], E->addrs[PEERCALL], E->chan);
- server_outstanding_frames_reply (E->chan, E->client, E->addrs[OWNCALL], E->addrs[PEERCALL], 0);
- return;
+ if (S != NULL) {
+ reversed_addrs = 0;
+ }
+ else {
+ // Try swapping the addresses.
+ // this is communicating with the client app, not over the air,
+ // so we don't need to worry about digipeaters.
+
+ char swapped[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
+ memset (swapped, 0, sizeof(swapped));
+ strlcpy (swapped[PEERCALL], E->addrs[OWNCALL], sizeof(swapped[PEERCALL]));
+ strlcpy (swapped[OWNCALL], E->addrs[PEERCALL], sizeof(swapped[OWNCALL]));
+ S = get_link_handle (swapped, E->num_addr, E->chan, E->client, ok_to_create);
+ if (S != NULL) {
+ reversed_addrs = 1;
+ }
+ else {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Can't get outstanding frames for %s -> %s, chan %d\n", E->addrs[OWNCALL], E->addrs[PEERCALL], E->chan);
+ server_outstanding_frames_reply (E->chan, E->client, E->addrs[OWNCALL], E->addrs[PEERCALL], 0);
+ return;
+ }
}
// Add up these
@@ -1628,7 +1703,13 @@ void dl_outstanding_frames_request (dlq_item_t *E)
}
}
- server_outstanding_frames_reply (S->chan, S->client, S->addrs[OWNCALL], S->addrs[PEERCALL], count1 + count2);
+ if (reversed_addrs) {
+ // Other end initiated the link.
+ server_outstanding_frames_reply (S->chan, S->client, S->addrs[PEERCALL], S->addrs[OWNCALL], count1 + count2);
+ }
+ else {
+ server_outstanding_frames_reply (S->chan, S->client, S->addrs[OWNCALL], S->addrs[PEERCALL], count1 + count2);
+ }
} // end dl_outstanding_frames_request
@@ -4561,6 +4642,8 @@ static void dm_frame (ax25_dlsm_t *S, int f)
if (f == 1) {
text_color_set(DW_COLOR_INFO);
dw_printf ("%s doesn't understand AX.25 v2.2. Trying v2.0 ...\n", S->addrs[PEERCALL]);
+ dw_printf ("You can avoid this failed attempt and speed up the\n");
+ dw_printf ("process by putting \"V20 %s\" in the configuration file.\n", S->addrs[PEERCALL]);
INIT_T1V_SRT;
@@ -4849,6 +4932,8 @@ static void frmr_frame (ax25_dlsm_t *S)
text_color_set(DW_COLOR_INFO);
dw_printf ("%s doesn't understand AX.25 v2.2. Trying v2.0 ...\n", S->addrs[PEERCALL]);
+ dw_printf ("You can avoid this failed attempt and speed up the\n");
+ dw_printf ("process by putting \"V20 %s\" in the configuration file.\n", S->addrs[PEERCALL]);
INIT_T1V_SRT;
@@ -6148,7 +6233,7 @@ static void select_t1_value (ax25_dlsm_t *S)
// This goes up exponentially if implemented as documented!
// For example, if we were trying to connect to a station which is not there, we
- // would retry after 3, the 8, 16, 32, ... and not time out for over an hour.
+ // would retry after 3, then 8, 16, 32, ... and not time out for over an hour.
// That's ridiculous. Let's try increasing it by a quarter second each time.
// We now give up after about a minute.
@@ -6165,12 +6250,30 @@ static void select_t1_value (ax25_dlsm_t *S)
}
+// See https://groups.io/g/direwolf/topic/100782658#8542
+// Perhaps the demands of file transfer lead to this problem.
+
+// "Temporary" hack.
+// Automatic fine tuning of t1v generally works well, but on very rare occasions, it gets wildly out of control.
+// Until I have more time to properly diagnose this, add some guardrails so it does not go flying off a cliff.
+
+// The initial value of t1v is frack + frack * 2 (number of digipeateers in path)
+// If anything, it should automatically be adjusted down.
+// Let's say, something smells fishy if it exceeds twice that initial value.
+
+// TODO: Add some instrumentation to record where this was called from and all the values in the printf below.
+
+#if 1
+ if (S->t1v < 0.25 || S->t1v > 2 * (g_misc_config_p->frack * (2 * (S->num_addr - 2) + 1)) ) {
+ INIT_T1V_SRT;
+ }
+#else
if (S->t1v < 0.99 || S->t1v > 30) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("INTERNAL ERROR? Stream %d: select_t1_value, rc = %d, t1 remaining = %.3f, old srt = %.3f, new srt = %.3f, Extreme new t1v = %.3f\n",
S->stream_id, S->rc, S->t1_remaining_when_last_stopped, old_srt, S->srt, S->t1v);
}
-
+#endif
} /* end select_t1_value */
diff --git a/src/beacon.c b/src/beacon.c
index 69a72701..de3d90d0 100644
--- a/src/beacon.c
+++ b/src/beacon.c
@@ -892,7 +892,7 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
bp->symtab, bp->symbol,
bp->power, bp->height, bp->gain, bp->dir,
G_UNKNOWN, G_UNKNOWN, /* course, speed */
- bp->freq, bp->tone, bp->offset,
+ bp->freq, bp->tone_type, bp->tone, bp->offset,
super_comment,
info, sizeof(info));
strlcat (beacon_text, info, sizeof(beacon_text));
@@ -904,7 +904,7 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
bp->symtab, bp->symbol,
bp->power, bp->height, bp->gain, bp->dir,
G_UNKNOWN, G_UNKNOWN, /* course, speed */
- bp->freq, bp->tone, bp->offset, super_comment,
+ bp->freq, bp-> tone_type, bp->tone, bp->offset, super_comment,
info, sizeof(info));
strlcat (beacon_text, info, sizeof(beacon_text));
break;
@@ -935,7 +935,7 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
bp->symtab, bp->symbol,
bp->power, bp->height, bp->gain, bp->dir,
coarse, (int)roundf(gpsinfo->speed_knots),
- bp->freq, bp->tone, bp->offset,
+ bp->freq, bp->tone_type, bp->tone, bp->offset,
super_comment,
info, sizeof(info));
strlcat (beacon_text, info, sizeof(beacon_text));
diff --git a/src/cm108.c b/src/cm108.c
index 8c8fc5ed..ff3ff792 100644
--- a/src/cm108.c
+++ b/src/cm108.c
@@ -34,6 +34,7 @@
* We have a few commercial products:
*
* DINAH https://hamprojects.info/dinah/
+ * PAUL https://hamprojects.info/paul/
* DMK URI http://www.dmkeng.com/URI_Order_Page.htm
* RB-USB RIM http://www.repeater-builder.com/products/usb-rim-lite.html
* RA-35 http://www.masterscommunications.com/products/radio-adapter/ra35.html
@@ -93,6 +94,12 @@
* with a single USB Audio Adapter, but does not automatically handle the multiple device case.
* Manual configuration needs to be used in this case.
*
+ * Here is something new and interesting. The All in One cable (AIOC).
+ * https://github.com/skuep/AIOC/tree/master
+ *
+ * A microcontroller is used to emulate a CM108-compatible soundcard
+ * and a serial port. It fits right on the side of a Bao Feng or similar.
+ *
*---------------------------------------------------------------*/
#include "direwolf.h"
@@ -178,6 +185,11 @@ static int cm108_write (char *name, int iomask, int iodata);
#define SSS_PID2 0x1607
#define SSS_PID3 0x160b
+// https://github.com/skuep/AIOC/blob/master/stm32/aioc-fw/Src/usb_descriptors.h
+
+#define AIOC_VID 0x1209
+#define AIOC_PID 0x7388
+
// Device VID PID Number of GPIO
// ------ --- --- --------------
@@ -217,7 +229,9 @@ static int cm108_write (char *name, int iomask, int iodata);
|| p == CMEDIA_PID_CM119A \
|| p == CMEDIA_PID_CM119B )) \
|| \
- (v == SSS_VID && (p == SSS_PID1 || p == SSS_PID2 || p == SSS_PID3)) )
+ (v == SSS_VID && (p == SSS_PID1 || p == SSS_PID2 || p == SSS_PID3)) \
+ || \
+ (v == AIOC_VID && p == AIOC_PID) )
// Look out for null source pointer, and avoid buffer overflow on destination.
@@ -243,6 +257,12 @@ static void substr_se (char *dest, const char *src, int start, int endp1)
#endif
+// Maximum length of name for PTT HID.
+// For Linux, this was originally 17 to handle names like /dev/hidraw3.
+// Windows has more complicated names. The longest I saw was 95 but longer have been reported.
+
+#define MAXX_HIDRAW_NAME_LEN 128
+
/*
* Result of taking inventory of USB soundcards and USB HIDs.
*/
@@ -258,7 +278,8 @@ struct thing_s {
// Oversized to silence a compiler warning.
char plughw2[72]; // With name rather than number.
char devpath[128]; // Kernel dev path. Does not include /sys mount point.
- char devnode_hidraw[128]; // e.g. /dev/hidraw3 - for Linux - was length 17
+ char devnode_hidraw[MAXX_HIDRAW_NAME_LEN];
+ // e.g. /dev/hidraw3 - for Linux - was length 17
// The Windows path for a HID looks like this, lengths up to 95 seen.
// \\?\hid#vid_0d8c&pid_000c&mi_03#8&164d11c9&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
char devnode_usb[25]; // e.g. /dev/bus/usb/001/012
@@ -852,7 +873,7 @@ void cm108_find_ptt (char *output_audio_device, char *ptt_device, int ptt_devic
*
* Errors: A descriptive error message will be printed for any problem.
*
- * Future: For our initial implementation we are making the simplifying
+ * Shortcut: For our initial implementation we are making the simplifying
* restriction of using only one GPIO pin per device and limit
* configuration to PTT only.
* Longer term, we might want to have DCD, and maybe other
@@ -882,7 +903,6 @@ int cm108_set_gpio_pin (char *name, int num, int state)
iomask = 1 << (num - 1); // 0=input, 1=output
iodata = state << (num - 1); // 0=low, 1=high
-
return (cm108_write (name, iomask, iodata));
} /* end cm108_set_gpio_pin */
diff --git a/src/config.c b/src/config.c
index 4f652939..c1b09499 100644
--- a/src/config.c
+++ b/src/config.c
@@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
-// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2021 John Langner, WB2OSZ
+// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2021, 2023 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
@@ -709,6 +709,14 @@ static char *split (char *string, int rest_of_line)
*
*--------------------------------------------------------------------*/
+static void rtfm()
+{
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("See online documentation:\n");
+ dw_printf (" stable release: https://github.com/wb2osz/direwolf/tree/master/doc\n");
+ dw_printf (" development version: https://github.com/wb2osz/direwolf/tree/dev/doc\n");
+ dw_printf (" additional topics: https://github.com/wb2osz/direwolf-doc\n");
+}
void config_init (char *fname, struct audio_s *p_audio_config,
struct digi_config_s *p_digi_config,
@@ -747,12 +755,13 @@ void config_init (char *fname, struct audio_s *p_audio_config,
strlcpy (p_audio_config->adev[adevice].adevice_out, DEFAULT_ADEVICE, sizeof(p_audio_config->adev[adevice].adevice_out));
p_audio_config->adev[adevice].defined = 0;
+ p_audio_config->adev[adevice].copy_from = -1;
p_audio_config->adev[adevice].num_channels = DEFAULT_NUM_CHANNELS; /* -2 stereo */
p_audio_config->adev[adevice].samples_per_sec = DEFAULT_SAMPLES_PER_SEC; /* -r option */
p_audio_config->adev[adevice].bits_per_sample = DEFAULT_BITS_PER_SAMPLE; /* -8 option for 8 instead of 16 bits */
}
- p_audio_config->adev[0].defined = 1;
+ p_audio_config->adev[0].defined = 2; // 2 means it was done by default and not the user's config file.
for (channel=0; channelmaxframe_extended = AX25_K_MAXFRAME_EXTENDED_DEFAULT; /* Max frames to send before ACK. mod 128 "Window" size. */
- p_misc_config->maxv22 = AX25_N2_RETRY_DEFAULT / 3; /* Max SABME before falling back to SABM. */
- p_misc_config->v20_addrs = NULL; /* Go directly to v2.0 for stations listed. */
+ p_misc_config->maxv22 = AX25_N2_RETRY_DEFAULT / 3; /* Send SABME this many times before falling back to SABM. */
+ p_misc_config->v20_addrs = NULL; /* Go directly to v2.0 for stations listed */
+ /* without trying v2.2 first. */
p_misc_config->v20_count = 0;
p_misc_config->noxid_addrs = NULL; /* Don't send XID to these stations. */
+ /* Might work with a partial v2.2 implementation */
+ /* on the other end. */
p_misc_config->noxid_count = 0;
/*
@@ -970,7 +982,8 @@ void config_init (char *fname, struct audio_s *p_audio_config,
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - Could not open config file %s\n", filepath);
dw_printf ("Try using -c command line option for alternate location.\n");
- return;
+ rtfm();
+ exit(EXIT_FAILURE);
}
dw_printf ("\nReading config file %s\n", filepath);
@@ -1003,7 +1016,11 @@ void config_init (char *fname, struct audio_s *p_audio_config,
* ADEVICE plughw:1,0 -- same for in and out.
* ADEVICE plughw:2,0 plughw:3,0 -- different in/out for a channel or channel pair.
* ADEVICE1 udp:7355 default -- from Software defined radio (SDR) via UDP.
- *
+ *
+ * New in 1.8: Ability to map to another audio device.
+ * This allows multiple modems (i.e. data speeds) on the same audio interface.
+ *
+ * ADEVICEn = n -- Copy from different already defined channel.
*/
/* Note that ALSA name can contain comma such as hw:1,0 */
@@ -1027,20 +1044,46 @@ void config_init (char *fname, struct audio_s *p_audio_config,
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing name of audio device for ADEVICE command on line %d.\n", line);
+ rtfm();
+ exit(EXIT_FAILURE);
+ }
+
+ // Do not allow same adevice to be defined more than once.
+ // Overriding the default for adevice 0 is ok.
+ // In that case definded was 2. That's why we check for 1, not just non-zero.
+
+ if (p_audio_config->adev[adevice].defined == 1) { // 1 means defined by user.
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file: ADEVICE%d can't be defined more than once. Line %d.\n", adevice, line);
continue;
}
p_audio_config->adev[adevice].defined = 1;
-
- /* First channel of device is valid. */
- p_audio_config->chan_medium[ADEVFIRSTCHAN(adevice)] = MEDIUM_RADIO;
- strlcpy (p_audio_config->adev[adevice].adevice_in, t, sizeof(p_audio_config->adev[adevice].adevice_in));
- strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out));
+ // New case for release 1.8.
- t = split(NULL,0);
- if (t != NULL) {
+ if (strcmp(t, "=") == 0) {
+ t = split(NULL,0);
+ if (t != NULL) {
+
+ }
+
+///////// to be continued.... FIXME
+
+ }
+ else {
+ /* First channel of device is valid. */
+ // This might be changed to UDP or STDIN when the device name is examined.
+ p_audio_config->chan_medium[ADEVFIRSTCHAN(adevice)] = MEDIUM_RADIO;
+
+ strlcpy (p_audio_config->adev[adevice].adevice_in, t, sizeof(p_audio_config->adev[adevice].adevice_in));
strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out));
+
+ t = split(NULL,0);
+ if (t != NULL) {
+ // Different audio devices for receive and transmit.
+ strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out));
+ }
}
}
@@ -1799,6 +1842,45 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_GPIO;
#endif
+ }
+ else if (strcasecmp(t, "GPIOD") == 0) {
+#if __WIN32__
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file line %d: %s with GPIOD is only available on Linux.\n", line, otname);
+#else
+#if defined(USE_GPIOD)
+ t = split(NULL,0);
+ if (t == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file line %d: Missing GPIO chip name for %s.\n", line, otname);
+ dw_printf ("Use the \"gpioinfo\" command to get a list of gpio chip names and corresponding I/O lines.\n");
+ continue;
+ }
+ strlcpy(p_audio_config->achan[channel].octrl[ot].out_gpio_name, t,
+ sizeof(p_audio_config->achan[channel].octrl[ot].out_gpio_name));
+
+ t = split(NULL,0);
+ if (t == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("Config file line %d: Missing GPIO number for %s.\n", line, otname);
+ continue;
+ }
+
+ if (*t == '-') {
+ p_audio_config->achan[channel].octrl[ot].out_gpio_num = atoi(t+1);
+ p_audio_config->achan[channel].octrl[ot].ptt_invert = 1;
+ }
+ else {
+ p_audio_config->achan[channel].octrl[ot].out_gpio_num = atoi(t);
+ p_audio_config->achan[channel].octrl[ot].ptt_invert = 0;
+ }
+ p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_GPIOD;
+#else
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Application was not built with optional support for GPIOD.\n");
+ dw_printf ("Install packages gpiod and libgpiod-dev, remove 'build' subdirectory, then rebuild.\n");
+#endif /* USE_GPIOD*/
+#endif /* __WIN32__ */
}
else if (strcasecmp(t, "LPT") == 0) {
@@ -1986,7 +2068,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
dw_printf ("Config file line %d: %s with CM108 is only available when USB Audio GPIO support is enabled.\n", line, otname);
dw_printf ("You must rebuild direwolf with CM108 Audio Adapter GPIO PTT support.\n");
dw_printf ("See Interface Guide for details.\n");
-
+ rtfm();
exit (EXIT_FAILURE);
#endif
}
@@ -2122,6 +2204,9 @@ void config_init (char *fname, struct audio_s *p_audio_config,
/*
* DWAIT n - Extra delay for receiver squelch. n = 10 mS units.
+ *
+ * Why did I do this? Just add more to TXDELAY.
+ * Now undocumented in User Guide. Might disappear someday.
*/
else if (strcasecmp(t, "DWAIT") == 0) {
@@ -2157,14 +2242,20 @@ void config_init (char *fname, struct audio_s *p_audio_config,
continue;
}
n = atoi(t);
- if (n >= 0 && n <= 255) {
+ if (n >= 5 && n < 50) {
+ // 0 = User has no clue. This would be no delay.
+ // 10 = Default.
+ // 50 = Half second. User might think it is mSec and use 100.
p_audio_config->achan[channel].slottime = n;
}
else {
p_audio_config->achan[channel].slottime = DEFAULT_SLOTTIME;
text_color_set(DW_COLOR_ERROR);
- dw_printf ("Line %d: Invalid delay time for persist algorithm. Using %d.\n",
+ dw_printf ("Line %d: Invalid delay time for persist algorithm. Using default %d.\n",
line, p_audio_config->achan[channel].slottime);
+ dw_printf ("Read the Dire Wolf User Guide, \"Radio Channel - Transmit Timing\"\n");
+ dw_printf ("section, to understand what this means.\n");
+ dw_printf ("Why don't you just use the default?\n");
}
}
@@ -2181,14 +2272,17 @@ void config_init (char *fname, struct audio_s *p_audio_config,
continue;
}
n = atoi(t);
- if (n >= 0 && n <= 255) {
+ if (n >= 5 && n <= 250) {
p_audio_config->achan[channel].persist = n;
}
else {
p_audio_config->achan[channel].persist = DEFAULT_PERSIST;
text_color_set(DW_COLOR_ERROR);
- dw_printf ("Line %d: Invalid probability for persist algorithm. Using %d.\n",
+ dw_printf ("Line %d: Invalid probability for persist algorithm. Using default %d.\n",
line, p_audio_config->achan[channel].persist);
+ dw_printf ("Read the Dire Wolf User Guide, \"Radio Channel - Transmit Timing\"\n");
+ dw_printf ("section, to understand what this means.\n");
+ dw_printf ("Why don't you just use the default?\n");
}
}
@@ -2206,6 +2300,23 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
n = atoi(t);
if (n >= 0 && n <= 255) {
+ text_color_set(DW_COLOR_ERROR);
+ if (n < 10) {
+ dw_printf ("Line %d: Setting TXDELAY this small is a REALLY BAD idea if you want other stations to hear you.\n",
+ line);
+ dw_printf ("Read the Dire Wolf User Guide, \"Radio Channel - Transmit Timing\"\n");
+ dw_printf ("section, to understand what this means.\n");
+ dw_printf ("Why don't you just use the default rather than reducing reliability?\n");
+ }
+ else if (n >= 100) {
+ dw_printf ("Line %d: Keeping with tradition, going back to the 1980s, TXDELAY is in 10 millisecond units.\n",
+ line);
+ dw_printf ("Line %d: The value %d would be %.3f seconds which seems rather excessive. Are you sure you want that?\n",
+ line, n, (double)n * 10. / 1000.);
+ dw_printf ("Read the Dire Wolf User Guide, \"Radio Channel - Transmit Timing\"\n");
+ dw_printf ("section, to understand what this means.\n");
+ dw_printf ("Why don't you just use the default?\n");
+ }
p_audio_config->achan[channel].txdelay = n;
}
else {
@@ -2230,12 +2341,28 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
n = atoi(t);
if (n >= 0 && n <= 255) {
+ if (n < 5) {
+ dw_printf ("Line %d: Setting TXTAIL that small is a REALLY BAD idea if you want other stations to hear you.\n",
+ line);
+ dw_printf ("Read the Dire Wolf User Guide, \"Radio Channel - Transmit Timing\"\n");
+ dw_printf ("section, to understand what this means.\n");
+ dw_printf ("Why don't you just use the default rather than reducing reliability?\n");
+ }
+ else if (n >= 50) {
+ dw_printf ("Line %d: Keeping with tradition, going back to the 1980s, TXTAIL is in 10 millisecond units.\n",
+ line);
+ dw_printf ("Line %d: The value %d would be %.3f seconds which seems rather excessive. Are you sure you want that?\n",
+ line, n, (double)n * 10. / 1000.);
+ dw_printf ("Read the Dire Wolf User Guide, \"Radio Channel - Transmit Timing\"\n");
+ dw_printf ("section, to understand what this means.\n");
+ dw_printf ("Why don't you just use the default?\n");
+ }
p_audio_config->achan[channel].txtail = n;
}
else {
p_audio_config->achan[channel].txtail = DEFAULT_TXTAIL;
text_color_set(DW_COLOR_ERROR);
- dw_printf ("Line %d: Invalid time for transmit timing. Using %d.\n",
+ dw_printf ("Line %d: Invalid time for transmit timing. Using %d.\n",
line, p_audio_config->achan[channel].txtail);
}
}
@@ -2370,7 +2497,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
p_audio_config->achan[channel].il2p_invert_polarity = 0;
while ((t = split(NULL,0)) != NULL) {
- for (char *c = t; *t != '\0'; c++) {
+ for (char *c = t; *c != '\0'; c++) {
switch (*c) {
case '+':
p_audio_config->achan[channel].il2p_invert_polarity = 0;
@@ -2513,7 +2640,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Preemptive digipeating DROP option is discouraged.\n", line);
dw_printf ("It can create a via path which is misleading about the actual path taken.\n");
- dw_printf ("TRACE is the best choice for this feature.\n");
+ dw_printf ("PREEMPT is the best choice for this feature.\n");
p_digi_config->preempt[from_chan][to_chan] = PREEMPT_DROP;
t = split(NULL,0);
}
@@ -2521,11 +2648,11 @@ void config_init (char *fname, struct audio_s *p_audio_config,
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Preemptive digipeating MARK option is discouraged.\n", line);
dw_printf ("It can create a via path which is misleading about the actual path taken.\n");
- dw_printf ("TRACE is the best choice for this feature.\n");
+ dw_printf ("PREEMPT is the best choice for this feature.\n");
p_digi_config->preempt[from_chan][to_chan] = PREEMPT_MARK;
t = split(NULL,0);
}
- else if (strcasecmp(t, "TRACE") == 0) {
+ else if ((strcasecmp(t, "TRACE") == 0) || (strncasecmp(t, "PREEMPT", 7) == 0)){
p_digi_config->preempt[from_chan][to_chan] = PREEMPT_TRACE;
t = split(NULL,0);
}
@@ -2780,6 +2907,12 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
if (*t == 'i' || *t == 'I') {
from_chan = MAX_CHANS;
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file: FILTER IG ... on line %d.\n", line);
+ dw_printf ("Warning! Don't mess with IS>RF filtering unless you are an expert and have an unusual situation.\n");
+ dw_printf ("Warning! The default is fine for nearly all situations.\n");
+ dw_printf ("Warning! Be sure to read carefully and understand \"Successful-APRS-Gateway-Operation.pdf\" .\n");
+ dw_printf ("Warning! If you insist, be sure to add \" | i/180 \" so you don't break messaging.\n");
}
else {
from_chan = isdigit(*t) ? atoi(t) : -999;
@@ -2813,6 +2946,12 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
if (*t == 'i' || *t == 'I') {
to_chan = MAX_CHANS;
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file: FILTER ... IG ... on line %d.\n", line);
+ dw_printf ("Warning! Don't mess with RF>IS filtering unless you are an expert and have an unusual situation.\n");
+ dw_printf ("Warning! Expected behavior is for everything to go from RF to IS.\n");
+ dw_printf ("Warning! The default is fine for nearly all situations.\n");
+ dw_printf ("Warning! Be sure to read carefully and understand \"Successful-APRS-Gateway-Operation.pdf\" .\n");
}
else {
to_chan = isdigit(*t) ? atoi(t) : -999;
@@ -4448,6 +4587,13 @@ void config_init (char *fname, struct audio_s *p_audio_config,
if (t != NULL && strlen(t) > 0) {
p_igate_config->t2_filter = strdup (t);
+
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Warning - IGFILTER is a rarely needed expert level feature.\n", line);
+ dw_printf ("If you don't have a special situation and a good understanding of\n");
+ dw_printf ("how this works, you probably should not be messing with it.\n");
+ dw_printf ("The default behavior is appropriate for most situations.\n");
+ dw_printf ("Please read \"Successful-APRS-IGate-Operation.pdf\".\n");
}
}
@@ -4578,7 +4724,6 @@ void config_init (char *fname, struct audio_s *p_audio_config,
*
* In version 1.2 we allow 0 to disable listening.
*/
-// FIXME: complain if extra parameter e.g. port as in KISSPORT
else if (strcasecmp(t, "AGWPORT") == 0) {
int n;
@@ -4589,6 +4734,13 @@ void config_init (char *fname, struct audio_s *p_audio_config,
continue;
}
n = atoi(t);
+ t = split(NULL,0);
+ if (t != NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Unexpected \"%s\" after the port number.\n", line, t);
+ dw_printf ("Perhaps you were trying to use feature available only with KISSPORT.\n");
+ continue;
+ }
if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) {
p_misc_config->agwpe_port = n;
}
@@ -5511,6 +5663,7 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
b->symtab = '/';
b->symbol = '-'; /* house */
b->freq = G_UNKNOWN;
+ b->tone_type = 'T';
b->tone = G_UNKNOWN;
b->offset = G_UNKNOWN;
b->source = NULL;
@@ -5747,8 +5900,9 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
else if (strcasecmp(keyword, "POWER") == 0) {
b->power = atoi(value);
}
- else if (strcasecmp(keyword, "HEIGHT") == 0) {
+ else if (strcasecmp(keyword, "HEIGHT") == 0) { // This is in feet.
b->height = atoi(value);
+ // TODO: ability to add units suffix, e.g. 10m
}
else if (strcasecmp(keyword, "GAIN") == 0) {
b->gain = atoi(value);
@@ -5760,7 +5914,18 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
b->freq = atof(value);
}
else if (strcasecmp(keyword, "TONE") == 0) {
- b->tone = atof(value);
+ b->tone_type = 'T';
+ /* Try the legacy tone config, just a number */
+ if((b->tone = atof(value)) == 0) {
+ /* Legacy parsing failed, try with leading type char */
+ if(sscanf(value, "%c%f", &b->tone_type, &b->tone) != 2) {
+ /* That failed. Bad tone format, set to no tone */
+ b->tone_type = 'T';
+ b->tone = 0;
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file, line %d: Bad tone format, %s.\n", line, value);
+ }
+ }
}
else if (strcasecmp(keyword, "OFFSET") == 0 || strcasecmp(keyword, "OFF") == 0) {
b->offset = atof(value);
diff --git a/src/config.h b/src/config.h
index 360ac492..a92a7e82 100644
--- a/src/config.h
+++ b/src/config.h
@@ -221,6 +221,7 @@ struct misc_config_s {
char dir[3]; /* 1 or 2 of N,E,W,S, or empty for omni. */
float freq; /* MHz. */
+ char tone_type; /* Tone(T), Tone Squelch(C) or DCS(D) */
float tone; /* Hz. */
float offset; /* MHz. */
diff --git a/src/decode_aprs.c b/src/decode_aprs.c
index d7cbd72f..08534f7a 100644
--- a/src/decode_aprs.c
+++ b/src/decode_aprs.c
@@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
-// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2017, 2022 John Langner, WB2OSZ
+// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2017, 2022, 2023 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
@@ -17,6 +17,7 @@
// along with this program. If not, see .
//
+// TODO: Better error messages for examples here: http://lists.tapr.org/pipermail/aprssig_lists.tapr.org/2023-July/date.html
/*------------------------------------------------------------------
*
@@ -55,7 +56,7 @@
#include "decode_aprs.h"
#include "telemetry.h"
#include "ais.h"
-
+#include "deviceid.h"
#define TRUE 1
#define FALSE 0
@@ -123,7 +124,6 @@ static double get_longitude_9 (char *p, int quiet);
static time_t get_timestamp (decode_aprs_t *A, char *p);
static int get_maidenhead (decode_aprs_t *A, char *p);
static int data_extension_comment (decode_aprs_t *A, char *pdext);
-static void decode_tocall (decode_aprs_t *A, char *dest);
//static void get_symbol (decode_aprs_t *A, char dti, char *src, char *dest);
static void process_comment (decode_aprs_t *A, char *pstart, int clen);
@@ -169,7 +169,9 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr
//dw_printf ("DEBUG decode_aprs info=\"%s\"\n", pinfo);
- memset (A, 0, sizeof (*A));
+ if (third_party_src == NULL) {
+ memset (A, 0, sizeof (*A));
+ }
A->g_quiet = quiet;
@@ -234,6 +236,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr
memcpy(payload_src, (char*)pinfo+1, sizeof(payload_src)-1);
char *q = strchr(payload_src, '>');
if (q != NULL) *q = '\0';
+ A->g_has_thirdparty_header = 1;
decode_aprs (A, pp_payload, quiet, payload_src); // 1 means used recursively
ax25_delete (pp_payload);
return;
@@ -288,7 +291,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr
}
/*
- * Application might be in the destination field for most message types.
+ * Device/Application is in the destination field for most packet types.
* MIC-E format has part of location in the destination field.
*/
@@ -299,7 +302,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr
break;
default:
- decode_tocall (A, A->g_dest);
+ deviceid_decode_dest (A->g_dest, A->g_mfr, sizeof(A->g_mfr));
break;
}
@@ -318,6 +321,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr
{
aprs_ll_pos (A, pinfo, info_len);
}
+ A->g_packet_type = packet_type_position;
break;
@@ -330,10 +334,12 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr
if (strncmp((char*)pinfo, "$ULTW", 5) == 0)
{
aprs_ultimeter (A, (char*)pinfo, info_len); // TODO: produce obsolete error.
+ A->g_packet_type = packet_type_weather;
}
else
{
aprs_raw_nmea (A, pinfo, info_len);
+ A->g_packet_type = packet_type_position;
}
break;
@@ -341,17 +347,20 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr
case '`': /* Current Mic-E Data (not used in TM-D700) */
aprs_mic_e (A, pp, pinfo, info_len);
+ A->g_packet_type = packet_type_position;
break;
case ')': /* Item. */
aprs_item (A, pinfo, info_len);
+ A->g_packet_type = packet_type_item;
break;
case '/': /* Position with timestamp (no APRS messaging) */
case '@': /* Position with timestamp (with APRS messaging) */
aprs_ll_pos_time (A, pinfo, info_len);
+ A->g_packet_type = packet_type_position;
break;
@@ -360,42 +369,76 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr
/* Telemetry metadata. */
aprs_message (A, pinfo, info_len, quiet);
+
+ switch (A->g_message_subtype) {
+ case message_subtype_message:
+ case message_subtype_ack:
+ case message_subtype_rej:
+ A->g_packet_type = packet_type_message;
+ break;
+
+ case message_subtype_nws:
+ A->g_packet_type = packet_type_nws;
+ break;
+
+ case message_subtype_bulletin:
+ default:
+ break;
+
+ case message_subtype_telem_parm:
+ case message_subtype_telem_unit:
+ case message_subtype_telem_eqns:
+ case message_subtype_telem_bits:
+ A->g_packet_type = packet_type_telemetry;
+ break;
+
+ case message_subtype_directed_query:
+ A->g_packet_type = packet_type_query;
+ break;
+ }
break;
case ';': /* Object */
aprs_object (A, pinfo, info_len);
+ A->g_packet_type = packet_type_object;
break;
case '<': /* Station Capabilities */
aprs_station_capabilities (A, (char*)pinfo, info_len);
+ A->g_packet_type = packet_type_capabilities;
break;
case '>': /* Status Report */
aprs_status_report (A, (char*)pinfo, info_len);
+ A->g_packet_type = packet_type_status;
break;
case '?': /* General Query */
aprs_general_query (A, (char*)pinfo, info_len, quiet);
+ A->g_packet_type = packet_type_query;
break;
case 'T': /* Telemetry */
aprs_telemetry (A, (char*)pinfo, info_len, quiet);
+ A->g_packet_type = packet_type_telemetry;
break;
case '_': /* Positionless Weather Report */
aprs_positionless_weather_report (A, pinfo, info_len);
+ A->g_packet_type = packet_type_weather;
break;
case '{': /* user defined data */
aprs_user_defined (A, (char*)pinfo, info_len);
+ A->g_packet_type = packet_type_userdefined;
break;
case 't': /* Raw touch tone data - NOT PART OF STANDARD */
@@ -404,6 +447,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr
/* Might move into user defined data, above. */
aprs_raw_touch_tone (A, (char*)pinfo, info_len);
+ // no packet type for t/ filter
break;
case 'm': /* Morse Code data - NOT PART OF STANDARD */
@@ -413,6 +457,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr
/* Might move into user defined data, above. */
aprs_morse_code (A, (char*)pinfo, info_len);
+ // no packet type for t/ filter
break;
//case '}': /* third party header */
@@ -504,13 +549,17 @@ void decode_aprs_print (decode_aprs_t *A) {
//dw_printf ("DEBUG decode_aprs_print stemp3=%s mfr=%s\n", stemp, A->g_mfr);
if (strlen(A->g_mfr) > 0) {
- if (strcmp(A->g_dest, "APRS") == 0) {
- strlcat (stemp, "\nUse of \"APRS\" in the destination field is obsolete.", sizeof(stemp));
+ if (strcmp(A->g_dest, "APRS") == 0 ||
+ strcmp(A->g_dest, "BEACON") == 0 ||
+ strcmp(A->g_dest, "ID") == 0) {
+ strlcat (stemp, "\nUse of \"", sizeof(stemp));
+ strlcat (stemp, A->g_dest, sizeof(stemp));
+ strlcat (stemp, "\" in the destination field is obsolete.", sizeof(stemp));
strlcat (stemp, " You can help to improve the quality of APRS signals.", sizeof(stemp));
strlcat (stemp, "\nTell the sender (", sizeof(stemp));
strlcat (stemp, A->g_src, sizeof(stemp));
- strlcat (stemp, ") to use the proper product code from", sizeof(stemp));
- strlcat (stemp, " http://www.aprs.org/aprs11/tocalls.txt", sizeof(stemp));
+ strlcat (stemp, ") to use the proper product identifier from", sizeof(stemp));
+ strlcat (stemp, " https://github.com/aprsorg/aprs-deviceid ", sizeof(stemp));
}
else {
strlcat (stemp, ", ", sizeof(stemp));
@@ -535,7 +584,7 @@ void decode_aprs_print (decode_aprs_t *A) {
/* http://eng.usna.navy.mil/~bruninga/aprs/aprs11.html */
/* "The Antenna Gain in the PHG format on page 28 is in dBi." */
- snprintf (phg, sizeof(phg), ", %d W height=%d %ddBi %s", A->g_power, A->g_height, A->g_gain, A->g_directivity);
+ snprintf (phg, sizeof(phg), ", %d W height(HAAT)=%dft=%.0fm %ddBi %s", A->g_power, A->g_height, DW_FEET_TO_METERS(A->g_height), A->g_gain, A->g_directivity);
strlcat (stemp, phg, sizeof(stemp));
}
@@ -1089,7 +1138,9 @@ static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen)
*
* Function: aprs_mic_e
*
- * Purpose: Decode MIC-E (also Kenwood D7 & D700) packet.
+ * Purpose: Decode MIC-E (e.g. Kenwood D7 & D700) packet.
+ * This format is an overzelous quest to make the packet as short as possible.
+ * It uses non-printable characters and hacks wrapped in kludges.
*
* Inputs: info - Pointer to Information field.
* ilen - Information field length.
@@ -1098,31 +1149,120 @@ static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen)
*
* Description:
*
- * Destination Address Field -
+ * AX.25 Destination Address Field -
*
- * The 7-byte Destination Address field contains
+ * The 6-byte Destination Address field contains
* the following encoded information:
*
- * * The 6 latitude digits.
- * * A 3-bit Mic-E message identifier, specifying one of 7 Standard Mic-E
- * Message Codes or one of 7 Custom Message Codes or an Emergency
- * Message Code.
- * * The North/South and West/East Indicators.
- * * The Longitude Offset Indicator.
- * * The generic APRS digipeater path code.
- *
+ * Byte 1: Lat digit 1, message bit A
+ * Byte 2: Lat digit 2, message bit B
+ * Byte 3: Lat digit 3, message bit C
+ * Byte 4: Lat digit 4, N/S lat indicator
+ * Byte 5: Lat digit 5, Longitude offset
+ * Byte 6: Lat digit 6, W/E Long indicator
+ * *
* "Although the destination address appears to be quite unconventional, it is
* still a valid AX.25 address, consisting only of printable 7-bit ASCII values."
*
- * References: Mic-E TYPE CODES -- http://www.aprs.org/aprs12/mic-e-types.txt
+ * AX.25 Information Field - Starts with ' or `
+ *
+ * Bytes 1,2,3: Longitude
+ * Bytes 4,5,6: Speed and Course
+ * Byte 6: Symbol code
+ * Byte 7: Symbol Table ID
+ *
+ * The rest of it is a complicated comment field which can hold various information
+ * and must be intrepreted in a particular order. At this point we look for any
+ * prefix and/or suffix to identify the equipment type.
+ *
+ * References: Mic-E TYPE CODES -- http://www.aprs.org/aprs12/mic-e-types.txt
+ * Mic-E TEST EXAMPLES -- http://www.aprs.org/aprs12/mic-e-examples.txt
+ *
+ * Next, we have what Addedum 1.2 calls the "type byte." This prefix can be
+ * space Original MIC-E.
+ * > Kenwood HT.
+ * ] Kenwood Mobile.
+ * none.
+ *
+ * We also need to look at the last byte or two
+ * for a possible suffix to distinguish equipment types. Examples:
+ * >...... is D7
+ * >......= is D72
+ * >......^ is D74
+ *
+ * For other brands, it gets worse. There might a 2 character suffix.
+ * The prefix indicates whether messaging-capable. Examples:
+ * `....._.% Yaesu FTM-400DR
+ * `......_) Yaesu FTM-100D
+ * `......_3 Yaesu FT5D
*
- * This is up to date with the 24 Aug 16 version mentioning the TH-D74.
+ * '......|3 Byonics TinyTrack3
+ * '......|4 Byonics TinyTrack4
+ *
+ * Any prefix and suffix must be removed before futher processsing.
+ *
+ * Pick one: MIC-E Telemetry Data or "Status Text" (called a comment everywhere else).
+ *
+ * If the character after the symbol table id is "," (comma) or 0x1d, we have telemetry.
+ * (Is this obsoleted by the base-91 telemetry?)
+ *
+ * ` Two 2-character hexadecimal numbers. (Channels 1 & 3)
+ * ' Five 2-character hexadecimal numbers.
+ *
+ * Anything left over is a comment which can contain various types of information.
+ *
+ * If present, the MIC-E compressed altitude must be first.
+ * It is three base-91 characters followed by "}".
+ * Examples: "4T} "4T} ]"4T}
+ *
+ * We can also have frequency specification -- http://www.aprs.org/info/freqspec.txt
+ *
+ * Warning: Some Kenwood radios add CR at the end, in apparent violation of the spec.
+ * Watch out so it doesn't get included when looking for equipment type suffix.
*
* Mic-E TEST EXAMPLES -- http://www.aprs.org/aprs12/mic-e-examples.txt
*
- * Examples: `b9Z!4y>/>"4N}Paul's_TH-D7
+ * Examples: Observed on the air.
+ *
+ * KB1HNZ-9>TSSP5P,W1IMD,WIDE1,KQ1L-8,N3LLO-3,WIDE2*:`b6,l}#>/]"48}449.225MHz<0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff><0xff>=<0x0d>
+ *
+ * ` b6, l}# >/ ] "48} 449.225MHz ...... = <0x0d>
+ * mic-e long. cs sym prefix alt. freq comment suffix must-ignore
+ * Kenwood D710
+ *---------------
+ *
+ * N1JDU-9>ECCU8Y,W1MHL*,WIDE2-1:'cZ<0x7f>l#H>/]Go fly a kite!<0x0d>
*
- * TODO: Destination SSID can contain generic digipeater path.
+ * ' cZ<0x7f> l#H >/ ] ..... <0x0d>
+ * mic-e long. cs sym prefix comment no-suffix must-ignore
+ * Kenwood D700
+ *---------------
+ *
+ * KC1HHO-7>T2PX5R,WA1PLE-4,WIDE1*,WIDE2-1:`c_snp(k/`"4B}official relay station NTS_(<0x0d>
+ *
+ * ` c_s np( k/ ` "4B} ....... _( <0x0d>
+ * mic-e long. cs sym prefix alt comment suffix must-ignore
+ * FT2D
+ *---------------
+ *
+ * N1CMD-12>T3PQ1Y,KA1GJU-3,WIDE1,WA1PLE-4*:`cP#l!Fk/'"7H}|!%&-']|!w`&!|3
+ *
+ * ` cP# l!F k/ ' "7H} |!%&-']| !w`&! |3
+ * mic-e long. cs sym prefix alt base91telemetry DAO suffix
+ * TinyTrack3
+ *---------------
+ *
+ * W1STJ-3>T2UR4X,WA1PLE-4,WIDE1*,WIDE2-1:`c@&l#.-/`"5,}146.685MHz T100 -060 146.520 Simplex or Voice Alert_%<0x0d>
+ *
+ * ` c@& l#. -/ ` "5,} 146.685MHz T100 -060 .............. _% <0x0d>
+ * mic-e long. cs sym prefix alt frequency-specification comment suffix must-ignore
+ * FTM-400DR
+ *---------------
+ *
+ *
+ *
+ *
+ * TODO: Destination SSID can contain generic digipeater path. (?)
*
* Bugs: Doesn't handle ambiguous position. "space" treated as zero.
* Invalid data results in a message but latitude is not set to unknown.
@@ -1251,7 +1391,6 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
int cust_msg = 0;
const char *std_text[8] = {"Emergency", "Priority", "Special", "Committed", "Returning", "In Service", "En Route", "Off Duty" };
const char *cust_text[8] = {"Emergency", "Custom-6", "Custom-5", "Custom-4", "Custom-3", "Custom-2", "Custom-1", "Custom-0" };
- unsigned char *pfirst, *plast;
strlcpy (A->g_data_type_desc, "MIC-E", sizeof(A->g_data_type_desc));
@@ -1481,121 +1620,43 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
A->g_course = n;
-/* Now try to pick out manufacturer and other optional items. */
-/* The telemetry field, in the original spec, is no longer used. */
-
- strlcpy (A->g_mfr, "Unknown manufacturer", sizeof(A->g_mfr));
-
- pfirst = info + sizeof(struct aprs_mic_e_s);
- plast = info + ilen - 1;
-
-/* Carriage return character at the end is not mentioned in spec. */
-/* Remove if found because it messes up extraction of manufacturer. */
-/* Don't drop trailing space because that is used for Yaesu VX-8. */
-/* As I recall, the IGate function trims trailing spaces. */
-/* That would be bad for this particular model. Maybe I'm mistaken? */
-
-
- if (*plast == '\r') plast--;
-
-#define isT(c) ((c) == ' ' || (c) == '>' || (c) == ']' || (c) == '`' || (c) == '\'')
-
-// Last Updated Dec. 2021
-
-// This does not change very often but I'm wondering if we could parse
-// http://www.aprs.org/aprs12/mic-e-types.txt similar to how we use tocalls.txt.
-
- if (isT(*pfirst)) {
+// The rest is a comment which can have other information cryptically embedded.
+// Remove any trailing CR, which I would argue, violates the protocol spec.
+// It is essential to keep trailing spaces. e.g. VX-8 suffix is "_ "
-// "legacy" formats.
-
- if (*pfirst == ' ' ) { strlcpy (A->g_mfr, "Original MIC-E", sizeof(A->g_mfr)); pfirst++; }
-
- else if (*pfirst == '>' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TH-D72", sizeof(A->g_mfr)); pfirst++; plast--; }
- else if (*pfirst == '>' && *plast == '^') { strlcpy (A->g_mfr, "Kenwood TH-D74", sizeof(A->g_mfr)); pfirst++; plast--; }
- else if (*pfirst == '>' ) { strlcpy (A->g_mfr, "Kenwood TH-D7A", sizeof(A->g_mfr)); pfirst++; }
-
- else if (*pfirst == ']' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TM-D710", sizeof(A->g_mfr)); pfirst++; plast--; }
- else if (*pfirst == ']' ) { strlcpy (A->g_mfr, "Kenwood TM-D700", sizeof(A->g_mfr)); pfirst++; }
-
-// ` should be used for message capable devices.
-
- else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ' ') { strlcpy (A->g_mfr, "Yaesu VX-8", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '"') { strlcpy (A->g_mfr, "Yaesu FTM-350", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '#') { strlcpy (A->g_mfr, "Yaesu VX-8G", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '$') { strlcpy (A->g_mfr, "Yaesu FT1D", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '%') { strlcpy (A->g_mfr, "Yaesu FTM-400DR", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ')') { strlcpy (A->g_mfr, "Yaesu FTM-100D", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '(') { strlcpy (A->g_mfr, "Yaesu FT2D", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '0') { strlcpy (A->g_mfr, "Yaesu FT3D", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '3') { strlcpy (A->g_mfr, "Yaesu FT5D", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '1') { strlcpy (A->g_mfr, "Yaesu FTM-300D", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+ char mcomment[256];
+ strlcpy (mcomment, info + sizeof(struct aprs_mic_e_s), sizeof(mcomment));
+ if (mcomment[strlen(mcomment)-1] == '\r') {
+ mcomment[strlen(mcomment)-1] = '\0';
+ }
- else if (*pfirst == '`' && *(plast-1) == ' ' && *plast == 'X') { strlcpy (A->g_mfr, "AP510", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+/* Now try to pick out manufacturer and other optional items. */
+/* The telemetry field, in the original spec, is no longer used. */
- else if (*pfirst == '`' && *(plast-1) == '(' && *plast == '5') { strlcpy (A->g_mfr, "Anytone D578UV", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if (*pfirst == '`' ) { strlcpy (A->g_mfr, "Generic Mic-Emsg", sizeof(A->g_mfr)); pfirst++; }
+ char trimmed[256]; // Comment with vendor/model removed.
+ deviceid_decode_mice (mcomment, trimmed, sizeof(trimmed), A->g_mfr, sizeof(A->g_mfr));
-// ' should be used for trackers (not message capable).
- else if (*pfirst == '\'' && *(plast-1) == '(' && *plast == '8') { strlcpy (A->g_mfr, "Anytone D878UV", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '3') { strlcpy (A->g_mfr, "Byonics TinyTrack3", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '4') { strlcpy (A->g_mfr, "Byonics TinyTrack4", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+// Possible altitude at beginning of remaining comment.
+// Three base 91 characters followed by }
- else if (*pfirst == '\'' && *(plast-1) == ':' && *plast == '4') { strlcpy (A->g_mfr, "SCS GmbH & Co. P4dragon DR-7400 modems", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if (*pfirst == '\'' && *(plast-1) == ':' && *plast == '8') { strlcpy (A->g_mfr, "SCS GmbH & Co. P4dragon DR-7800 modems", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if (*pfirst == '\'' ) { strlcpy (A->g_mfr, "Generic McTrackr", sizeof(A->g_mfr)); pfirst++; }
+ if (strlen(trimmed) >=4 &&
+ isdigit91(trimmed[0]) &&
+ isdigit91(trimmed[1]) &&
+ isdigit91(trimmed[2]) &&
+ trimmed[3] == '}') {
- else if ( *(plast-1) == '\\' ) { strlcpy (A->g_mfr, "Hamhud ?", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if ( *(plast-1) == '/' ) { strlcpy (A->g_mfr, "Argent ?", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if ( *(plast-1) == '^' ) { strlcpy (A->g_mfr, "HinzTec anyfrog", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if ( *(plast-1) == '*' ) { strlcpy (A->g_mfr, "APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if ( *(plast-1) == '~' ) { strlcpy (A->g_mfr, "Unknown OTHER", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- }
+ A->g_altitude_ft = DW_METERS_TO_FEET((trimmed[0]-33)*91*91 + (trimmed[1]-33)*91 + (trimmed[2]-33) - 10000);
-/*
- * An optional altitude is next.
- * It is three base-91 digits followed by "}".
- * The TM-D710A might have encoding bug. This was observed:
- *
- * KJ4ETP-9>SUUP9Q,KE4OTZ-3,WIDE1*,WIDE2-1,qAR,KI4HDU-2:`oV$n6:>/]"7&}162.475MHz clintserman@gmail=
- * N 35 50.9100, W 083 58.0800, 25 MPH, course 230, alt 945 ft, 162.475MHz
- *
- * KJ4ETP-9>SUUP6Y,GRNTOP-3*,WIDE2-1,qAR,KI4HDU-2:`oU~nT >/]<0x9a>xt}162.475MHz clintserman@gmail=
- * Invalid character in MIC-E altitude. Must be in range of '!' to '{'.
- * N 35 50.6900, W 083 57.9800, 29 MPH, course 204, alt 3280843 ft, 162.475MHz
- *
- * KJ4ETP-9>SUUP6Y,N4NEQ-3,K4EGA-1,WIDE2*,qAS,N5CWH-1:`oU~nT >/]?xt}162.475MHz clintserman@gmail=
- * N 35 50.6900, W 083 57.9800, 29 MPH, course 204, alt 808497 ft, 162.475MHz
- *
- * KJ4ETP-9>SUUP2W,KE4OTZ-3,WIDE1*,WIDE2-1,qAR,KI4HDU-2:`oV2o"J>/]"7)}162.475MHz clintserman@gmail=
- * N 35 50.2700, W 083 58.2200, 35 MPH, course 246, alt 955 ft, 162.475MHz
- *
- * Note the <0x9a> which is outside of the 7-bit ASCII range. Clearly very wrong.
- */
-
- if (plast > pfirst && pfirst[3] == '}') {
-
- A->g_altitude_ft = DW_METERS_TO_FEET((pfirst[0]-33)*91*91 + (pfirst[1]-33)*91 + (pfirst[2]-33) - 10000);
-
- if ( ! isdigit91(pfirst[0]) || ! isdigit91(pfirst[1]) || ! isdigit91(pfirst[2]))
- {
- if ( ! A->g_quiet) {
- text_color_set(DW_COLOR_ERROR);
- dw_printf("Invalid character in MIC-E altitude. Must be in range of '!' to '{'.\n");
- dw_printf("Bogus altitude of %.0f changed to unknown.\n", A->g_altitude_ft);
- }
- A->g_altitude_ft = G_UNKNOWN;
- }
-
- pfirst += 4;
+ process_comment (A, trimmed+4, strlen(trimmed) - 4);
+ return;
}
- process_comment (A, (char*)pfirst, (int)(plast - pfirst) + 1);
+ process_comment (A, trimmed, strlen(trimmed));
-}
+} // end aprs_mic_e
/*------------------------------------------------------------------
@@ -1626,10 +1687,19 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
* It's a lot more complicated with different types of addressees
* and replies with acknowledgement or rejection.
*
- * There is even a special case for telemetry metadata.
+ * Is it an elegant generalization to lump all of these special cases
+ * together or was it a big mistake that will cause confusion and incorrect
+ * implementations? The decision to put telemetry metadata here is baffling.
*
*
- * Cases: :xxxxxxxxx:PARM. Telemetry metadata, parameter name
+ * Cases: :BLNxxxxxx: ... Bulletin.
+ * :NWSxxxxxx: ... National Weather Service Bulletin.
+ * http://www.aprs.org/APRS-docs/WX.TXT
+ * :SKYxxxxxx: ... Need reference.
+ * :CWAxxxxxx: ... Need reference.
+ * :BOMxxxxxx: ... Australian version.
+ *
+ * :xxxxxxxxx:PARM. Telemetry metadata, parameter name
* :xxxxxxxxx:UNIT. Telemetry metadata, unit/label
* :xxxxxxxxx:EQNS. Telemetry metadata, Equation Coefficients
* :xxxxxxxxx:BITS. Telemetry metadata, Bit Sense/Project Name
@@ -1645,7 +1715,8 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
* :xxxxxxxxx: ... {mm}aa Message with new style message number and ack.
*
*
- * Reference: See new message id style: http://www.aprs.org/aprs11/replyacks.txt
+ * Reference: http://www.aprs.org/txt/messages101.txt
+ * http://www.aprs.org/aprs11/replyacks.txt <-- New (1999) adding ack to outgoing message.
*
*------------------------------------------------------------------*/
@@ -1734,6 +1805,62 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
strlcpy (A->g_addressee, addressee, sizeof(A->g_addressee));
+/*
+ * Addressee starting with BLN or NWS is a bulletin.
+ */
+ if (strlen(addressee) >= 3 && strncmp(addressee,"BLN",3) == 0) {
+
+ // Interpret 3 cases of identifiers.
+ // BLN9 "general bulletin" has a single digit.
+ // BLNX "announcement" has a single uppercase letter.
+ // BLN9xxxxx "group bulletin" has single digit group id and group name up to 5 characters.
+
+ if (strlen(addressee) == 4 && isdigit(addressee[3])) {
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "General Bulletin with identifier \"%s\"", addressee+3);
+ }
+ else if (strlen(addressee) == 4 && isupper(addressee[3])) {
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Announcement with identifier \"%s\"", addressee+3);
+ }
+ if (strlen(addressee) >=5 && isdigit(addressee[3])) {
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Group Bulletin with identifier \"%c\", group name \"%s\"", addressee[3], addressee+4);
+ }
+ else {
+ // Not one of the official formats.
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Bulletin with identifier \"%s\"", addressee+3);
+ }
+ A->g_message_subtype = message_subtype_bulletin;
+ strlcpy (A->g_comment, p->message, sizeof(A->g_comment));
+ }
+
+
+ // Weather bulletins have addressee starting with NWS, SKY, CWA, or BOM.
+ // The protocol spec and http://www.aprs.org/APRS-docs/WX.TXT state that
+ // the 3 letter prefix must be followed by a dash.
+ // However, https://www.aprs-is.net/WX/ also lists the underscore
+ // alternative for the compressed format. Xastir implements this.
+
+ else if (strlen(addressee) >= 3 && strncmp(addressee,"NWS",3) == 0) {
+
+ if (strlen(addressee) >=4 && addressee[3] == '-') {
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Weather bulletin with identifier \"%s\"", addressee+4);
+ }
+ else if (strlen(addressee) >=4 && addressee[3] == '_') {
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Compressed Weather bulletin with identifier \"%s\"", addressee+4);
+ }
+ else {
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Weather bulletin is missing - or _ after %.3s", addressee);
+ }
+ A->g_message_subtype = message_subtype_nws;
+ strlcpy (A->g_comment, p->message, sizeof(A->g_comment));
+ }
+
+ else if (strlen(addressee) >= 3 && (strncmp(addressee,"SKY",3) == 0 || strncmp(addressee,"CWA",3) == 0 || strncmp(addressee,"BOM",3) == 0)) {
+ // SKY... or CWA... https://www.aprs-is.net/WX/
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Weather bulletin with identifier \"%s\"", addressee+4);
+ A->g_message_subtype = message_subtype_nws;
+ strlcpy (A->g_comment, p->message, sizeof(A->g_comment));
+ }
+
/*
* Special message formats contain telemetry metadata.
@@ -1746,23 +1873,23 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
* Why not use other characters after the "T" for metadata?
*/
- if (strncmp(p->message,"PARM.",5) == 0) {
- snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Telemetry Parameter Name Message for \"%s\"", addressee);
+ else if (strncmp(p->message,"PARM.",5) == 0) {
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Telemetry Parameter Name for \"%s\"", addressee);
A->g_message_subtype = message_subtype_telem_parm;
telemetry_name_message (addressee, p->message+5);
}
else if (strncmp(p->message,"UNIT.",5) == 0) {
- snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Telemetry Unit/Label Message for \"%s\"", addressee);
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Telemetry Unit/Label for \"%s\"", addressee);
A->g_message_subtype = message_subtype_telem_unit;
telemetry_unit_label_message (addressee, p->message+5);
}
else if (strncmp(p->message,"EQNS.",5) == 0) {
- snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Telemetry Equation Coefficients Message for \"%s\"", addressee);
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Telemetry Equation Coefficients for \"%s\"", addressee);
A->g_message_subtype = message_subtype_telem_eqns;
telemetry_coefficents_message (addressee, p->message+5, quiet);
}
else if (strncmp(p->message,"BITS.",5) == 0) {
- snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Telemetry Bit Sense/Project Name Message for \"%s\"", addressee);
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Telemetry Bit Sense/Project Name for \"%s\"", addressee);
A->g_message_subtype = message_subtype_telem_bits;
telemetry_bit_sense_message (addressee, p->message+5, quiet);
}
@@ -1786,10 +1913,12 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
text_color_set(DW_COLOR_ERROR);
dw_printf("ERROR: \"%s\" must be lower case \"ack\"\n", p->message);
}
- strlcpy (A->g_message_number, p->message + 3, sizeof(A->g_message_number));
- if (strlen(A->g_message_number) == 0) {
+ else {
+ strlcpy (A->g_message_number, p->message + 3, sizeof(A->g_message_number));
+ if (strlen(A->g_message_number) == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf("ERROR: Message number is missing after \"ack\".\n");
+ }
}
// Xastir puts a carriage return on the end.
@@ -1810,10 +1939,12 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
text_color_set(DW_COLOR_ERROR);
dw_printf("ERROR: \"%s\" must be lower case \"rej\"\n", p->message);
}
- strlcpy (A->g_message_number, p->message + 3, sizeof(A->g_message_number));
- if (strlen(A->g_message_number) == 0) {
+ else {
+ strlcpy (A->g_message_number, p->message + 3, sizeof(A->g_message_number));
+ if (strlen(A->g_message_number) == 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf("ERROR: Message number is missing after \"rej\".\n");
+ }
}
// Xastir puts a carriage return on the end.
@@ -1845,7 +1976,7 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
// X>Y:}A>B::WA1XYX-15:Howdy y'all{toolong
else {
- // Look for message number.
+ // Normal messaage case. Look for message number.
char *pno = strchr(p->message, '{');
if (pno != NULL) {
strlcpy (A->g_message_number, pno+1, sizeof(A->g_message_number));
@@ -4035,221 +4166,6 @@ static int data_extension_comment (decode_aprs_t *A, char *pdext)
}
-/*------------------------------------------------------------------
- *
- * Function: decode_tocall
- *
- * Purpose: Extract application from the destination.
- *
- * Inputs: dest - Destination address.
- * Don't care if SSID is present or not.
- *
- * Outputs: A->g_mfr
- *
- * Description: For maximum flexibility, we will read the
- * data file at run time rather than compiling it in.
- *
- * For the most recent version, download from:
- *
- * http://www.aprs.org/aprs11/tocalls.txt
- *
- * Windows version: File must be in current working directory.
- *
- * Linux version: Search order is current working directory then
- * /usr/local/share/direwolf
- * /usr/share/direwolf/tocalls.txt
- *
- * Mac: Like Linux and then
- * /opt/local/share/direwolf
- *
- *------------------------------------------------------------------*/
-
-// If I was more ambitious, this would dynamically allocate enough
-// storage based on the file contents. Just stick in a constant for
-// now. This takes an insignificant amount of space and
-// I don't anticipate tocalls.txt growing that quickly.
-// Version 1.4 - add message if too small instead of silently ignoring the rest.
-
-// Dec. 2016 tocalls.txt has 153 destination addresses.
-
-#define MAX_TOCALLS 250
-
-static struct tocalls_s {
- unsigned char len;
- char prefix[7];
- char *description;
-} tocalls[MAX_TOCALLS];
-
-static int num_tocalls = 0;
-
-// Make sure the array is null terminated.
-// If search order is changed, do the same in symbols.c for consistency.
-
-static const char *search_locations[] = {
- (const char *) "tocalls.txt", // CWD
- (const char *) "data/tocalls.txt", // Windows with CMake
- (const char *) "../data/tocalls.txt", // ?
-#ifndef __WIN32__
- (const char *) "/usr/local/share/direwolf/tocalls.txt",
- (const char *) "/usr/share/direwolf/tocalls.txt",
-#endif
-#if __APPLE__
- // https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/2458
- // Adding the /opt/local tree since macports typically installs there. Users might want their
- // INSTALLDIR (see Makefile.macosx) to mirror that. If so, then we need to search the /opt/local
- // path as well.
- (const char *) "/opt/local/share/direwolf/tocalls.txt",
-#endif
- (const char *) NULL // Important - Indicates end of list.
-};
-
-static int tocall_cmp (const void *px, const void *py)
-{
- const struct tocalls_s *x = (struct tocalls_s *)px;
- const struct tocalls_s *y = (struct tocalls_s *)py;
-
- if (x->len != y->len) return (y->len - x->len);
- return (strcmp(x->prefix, y->prefix));
-}
-
-static void decode_tocall (decode_aprs_t *A, char *dest)
-{
- FILE *fp = 0;
- int n = 0;
- static int first_time = 1;
- char stuff[100];
- char *p = NULL;
- char *r = NULL;
-
- //dw_printf("debug: decode_tocall(\"%s\")\n", dest);
-
-/*
- * Extract the calls and descriptions from the file.
- *
- * Use only lines with exactly these formats:
- *
- * APN Network nodes, digis, etc
- * APWWxx APRSISCE win32 version
- * | | |
- * 00000000001111111111
- * 01234567890123456789...
- *
- * Matching will be with only leading upper case and digits.
- */
-
-// TODO: Look for this in multiple locations.
-// For example, if application was installed in /usr/local/bin,
-// we might want to put this in /usr/local/share/aprs
-
-// If search strategy changes, be sure to keep symbols_init in sync.
-
- if (first_time) {
-
- n = 0;
- fp = NULL;
- do {
- if(search_locations[n] == NULL) break;
- fp = fopen(search_locations[n++], "r");
- } while (fp == NULL);
-
- if (fp != NULL) {
-
- while (fgets(stuff, sizeof(stuff), fp) != NULL && num_tocalls < MAX_TOCALLS) {
-
- p = stuff + strlen(stuff) - 1;
- while (p >= stuff && (*p == '\r' || *p == '\n')) {
- *p-- = '\0';
- }
-
- // dw_printf("debug: %s\n", stuff);
-
- if (stuff[0] == ' ' &&
- stuff[4] == ' ' &&
- stuff[5] == ' ' &&
- stuff[6] == 'A' &&
- stuff[7] == 'P' &&
- stuff[12] == ' ' &&
- stuff[13] == ' ' ) {
-
- p = stuff + 6;
- r = tocalls[num_tocalls].prefix;
- while (isupper((int)(*p)) || isdigit((int)(*p))) {
- *r++ = *p++;
- }
- *r = '\0';
- if (strlen(tocalls[num_tocalls].prefix) > 2) {
- tocalls[num_tocalls].description = strdup(stuff+14);
- tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix);
- // dw_printf("debug %d: %d '%s' -> '%s'\n", num_tocalls, tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description);
-
- num_tocalls++;
- }
- }
- else if (stuff[0] == ' ' &&
- stuff[1] == 'A' &&
- stuff[2] == 'P' &&
- isupper((int)(stuff[3])) &&
- stuff[4] == ' ' &&
- stuff[5] == ' ' &&
- stuff[6] == ' ' &&
- stuff[12] == ' ' &&
- stuff[13] == ' ' ) {
-
- p = stuff + 1;
- r = tocalls[num_tocalls].prefix;
- while (isupper((int)(*p)) || isdigit((int)(*p))) {
- *r++ = *p++;
- }
- *r = '\0';
- if (strlen(tocalls[num_tocalls].prefix) > 2) {
- tocalls[num_tocalls].description = strdup(stuff+14);
- tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix);
- // dw_printf("debug %d: %d '%s' -> '%s'\n", num_tocalls, tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description);
-
- num_tocalls++;
- }
- }
- if (num_tocalls == MAX_TOCALLS) { // oops. might have discarded some.
- text_color_set(DW_COLOR_ERROR);
- dw_printf("MAX_TOCALLS needs to be larger than %d to handle contents of 'tocalls.txt'.\n", MAX_TOCALLS);
- }
- }
- fclose(fp);
-
-/*
- * Sort by decreasing length so the search will go
- * from most specific to least specific.
- * Example: APY350 or APY008 would match those specific
- * models before getting to the more generic APY.
- */
-
- qsort (tocalls, num_tocalls, sizeof(struct tocalls_s), tocall_cmp);
-
- }
- else {
- if ( ! A->g_quiet) {
- text_color_set(DW_COLOR_ERROR);
- dw_printf("Warning: Could not open 'tocalls.txt'.\n");
- dw_printf("System types in the destination field will not be decoded.\n");
- }
- }
-
- first_time = 0;
-
- //for (n=0; n '%s'\n", n, tocalls[n].len, tocalls[n].prefix, tocalls[n].description);
- //}
- }
-
-
- for (n=0; ng_mfr, tocalls[n].description, sizeof(A->g_mfr));
- return;
- }
- }
-
-} /* end decode_tocall */
@@ -4297,7 +4213,7 @@ static void substr_se (char *dest, const char *src, int start, int endp1)
* clen - Length of comment or -1 to take it all.
*
* Outputs: A->g_telemetry - Base 91 telemetry |ss1122|
- * A->g_altitude_ft - from /A=123456
+ * A->g_altitude_ft - from /A=123456 or /A=-12345
* A->g_lat - Might be adjusted from !DAO!
* A->g_lon - Might be adjusted from !DAO!
* A->g_aprstt_loc - Private extension to !DAO!
@@ -4327,6 +4243,10 @@ static void substr_se (char *dest, const char *src, int start, int endp1)
* Protocol reference, end of chapter 6.
*
* /A=123456 Altitude
+ * /A=-12345 Enhancement - There are many places on the earth's
+ * surface but the APRS spec has no provision for negative
+ * numbers. I propose having 5 digits for a consistent
+ * field width. 6 would be excessive.
*
* What can appear in a comment?
*
@@ -4492,7 +4412,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen)
dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
}
- e = regcomp (&alt_re, "/A=[0-9][0-9][0-9][0-9][0-9][0-9]", REG_EXTENDED);
+ e = regcomp (&alt_re, "/A=[0-9-][0-9][0-9][0-9][0-9][0-9]", REG_EXTENDED);
if (e) {
regerror (e, &alt_re, emsg, sizeof(emsg));
dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
@@ -4852,7 +4772,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen)
}
/*
- * Altitude in feet. /A=123456
+ * Altitude in feet. /A=123456 or /A=-12345
*/
if (regexec (&alt_re, A->g_comment, MAXMATCH, match, 0) == 0)
@@ -4970,7 +4890,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen)
*
* Function: main
*
- * Purpose: Main program for standalone test program.
+ * Purpose: Main program for standalone application to parse and explain APRS packets.
*
* Inputs: stdin for raw data to decode.
* This is in the usual display format either from
@@ -5116,6 +5036,7 @@ int main (int argc, char *argv[])
// If you don't like the text colors, use 0 instead of 1 here.
text_color_init(1);
text_color_set(DW_COLOR_INFO);
+ deviceid_init();
while (fgets(stuff, sizeof(stuff), stdin) != NULL)
{
diff --git a/src/decode_aprs.h b/src/decode_aprs.h
index f25d1e91..94a9fd6b 100644
--- a/src/decode_aprs.h
+++ b/src/decode_aprs.h
@@ -24,7 +24,8 @@ typedef struct decode_aprs_s {
int g_quiet; /* Suppress error messages when decoding. */
- char g_src[AX25_MAX_ADDR_LEN];
+ char g_src[AX25_MAX_ADDR_LEN]; // In the case of a packet encapsulated by a 3rd party
+ // header, this is the encapsulated source.
char g_dest[AX25_MAX_ADDR_LEN];
@@ -66,10 +67,30 @@ typedef struct decode_aprs_s {
/* Also for Directed Station Query which is a */
/* special case of message. */
+ // This is so pfilter.c:filt_t does not need to duplicate the same work.
+
+ int g_has_thirdparty_header;
+ enum packet_type_e {
+ packet_type_none=0,
+ packet_type_position,
+ packet_type_weather,
+ packet_type_object,
+ packet_type_item,
+ packet_type_message,
+ packet_type_query,
+ packet_type_capabilities,
+ packet_type_status,
+ packet_type_telemetry,
+ packet_type_userdefined,
+ packet_type_nws
+ } g_packet_type;
+
enum message_subtype_e { message_subtype_invalid = 0,
message_subtype_message,
message_subtype_ack,
message_subtype_rej,
+ message_subtype_bulletin,
+ message_subtype_nws,
message_subtype_telem_parm,
message_subtype_telem_unit,
message_subtype_telem_eqns,
@@ -90,8 +111,9 @@ typedef struct decode_aprs_s {
int g_power; /* Transmitter power in watts. */
int g_height; /* Antenna height above average terrain, feet. */
+ // TODO: rename to g_height_ft
- int g_gain; /* Antenna gain in dB. */
+ int g_gain; /* Antenna gain in dBi. */
char g_directivity[12]; /* Direction of max signal strength */
@@ -99,7 +121,7 @@ typedef struct decode_aprs_s {
float g_altitude_ft; /* Feet above median sea level. */
/* I used feet here because the APRS specification */
- /* has units of feet for alititude. Meters would be */
+ /* has units of feet for altitude. Meters would be */
/* more natural to the other 96% of the world. */
char g_mfr[80]; /* Manufacturer or application. */
diff --git a/src/demod.c b/src/demod.c
index 9f94dd8d..cc522271 100644
--- a/src/demod.c
+++ b/src/demod.c
@@ -852,7 +852,6 @@ int demod_init (struct audio_s *pa)
#define FSK_READ_ERR (256*256)
-
__attribute__((hot))
int demod_get_sample (int a)
{
@@ -862,7 +861,6 @@ int demod_get_sample (int a)
assert (save_audio_config_p->adev[a].bits_per_sample == 8 || save_audio_config_p->adev[a].bits_per_sample == 16);
-
if (save_audio_config_p->adev[a].bits_per_sample == 8) {
x1 = audio_get(a);
@@ -929,6 +927,21 @@ int demod_get_sample (int a)
*
*--------------------------------------------------------------------*/
+static volatile int mute_input[MAX_CHANS];
+
+// New in 1.7.
+// A few people have a really bad audio cross talk situation where they receive their own transmissions.
+// It usually doesn't cause a problem but it is confusing to look at.
+// "half duplex" setting applied only to the transmit logic. i.e. wait for clear channel before sending.
+// Receiving was still active.
+// I think the simplest solution is to mute/unmute the audio input at this point if not full duplex.
+// This is called from ptt_set for half duplex.
+
+void demod_mute_input (int chan, int mute_during_xmit)
+{
+ assert (chan >= 0 && chan < MAX_CHANS);
+ mute_input[chan] = mute_during_xmit;
+}
__attribute__((hot))
void demod_process_sample (int chan, int subchan, int sam)
@@ -942,6 +955,10 @@ void demod_process_sample (int chan, int subchan, int sam)
assert (chan >= 0 && chan < MAX_CHANS);
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
+ if (mute_input[chan]) {
+ sam = 0;
+ };
+
D = &demodulator_state[chan][subchan];
diff --git a/src/demod.h b/src/demod.h
index 3233b9ba..f1120cd0 100644
--- a/src/demod.h
+++ b/src/demod.h
@@ -8,10 +8,13 @@
int demod_init (struct audio_s *pa);
+void demod_mute_input (int chan, int mute);
+
int demod_get_sample (int a);
void demod_process_sample (int chan, int subchan, int sam);
void demod_print_agc (int chan, int subchan);
-alevel_t demod_get_audio_level (int chan, int subchan);
\ No newline at end of file
+alevel_t demod_get_audio_level (int chan, int subchan);
+
diff --git a/src/deviceid.c b/src/deviceid.c
new file mode 100644
index 00000000..de910e50
--- /dev/null
+++ b/src/deviceid.c
@@ -0,0 +1,668 @@
+//
+// This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+// Copyright (C) 2023 John Langner, WB2OSZ
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * File: deviceid.c
+ *
+ * Purpose: Determine the device identifier from the destination field,
+ * or from prefix/suffix for MIC-E format.
+ *
+ * Description: Orginally this used the tocalls.txt file and was part of decode_aprs.c.
+ * For release 1.8, we use tocalls.yaml and this is split into a separate file.
+ *
+ *------------------------------------------------------------------*/
+
+//#define TEST 1 // Standalone test. $ gcc -DTEST deviceid.c && ./a.out
+
+
+#if TEST
+#define HAVE_STRLCPY 1 // prevent defining in direwolf.h
+#define HAVE_STRLCAT 1
+#endif
+
+#include "direwolf.h"
+
+#include
+#include
+#include
+#include
+
+#include "deviceid.h"
+#include "textcolor.h"
+
+
+static void unquote (int line, char *pin, char *pout);
+static int tocall_cmp (const void *px, const void *py);
+static int mice_cmp (const void *px, const void *py);
+
+/*------------------------------------------------------------------
+ *
+ * Function: main
+ *
+ * Purpose: A little self-test used during development.
+ *
+ * Description: Read the yaml file. Decipher a few typical values.
+ *
+ *------------------------------------------------------------------*/
+
+#if TEST
+// So we don't need to link with any other files.
+#define dw_printf printf
+void text_color_set(dw_color_t) { return; }
+void strlcpy(char *dst, char *src, size_t dlen) {
+ strcpy (dst, src);
+}
+void strlcat(char *dst, char *src, size_t dlen) {
+ strcat (dst, src);
+}
+
+
+int main (int argc, char *argv[])
+{
+ char device[80];
+ char comment_out[80];
+
+ deviceid_init ();
+
+ dw_printf ("\n");
+ dw_printf ("Testing ...\n");
+
+// MIC-E Legacy (really Kenwood).
+
+ deviceid_decode_mice (">Comment", comment_out, sizeof(comment_out), device, sizeof(device));
+ dw_printf ("%s %s\n", comment_out, device);
+ assert (strcmp(comment_out, "Comment") == 0);
+ assert (strcmp(device, "Kenwood TH-D7A") == 0);
+
+ deviceid_decode_mice (">Comment^", comment_out, sizeof(comment_out), device, sizeof(device));
+ dw_printf ("%s %s\n", comment_out, device);
+ assert (strcmp(comment_out, "Comment") == 0);
+ assert (strcmp(device, "Kenwood TH-D74") == 0);
+
+ deviceid_decode_mice ("]Comment", comment_out, sizeof(comment_out), device, sizeof(device));
+ dw_printf ("%s %s\n", comment_out, device);
+ assert (strcmp(comment_out, "Comment") == 0);
+ assert (strcmp(device, "Kenwood TM-D700") == 0);
+
+ deviceid_decode_mice ("]Comment=", comment_out, sizeof(comment_out), device, sizeof(device));
+ dw_printf ("%s %s\n", comment_out, device);
+ assert (strcmp(comment_out, "Comment") == 0);
+ assert (strcmp(device, "Kenwood TM-D710") == 0);
+
+ deviceid_decode_mice ("]\"4V}=", comment_out, sizeof(comment_out), device, sizeof(device));
+ dw_printf ("%s %s\n", comment_out, device);
+ assert (strcmp(comment_out, "\"4V}") == 0);
+ assert (strcmp(device, "Kenwood TM-D710") == 0);
+
+
+// Modern MIC-E.
+
+ deviceid_decode_mice ("`Comment_\"", comment_out, sizeof(comment_out), device, sizeof(device));
+ dw_printf ("%s %s\n", comment_out, device);
+ assert (strcmp(comment_out, "Comment") == 0);
+ assert (strcmp(device, "Yaesu FTM-350") == 0);
+
+ deviceid_decode_mice ("`Comment_ ", comment_out, sizeof(comment_out), device, sizeof(device));
+ dw_printf ("%s %s\n", comment_out, device);
+ assert (strcmp(comment_out, "Comment") == 0);
+ assert (strcmp(device, "Yaesu VX-8") == 0);
+
+ deviceid_decode_mice ("'Comment|3", comment_out, sizeof(comment_out), device, sizeof(device));
+ dw_printf ("%s %s\n", comment_out, device);
+ assert (strcmp(comment_out, "Comment") == 0);
+ assert (strcmp(device, "Byonics TinyTrak3") == 0);
+
+ deviceid_decode_mice ("Comment", comment_out, sizeof(comment_out), device, sizeof(device));
+ dw_printf ("%s %s\n", comment_out, device);
+ assert (strcmp(comment_out, "Comment") == 0);
+ assert (strcmp(device, "UNKNOWN vendor/model") == 0);
+
+
+// Tocall
+
+ deviceid_decode_dest ("APDW18", device, sizeof(device));
+ dw_printf ("%s\n", device);
+ assert (strcmp(device, "WB2OSZ DireWolf") == 0);
+
+ deviceid_decode_dest ("APD123", device, sizeof(device));
+ dw_printf ("%s\n", device);
+ assert (strcmp(device, "Open Source aprsd") == 0);
+
+ // null for Vendor.
+ deviceid_decode_dest ("APAX", device, sizeof(device));
+ dw_printf ("%s\n", device);
+ assert (strcmp(device, "AFilterX") == 0);
+
+ deviceid_decode_dest ("APA123", device, sizeof(device));
+ dw_printf ("%s\n", device);
+ assert (strcmp(device, "UNKNOWN vendor/model") == 0);
+
+ dw_printf ("\n");
+ dw_printf ("Success!\n");
+
+ exit (EXIT_SUCCESS);
+}
+
+#endif // TEST
+
+
+
+// Structures to hold mapping from encoded form to vendor and model.
+// The .yaml file has two separate sections for MIC-E but they can
+// both be handled as a single more general case.
+
+struct mice {
+ char prefix[4]; // The legacy form has 1 prefix character.
+ // The newer form has none. (more accurately ` or ')
+ char suffix[4]; // The legacy form has 0 or 1.
+ // The newer form has 2.
+ char *vendor;
+ char *model;
+};
+
+struct tocalls {
+ char tocall[8]; // Up to 6 characters. Some may have wildcards at the end.
+ // Most often they are trailing "??" or "?" or "???" in one case.
+ // Sometimes there is trailing "nnn". Does that imply digits only?
+ // Sometimes we see a trailing "*". Is "*" different than "?"?
+ // There are a couple bizzare cases like APnnnD which can
+ // create an ambigious situation. APMPAD, APRFGD, APY0[125]D.
+ // Screw them if they can't follow the rules. I'm not putting in a special case.
+ char *vendor;
+ char *model;
+};
+
+
+static struct mice *pmice = NULL; // Pointer to array.
+static int mice_count = 0; // Number of allocated elements.
+static int mice_index = -1; // Current index for filling in.
+
+static struct tocalls *ptocalls = NULL; // Pointer to array.
+static int tocalls_count = 0; // Number of allocated elements.
+static int tocalls_index = -1; // Current index for filling in.
+
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function: deviceid_init
+ *
+ * Purpose: Called once at startup to read the tocalls.yaml file which was obtained from
+ * https://github.com/aprsorg/aprs-deviceid .
+ *
+ * Inputs: tocalls.yaml with OS specific directory search list.
+ *
+ * Outputs: static variables listed above.
+ *
+ * Description: For maximum flexibility, we will read the
+ * data file at run time rather than compiling it in.
+ *
+ *------------------------------------------------------------------*/
+
+// Make sure the array is null terminated.
+// If search order is changed, do the same in symbols.c for consistency.
+// fopen is perfectly happy with / in file path when running on Windows.
+
+static const char *search_locations[] = {
+ (const char *) "tocalls.yaml", // Current working directory
+ (const char *) "data/tocalls.yaml", // Windows with CMake
+ (const char *) "../data/tocalls.yaml", // Source tree
+#ifndef __WIN32__
+ (const char *) "/usr/local/share/direwolf/tocalls.yaml",
+ (const char *) "/usr/share/direwolf/tocalls.yaml",
+#endif
+#if __APPLE__
+ // https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/2458
+ // Adding the /opt/local tree since macports typically installs there. Users might want their
+ // INSTALLDIR (see Makefile.macosx) to mirror that. If so, then we need to search the /opt/local
+ // path as well.
+ (const char *) "/opt/local/share/direwolf/tocalls.yaml",
+#endif
+ (const char *) NULL // Important - Indicates end of list.
+};
+
+
+void deviceid_init(void)
+{
+ FILE *fp = NULL;
+ for (int n = 0; search_locations[n] != NULL && fp == NULL; n++) {
+#if TEST
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("Trying %s\n", search_locations[n]);
+#endif
+ fp = fopen(search_locations[n], "r");
+#if TEST
+ if (fp != NULL) {
+ dw_printf ("Opened %s\n", search_locations[n]);
+ }
+#endif
+ };
+
+ if (fp == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("Could not open any of these file locations:\n");
+ for (int n = 0; search_locations[n] != NULL; n++) {
+ dw_printf (" %s\n", search_locations[n]);
+ }
+ dw_printf("It won't be possible to extract device identifiers from packets.\n");
+ return;
+ };
+
+// Read file first time to get number of items.
+// Allocate required space.
+// Rewind.
+// Read file second time to gather data.
+
+ enum { no_section, mice_section, tocalls_section} section = no_section;
+ char stuff[80];
+
+ for (int pass = 1; pass <=2; pass++) {
+ int line = 0; // Line number within file.
+
+ while (fgets(stuff, sizeof(stuff), fp)) {
+ line++;
+
+ // Remove trailing CR/LF or spaces.
+ char *p = stuff + strlen(stuff) - 1;
+ while (p >= (char*)stuff && (*p == '\r' || *p == '\n' || *p == ' ')) {
+ *p-- = '\0';
+ }
+
+ // Ignore comment lines.
+ if (stuff[0] == '#') {
+ continue;
+ }
+
+#if TEST
+ //dw_printf ("%d: %s\n", line, stuff);
+#endif
+ // This is not very robust; everything better be in exactly the right format.
+
+ if (strncmp(stuff, "mice:", strlen("mice:")) == 0) {
+ section = mice_section;
+#if TEST
+ dw_printf ("Pass %d, line %d, MIC-E section\n", pass, line);
+#endif
+ }
+ else if (strncmp(stuff, "micelegacy:", strlen("micelegacy:")) == 0) {
+ section = mice_section; // treat both same.
+#if TEST
+ dw_printf ("Pass %d, line %d, Legacy MIC-E section\n", pass, line);
+#endif
+ }
+ else if (strncmp(stuff, "tocalls:", strlen("tocalls:")) == 0) {
+ section = tocalls_section;
+#if TEST
+ dw_printf ("Pass %d, line %d, TOCALLS section\n", pass, line);
+#endif
+ }
+
+ // The first property of an item is preceded by " - ".
+
+ if (pass == 1 && strncmp(stuff, " - ", 3) == 0) {
+ switch (section) {
+ case no_section: break;
+ case mice_section: mice_count++; break;
+ case tocalls_section: tocalls_count++; break;
+ }
+ }
+
+ if (pass == 2) {
+ switch (section) {
+ case no_section:
+ break;
+
+ case mice_section:
+ if (strncmp(stuff, " - ", 3) == 0) {
+ mice_index++;
+ assert (mice_index >= 0 && mice_index < mice_count);
+ }
+ if (strncmp(stuff+3, "prefix: ", strlen("prefix: ")) == 0) {
+ unquote (line, stuff+3+8, pmice[mice_index].prefix);
+ }
+ else if (strncmp(stuff+3, "suffix: ", strlen("suffix: ")) == 0) {
+ unquote (line, stuff+3+8, pmice[mice_index].suffix);
+ }
+ else if (strncmp(stuff+3, "vendor: ", strlen("vendor: ")) == 0) {
+ pmice[mice_index].vendor = strdup(stuff+3+8);
+ }
+ else if (strncmp(stuff+3, "model: ", strlen("model: ")) == 0) {
+ pmice[mice_index].model = strdup(stuff+3+7);
+ }
+ break;
+
+ case tocalls_section:
+ if (strncmp(stuff, " - ", 3) == 0) {
+ tocalls_index++;
+ assert (tocalls_index >= 0 && tocalls_index < tocalls_count);
+ }
+ if (strncmp(stuff+3, "tocall: ", strlen("tocall: ")) == 0) {
+ // Remove trailing wildcard characters ? * n
+ char *r = stuff + strlen(stuff) - 1;
+ while (r >= (char*)stuff && (*r == '?' || *r == '*' || *r == 'n')) {
+ *r-- = '\0';
+ }
+
+ strlcpy (ptocalls[tocalls_index].tocall, stuff+3+8, sizeof(ptocalls[tocalls_index].tocall));
+
+ // Remove trailing CR/LF or spaces.
+ char *p = stuff + strlen(stuff) - 1;
+ while (p >= (char*)stuff && (*p == '\r' || *p == '\n' || *p == ' ')) {
+ *p-- = '\0';
+ }
+ }
+ else if (strncmp(stuff+3, "vendor: ", strlen("vendor: ")) == 0) {
+ ptocalls[tocalls_index].vendor = strdup(stuff+3+8);
+ }
+ else if (strncmp(stuff+3, "model: ", strlen("model: ")) == 0) {
+ ptocalls[tocalls_index].model = strdup(stuff+3+7);
+ }
+ break;
+ }
+ }
+ } // while(fgets
+
+ if (pass == 1) {
+#if TEST
+ dw_printf ("deviceid sizes %d %d\n", mice_count, tocalls_count);
+#endif
+ pmice = calloc(sizeof(struct mice), mice_count);
+ ptocalls = calloc(sizeof(struct tocalls), tocalls_count);
+
+ rewind (fp);
+ section = no_section;
+ }
+ } // for pass = 1 or 2
+
+ fclose (fp);
+
+ assert (mice_index == mice_count - 1);
+ assert (tocalls_index == tocalls_count - 1);
+
+
+// MIC-E Legacy needs to be sorted so those with suffix come first.
+
+ qsort (pmice, mice_count, sizeof(struct mice), mice_cmp);
+
+
+// Sort tocalls by decreasing length so the search will go from most specific to least specific.
+// Example: APY350 or APY008 would match those specific models before getting to the more generic APY.
+
+ qsort (ptocalls, tocalls_count, sizeof(struct tocalls), tocall_cmp);
+
+
+#if TEST
+ dw_printf ("MIC-E:\n");
+ for (int i = 0; i < mice_count; i++) {
+ dw_printf ("%s %s %s\n", pmice[i].suffix, pmice[i].vendor, pmice[i].model);
+ }
+ dw_printf ("TOCALLS:\n");
+ for (int i = 0; i < tocalls_count; i++) {
+ dw_printf ("%s %s %s\n", ptocalls[i].tocall, ptocalls[i].vendor, ptocalls[i].model);
+ }
+#endif
+
+ return;
+
+} // end deviceid_init
+
+
+/*------------------------------------------------------------------
+ *
+ * Function: unquote
+ *
+ * Purpose: Remove surrounding quotes and undo any escapes.
+ *
+ * Inputs: line - File line number for error message.
+ *
+ * in - String with quotes. Might contain \ escapes.
+ *
+ * Outputs: out - Quotes and escapes removed.
+ * Limited to 2 characters to avoid buffer overflow.
+ *
+ * Examples: in out
+ * "_#" _#
+ * "_\"" _"
+ * "=" =
+ *
+ *------------------------------------------------------------------*/
+
+static void unquote (int line, char *pin, char *pout)
+{
+ int count = 0;
+
+ *pout = '\0';
+ if (*pin != '"') {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("Missing leading \" for %s on line %d.\n", pin, line);
+ return;
+ }
+
+ pin++;
+ while (*pin != '\0' && *pin != '\"' && count < 2) {
+ if (*pin == '\\') {
+ pin++;
+ }
+ *pout++ = *pin++;
+ count++;
+ }
+ *pout = '\0';
+
+ if (*pin != '"') {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("Missing trailing \" or string too long on line %d.\n", line);
+ return;
+ }
+}
+
+// Used to sort the tocalls by length.
+// When length is equal, alphabetically.
+
+static int tocall_cmp (const void *px, const void *py)
+{
+ const struct tocalls *x = (struct tocalls *)px;
+ const struct tocalls *y = (struct tocalls *)py;
+
+ if (strlen(x->tocall) != strlen(y->tocall)) {
+ return (strlen(y->tocall) - strlen(x->tocall));
+ }
+ return (strcmp(x->tocall, y->tocall));
+}
+
+// Used to sort the suffixes by length.
+// Longer at the top.
+// Example check for >xxx^ before >xxx .
+
+static int mice_cmp (const void *px, const void *py)
+{
+ const struct mice *x = (struct mice *)px;
+ const struct mice *y = (struct mice *)py;
+
+ return (strlen(y->suffix) - strlen(x->suffix));
+}
+
+
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Function: deviceid_decode_dest
+ *
+ * Purpose: Find vendor/model for destination address of form APxxxx.
+ *
+ * Inputs: dest - Destination address. No SSID.
+ *
+ * device_size - Amount of space available for result to avoid buffer overflow.
+ *
+ * Outputs: device - Vendor and model.
+ *
+ * Description: With the exception of MIC-E format, we expect to find the vendor/model in the
+ * AX.25 destination field. The form should be APxxxx.
+ *
+ * Search the list looking for the maximum length match.
+ * For example,
+ * APXR = Xrouter
+ * APX = Xastir
+ *
+ *------------------------------------------------------------------*/
+
+void deviceid_decode_dest (char *dest, char *device, size_t device_size)
+{
+ *device = '\0';
+
+ if (ptocalls == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("deviceid_decode_dest called without any deviceid data.\n");
+ return;
+ }
+
+ for (int n = 0; n < tocalls_count; n++) {
+ if (strncmp(dest, ptocalls[n].tocall, strlen(ptocalls[n].tocall)) == 0) {
+
+ if (ptocalls[n].vendor != NULL) {
+ strlcpy (device, ptocalls[n].vendor, device_size);
+ }
+
+ if (ptocalls[n].vendor != NULL && ptocalls[n].model != NULL) {
+ strlcat (device, " ", device_size);
+ }
+
+ if (ptocalls[n].model != NULL) {
+ strlcat (device, ptocalls[n].model, device_size);
+ }
+ return;
+ }
+ }
+
+ strlcpy (device, "UNKNOWN vendor/model", device_size);
+
+} // end deviceid_decode_dest
+
+
+/*------------------------------------------------------------------
+ *
+ * Function: deviceid_decode_mice
+ *
+ * Purpose: Find vendor/model for MIC-E comment.
+ *
+ * Inputs: comment - MIC-E comment that might have vendor/model encoded as
+ * a prefix and/or suffix.
+ *
+ * trimmed_size - Amount of space available for result to avoid buffer overflow.
+ *
+ * device_size - Amount of space available for result to avoid buffer overflow.
+ *
+ * Outputs: trimmed - Final comment with device vendor/model removed.
+ *
+ * device - Vendor and model.
+ *
+ * Description: This has a tortured history.
+ *
+ * The Kenwood TH-D7A put ">" at the beginning of the comment.
+ * The Kenwood TM-D700 put "]" at the beginning of the comment.
+ * Later Kenwood models also added a single suffix character
+ * using a character very unlikely to appear at the end of a comment.
+ *
+ * The later convention, used by everyone else, is to have a prefix of ` or '
+ * and a suffix of two characters. The suffix characters need to be
+ * something very unlikely to be found at the end of a comment.
+ *
+ * A receiving device is expected to remove those extra characters
+ * before displaying the comment.
+ *
+ * References: http://www.aprs.org/aprs12/mic-e-types.txt
+ * http://www.aprs.org/aprs12/mic-e-examples.txt
+ *
+ *------------------------------------------------------------------*/
+
+// The strncmp documentation doesn't mention behavior if length is zero.
+// Do our own just to be safe.
+
+static inline int strncmp_z (char *a, char *b, size_t len)
+{
+ int result = 0;
+ if (len > 0) {
+ result = strncmp(a, b, len);
+ }
+ //dw_printf ("Comparing '%s' and '%s' len %d result %d\n", a, b, len, result);
+ return result;
+}
+
+void deviceid_decode_mice (char *comment, char *trimmed, size_t trimmed_size, char *device, size_t device_size)
+{
+ *device = '\0';
+
+ if (ptocalls == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("deviceid_decode_mice called without any deviceid data.\n");
+ return;
+ }
+
+
+// The Legacy format has an explicit prefix in the table.
+// For others, it must be ` or ' to indicate whether messaging capable.
+
+ for (int n = 0; n < mice_count; n++) {
+ if ((strlen(pmice[n].prefix) != 0 && // Legacy
+ strncmp_z(comment, // prefix from table
+ pmice[n].prefix,
+ strlen(pmice[n].prefix)) == 0 &&
+ strncmp_z(comment + strlen(comment) - strlen(pmice[n].suffix), // possible suffix
+ pmice[n].suffix,
+ strlen(pmice[n].suffix)) == 0) ||
+
+ (strlen(pmice[n].prefix) == 0 && // Later
+ (comment[0] == '`' || comment[0] == '\'') && // prefix ` or '
+ strncmp_z(comment + strlen(comment) - strlen(pmice[n].suffix), // suffix
+ pmice[n].suffix,
+ strlen(pmice[n].suffix)) == 0) ) {
+
+ if (pmice[n].vendor != NULL) {
+ strlcpy (device, pmice[n].vendor, device_size);
+ }
+
+ if (pmice[n].vendor != NULL && pmice[n].model != NULL) {
+ strlcat (device, " ", device_size);
+ }
+
+ if (pmice[n].model != NULL) {
+ strlcat (device, pmice[n].model, device_size);
+ }
+
+ // Remove any prefix/suffix and return what remains.
+
+ strlcpy (trimmed, comment + 1, trimmed_size);
+ trimmed[strlen(comment) - 1 - strlen(pmice[n].suffix)] = '\0';
+
+ return;
+ }
+ }
+
+
+// Not found.
+
+ strlcpy (device, "UNKNOWN vendor/model", device_size);
+
+} // end deviceid_decode_mice
+
+// end deviceid.c
diff --git a/src/deviceid.h b/src/deviceid.h
new file mode 100644
index 00000000..d7a1b30e
--- /dev/null
+++ b/src/deviceid.h
@@ -0,0 +1,6 @@
+
+// deviceid.h
+
+void deviceid_init(void);
+void deviceid_decode_dest (char *dest, char *device, size_t device_size);
+void deviceid_decode_mice (char *comment, char *trimmed, size_t trimmed_size, char *device, size_t device_size);
diff --git a/src/digipeater.c b/src/digipeater.c
index 006ee7b7..fbe89370 100644
--- a/src/digipeater.c
+++ b/src/digipeater.c
@@ -49,7 +49,8 @@
* Preemptive Digipeating (new in version 0.8)
*
* http://www.aprs.org/aprs12/preemptive-digipeating.txt
- *
+ * I ignored the part about the RR bits.
+ *
*------------------------------------------------------------------*/
#define DIGIPEATER_C
@@ -164,7 +165,34 @@ void digipeater (int from_chan, packet_t pp)
/*
* First pass: Look at packets being digipeated to same channel.
*
- * We want these to get out quickly.
+ * We want these to get out quickly, bypassing the usual random wait time.
+ *
+ * Some may disagree but I followed what WB4APR had to say about it.
+ *
+ * http://www.aprs.org/balloons.html
+ *
+ * APRS NETWORK FRATRICIDE: Generally, all APRS digipeaters are supposed to transmit
+ * immediately and all at the same time. They should NOT wait long enough for each
+ * one to QRM the channel with the same copy of each packet. NO, APRS digipeaters
+ * are all supposed to STEP ON EACH OTHER with every packet. This makes sure that
+ * everyone in range of a digi will hear one and only one copy of each packet.
+ * and that the packet will digipeat OUTWARD and not backward. The goal is that a
+ * digipeated packet is cleared out of the local area in ONE packet time and not
+ * N packet times for every N digipeaters that heard the packet. This means no
+ * PERSIST times, no DWAIT times and no UIDWAIT times. Notice, this is contrary
+ * to other packet systems that might want to guarantee delivery (but at the
+ * expense of throughput). APRS wants to clear the channel quickly to maximize throughput.
+ *
+ * http://www.aprs.org/kpc3/kpc3+WIDEn.txt
+ *
+ * THIRD: Eliminate the settings that are detrimental to the network.
+ *
+ * * UIDWAIT should be OFF. (the default). With it on, your digi is not doing the
+ * fundamental APRS fratricide that is the primary mechanism for minimizing channel
+ * loading. All digis that hear the same packet are supposed to DIGI it at the SAME
+ * time so that all those copies only take up one additional time slot. (but outward
+ * located digs will hear it without collision (and continue outward propagation)
+ *
*/
for (to_chan=0; to_chanfilter_str[from_chan][to_chan]);
if (result != NULL) {
dedupe_remember (pp, to_chan);
- tq_append (to_chan, TQ_PRIO_0_HI, result);
+ tq_append (to_chan, TQ_PRIO_0_HI, result); // High priority queue.
digi_count[from_chan][to_chan]++;
}
}
@@ -207,7 +235,7 @@ void digipeater (int from_chan, packet_t pp)
save_digi_config_p->filter_str[from_chan][to_chan]);
if (result != NULL) {
dedupe_remember (pp, to_chan);
- tq_append (to_chan, TQ_PRIO_1_LO, result);
+ tq_append (to_chan, TQ_PRIO_1_LO, result); // Low priority queue.
digi_count[from_chan][to_chan]++;
}
}
@@ -413,6 +441,10 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch
/*
* If preemptive digipeating is enabled, try matching my call
* and aliases against all remaining unused digipeaters.
+ *
+ * Bob says: "GENERIC XXXXn-N DIGIPEATING should not do preemptive digipeating."
+ *
+ * But consider this case: https://github.com/wb2osz/direwolf/issues/488
*/
if (preempt != PREEMPT_OFF) {
@@ -438,13 +470,22 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch
switch (preempt) {
case PREEMPT_DROP: /* remove all prior */
+ // TODO: deprecate this option. Result is misleading.
+
+ text_color_set (DW_COLOR_ERROR);
+ dw_printf ("The digipeat DROP option will be removed in a future release. Use PREEMPT for preemptive digipeating.\n");
+
while (r2 > AX25_REPEATER_1) {
ax25_remove_addr (result, r2-1);
r2--;
}
break;
- case PREEMPT_MARK:
+ case PREEMPT_MARK: // TODO: deprecate this option. Result is misleading.
+
+ text_color_set (DW_COLOR_ERROR);
+ dw_printf ("The digipeat MARK option will be removed in a future release. Use PREEMPT for preemptive digipeating.\n");
+
r2--;
while (r2 >= AX25_REPEATER_1 && ax25_get_h(result,r2) == 0) {
ax25_set_h (result, r2);
@@ -452,7 +493,12 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch
}
break;
- case PREEMPT_TRACE: /* remove prior unused */
+ case PREEMPT_TRACE: /* My enhancement - remove prior unused digis. */
+ /* this provides an accurate path of where packet traveled. */
+
+ // Uh oh. It looks like sample config files went out
+ // with this option. Should it be renamed as
+ // PREEMPT which is more descriptive?
default:
while (r2 > AX25_REPEATER_1 && ax25_get_h(result,r2-1) == 0) {
ax25_remove_addr (result, r2-1);
diff --git a/src/direwolf.c b/src/direwolf.c
index f2aeca14..a8cddd82 100644
--- a/src/direwolf.c
+++ b/src/direwolf.c
@@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
-// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019, 2020, 2021 John Langner, WB2OSZ
+// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019, 2020, 2021, 2023 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
@@ -128,6 +128,8 @@
#include "il2p.h"
#include "dwsock.h"
#include "dns_sd_dw.h"
+#include "dlq.h" // for fec_type_t definition.
+#include "deviceid.h"
//static int idx_decoded = 0;
@@ -138,7 +140,7 @@ static BOOL cleanup_win (int);
static void cleanup_linux (int);
#endif
-static void usage (char **argv);
+static void usage ();
#if defined(__SSE__) && !defined(__APPLE__)
@@ -185,7 +187,7 @@ static int d_u_opt = 0; /* "-d u" command line option to print UTF-8 also in h
static int d_p_opt = 0; /* "-d p" option for dumping packets over radio. */
static int q_h_opt = 0; /* "-q h" Quiet, suppress the "heard" line with audio level. */
-static int q_d_opt = 0; /* "-q d" Quiet, suppress the printing of decoded of APRS packets. */
+static int q_d_opt = 0; /* "-q d" Quiet, suppress the printing of description of APRS packets. */
static int A_opt_ais_to_obj = 0; /* "-A" Convert received AIS to APRS "Object Report." */
@@ -283,6 +285,8 @@ int main (int argc, char *argv[])
/* 1 = normal, 0 = no text colors. */
/* 2, 3, ... alternate escape sequences for different terminals. */
+// FIXME: consider case of no space between t and number.
+
for (j=1; j= 0 && chan < MAX_TOTAL_CHANS); // TOTAL for virtual channels
assert (subchan >= -2 && subchan < MAX_SUBCHANS);
@@ -1191,12 +1204,21 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
strlcpy (display_retries, "", sizeof(display_retries));
- if (is_fx25) {
- ;
- }
- else if (audio_config.achan[chan].fix_bits != RETRY_NONE || audio_config.achan[chan].passall) {
- assert (retries >= RETRY_NONE && retries <= RETRY_MAX);
- snprintf (display_retries, sizeof(display_retries), " [%s] ", retry_text[(int)retries]);
+ switch (fec_type) {
+ case fec_type_fx25:
+ strlcpy (display_retries, " FX.25 ", sizeof(display_retries));
+ break;
+ case fec_type_il2p:
+ strlcpy (display_retries, " IL2P ", sizeof(display_retries));
+ break;
+ case fec_type_none:
+ default:
+ // Possible fix_bits indication.
+ if (audio_config.achan[chan].fix_bits != RETRY_NONE || audio_config.achan[chan].passall) {
+ assert (retries >= RETRY_NONE && retries <= RETRY_MAX);
+ snprintf (display_retries, sizeof(display_retries), " [%s] ", retry_text[(int)retries]);
+ }
+ break;
}
ax25_format_addrs (pp, stemp);
@@ -1473,7 +1495,7 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
// Unknown not handled properly.
// Should encode_object take floating point here?
(int)(A.g_course+0.5), (int)(DW_MPH_TO_KNOTS(A.g_speed_mph)+0.5),
- 0, 0, 0, A.g_comment, // freq, tone, offset
+ 0, 'T', 0, 0, A.g_comment, // freq, tone_type, tone, offset
ais_obj_info, sizeof(ais_obj_info));
snprintf (ais_obj_packet, sizeof(ais_obj_packet), "%s>%s%1d%1d:%s", A.g_src, APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, ais_obj_info);
@@ -1561,7 +1583,7 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
* However, if it used FEC mode (FX.25. IL2P), we have much higher level of
* confidence that it is correct.
*/
- if (ax25_is_aprs(pp) && ( retries == RETRY_NONE || is_fx25) ) {
+ if (ax25_is_aprs(pp) && ( retries == RETRY_NONE || fec_type == fec_type_fx25 || fec_type == fec_type_il2p) ) {
igate_send_rec_packet (chan, pp);
}
@@ -1582,7 +1604,7 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
* However, if it used FEC mode (FX.25. IL2P), we have much higher level of
* confidence that it is correct.
*/
- if (ax25_is_aprs(pp) && ( retries == RETRY_NONE || is_fx25) ) {
+ if (ax25_is_aprs(pp) && ( retries == RETRY_NONE || fec_type == fec_type_fx25 || fec_type == fec_type_il2p) ) {
digipeater (chan, pp);
}
@@ -1592,7 +1614,7 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
* Use only those with correct CRC (or using FEC.)
*/
- if (retries == RETRY_NONE || is_fx25) {
+ if (retries == RETRY_NONE || fec_type == fec_type_fx25 || fec_type == fec_type_il2p) {
cdigipeater (chan, pp);
}
@@ -1691,7 +1713,7 @@ static void usage (char **argv)
dw_printf (" d d = APRStt (DTMF to APRS object translation).\n");
dw_printf (" -q Quiet (suppress output) options:\n");
dw_printf (" h h = Heard line with the audio level.\n");
- dw_printf (" d d = Decoding of APRS packets.\n");
+ dw_printf (" d d = Description of APRS packets.\n");
dw_printf (" x x = Silence FX.25 information.\n");
dw_printf (" -t n Text colors. 0=disabled. 1=default. 2,3,4,... alternatives.\n");
dw_printf (" Use 9 to test compatibility with your terminal.\n");
@@ -1717,16 +1739,16 @@ static void usage (char **argv)
dw_printf ("\n");
#if __WIN32__
- dw_printf ("Complete documentation can be found in the 'doc' folder\n");
+ dw_printf ("Documentation can be found in the 'doc' folder\n");
#else
// TODO: Could vary by platform and build options.
- dw_printf ("Complete documentation can be found in /usr/local/share/doc/direwolf\n");
+ dw_printf ("Documentation can be found in /usr/local/share/doc/direwolf\n");
#endif
dw_printf ("or online at https://github.com/wb2osz/direwolf/tree/master/doc\n");
+ dw_printf ("additional topics: https://github.com/wb2osz/direwolf-doc\n");
text_color_set(DW_COLOR_INFO);
exit (EXIT_FAILURE);
}
-
/* end direwolf.c */
diff --git a/src/direwolf.h b/src/direwolf.h
index 3a47398c..69b09529 100644
--- a/src/direwolf.h
+++ b/src/direwolf.h
@@ -278,6 +278,8 @@ typedef pthread_mutex_t dw_mutex_t;
/* Platform differences for string functions. */
+// Windows is missing a few which are available on Unix/Linux platforms.
+// We provide our own copies when building on Windows.
#if __WIN32__
char *strsep(char **stringp, const char *delim);
@@ -285,13 +287,49 @@ char *strtok_r(char *str, const char *delim, char **saveptr);
#endif
// Don't recall why I added this for everyone rather than only for Windows.
+// Potential problem if some C library declares it a little differently.
char *strcasestr(const char *S, const char *FIND);
-// cmake determines whether strlcpy and strlcat are available
-// or if we need to supply our own.
+// cmake tries to determine whether strlcpy and strlcat are provided by the C runtime library.
+//
+// ../CMakeLists.txt:check_symbol_exists(strlcpy string.h HAVE_STRLCPY)
+//
+// It sets HAVE_STRLCPY and HAVE_STRLCAT if the corresponding functions are declared.
+// Unfortunately this does not work right for glibc 2.38 which declares the functions
+// like this:
+//
+// extern __typeof (strlcpy) __strlcpy;
+// libc_hidden_proto (__strlcpy)
+// extern __typeof (strlcat) __strlcat;
+// libc_hidden_proto (__strlcat)
+//
+// Rather than the normal way found in earlier versions:
+//
+// extern char *strcpy (char *__restrict __dest, const char *__restrict __src)
+//
+// Perhaps a later version of cmake will recognize this form but the version I'm
+// using does not.
+// So, our work around is to assume these functions are available for glibc >= 2.38.
+//
+// In theory, cmake should be able to find the version of the C runtime library,
+// but I could not get it to work. So we have the test here. We will still build
+// own library with the strl... functions but this does not cause a problem
+// because they have special debug names which will not cause a conflict.
+
+#ifdef __GLIBC__
+#if (__GLIBC__ > 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 38))
+// These functions first added in 2.38.
+//#warning "DEBUG - glibc >= 2.38"
+#define HAVE_STRLCPY 1
+#define HAVE_STRLCAT 1
+#else
+//#warning "DEBUG - glibc < 2.38"
+#endif
+#endif
#define DEBUG_STRL 1 // Extra Debug version when using our own strlcpy, strlcat.
+ // Should be ignored if not supplying our own.
#ifndef HAVE_STRLCPY // Need to supply our own.
#if DEBUG_STRL
diff --git a/src/dlq.c b/src/dlq.c
index 0d4b4a41..f56b8649 100644
--- a/src/dlq.c
+++ b/src/dlq.c
@@ -215,10 +215,10 @@ void dlq_init (void)
* display of audio level line.
* Use -2 to indicate DTMF message.)
*
- * is_fx25 - Was it from FX.25? Need to know because
+ * fec_type - Was it from FX.25 or IL2P? Need to know because
* meaning of retries is different.
*
- * retries - Level of bit correction used.
+ * retries - Level of correction used.
*
* spectrum - Display of how well multiple decoders did.
*
@@ -228,7 +228,7 @@ void dlq_init (void)
*
*--------------------------------------------------------------------*/
-void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, int is_fx25, retry_t retries, char *spectrum)
+void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, fec_type_t fec_type, retry_t retries, char *spectrum)
{
struct dlq_item_s *pnew;
@@ -278,7 +278,7 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev
pnew->subchan = subchan;
pnew->pp = pp;
pnew->alevel = alevel;
- pnew->is_fx25 = is_fx25;
+ pnew->fec_type = fec_type;
pnew->retries = retries;
if (spectrum == NULL)
strlcpy(pnew->spectrum, "", sizeof(pnew->spectrum));
@@ -728,8 +728,7 @@ void dlq_xmit_data_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int n
*
* Purpose: Register callsigns that we will recognize for incoming connection requests.
*
- * Inputs: addrs - Source (owncall), destination (peercall),
- * and possibly digipeaters.
+ * Inputs: addr - Callsign to [un]register.
*
* chan - Channel, 0 is first.
*
@@ -749,7 +748,7 @@ void dlq_xmit_data_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int n
*--------------------------------------------------------------------*/
-void dlq_register_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client)
+void dlq_register_callsign (char *addr, int chan, int client)
{
struct dlq_item_s *pnew;
@@ -773,7 +772,7 @@ void dlq_register_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client)
pnew->type = DLQ_REGISTER_CALLSIGN;
pnew->chan = chan;
- strlcpy (pnew->addrs[0], addr, AX25_MAX_ADDR_LEN);
+ strlcpy (pnew->addrs[0], addr, sizeof(pnew->addrs[0]));
pnew->num_addr = 1;
pnew->client = client;
@@ -784,7 +783,7 @@ void dlq_register_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client)
} /* end dlq_register_callsign */
-void dlq_unregister_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client)
+void dlq_unregister_callsign (char *addr, int chan, int client)
{
struct dlq_item_s *pnew;
@@ -808,7 +807,7 @@ void dlq_unregister_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client
pnew->type = DLQ_UNREGISTER_CALLSIGN;
pnew->chan = chan;
- strlcpy (pnew->addrs[0], addr, AX25_MAX_ADDR_LEN);
+ strlcpy (pnew->addrs[0], addr, sizeof(pnew->addrs[0]));
pnew->num_addr = 1;
pnew->client = client;
diff --git a/src/dlq.h b/src/dlq.h
index 87716362..fdac1c0c 100644
--- a/src/dlq.h
+++ b/src/dlq.h
@@ -33,10 +33,13 @@ typedef struct cdata_s {
+
/* Types of things that can be in queue. */
typedef enum dlq_type_e {DLQ_REC_FRAME, DLQ_CONNECT_REQUEST, DLQ_DISCONNECT_REQUEST, DLQ_XMIT_DATA_REQUEST, DLQ_REGISTER_CALLSIGN, DLQ_UNREGISTER_CALLSIGN, DLQ_OUTSTANDING_FRAMES_REQUEST, DLQ_CHANNEL_BUSY, DLQ_SEIZE_CONFIRM, DLQ_CLIENT_CLEANUP} dlq_type_t;
+typedef enum fec_type_e {fec_type_none=0, fec_type_fx25=1, fec_type_il2p=2} fec_type_t;
+
/* A queue item. */
@@ -68,7 +71,7 @@ typedef struct dlq_item_s {
alevel_t alevel; /* Audio level. */
- int is_fx25; /* Was it from FX.25? */
+ fec_type_t fec_type; // Type of FEC for received signal: none, FX.25, or IL2P.
retry_t retries; /* Effort expended to get a valid CRC. */
/* Bits changed for regular AX.25. */
@@ -106,7 +109,7 @@ void dlq_init (void);
-void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, int is_fx25, retry_t retries, char *spectrum);
+void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, fec_type_t fec_type, retry_t retries, char *spectrum);
void dlq_connect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int pid);
@@ -116,9 +119,9 @@ void dlq_outstanding_frames_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LE
void dlq_xmit_data_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int pid, char *xdata_ptr, int xdata_len);
-void dlq_register_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client);
+void dlq_register_callsign (char *addr, int chan, int client);
-void dlq_unregister_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client);
+void dlq_unregister_callsign (char *addr, int chan, int client);
void dlq_channel_busy (int chan, int activity, int status);
diff --git a/src/encode_aprs.c b/src/encode_aprs.c
index 20992bf7..e1eaa2b9 100644
--- a/src/encode_aprs.c
+++ b/src/encode_aprs.c
@@ -405,6 +405,7 @@ static int cse_spd_data_extension (int course, int speed, char *presult)
* Purpose: Put frequency specification in beginning of comment field.
*
* Inputs: freq - MHz.
+ * tone_type - T/Tone, C/Tone Squelch, D/DCS
* tone - Hz.
* offset - MHz.
*
@@ -430,7 +431,7 @@ static int cse_spd_data_extension (int course, int speed, char *presult)
*----------------------------------------------------------------*/
-static int frequency_spec (float freq, float tone, float offset, char *presult)
+static int frequency_spec (float freq, char tone_type, float tone, float offset, char *presult)
{
int result_size = 24; // TODO: add as parameter.
@@ -456,7 +457,15 @@ static int frequency_spec (float freq, float tone, float offset, char *presult)
strlcpy (stemp, "Toff ", sizeof (stemp));
}
else {
- snprintf (stemp, sizeof(stemp), "T%03d ", (int)tone);
+ /* Tone enabled */
+ if (tone_type == 'T' || tone_type == 'C' || tone_type == 'D') {
+ /* CTCSS Tone or DCS Code*/
+ snprintf(stemp, sizeof(stemp), "%c%03d ", tone_type, (int)tone);
+ } else {
+ /* Error */
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("Invalid tone type\n");
+ }
}
strlcat (presult, stemp, result_size);
@@ -499,6 +508,7 @@ static int frequency_spec (float freq, float tone, float offset, char *presult)
* speed - knots. // TODO: should distinguish unknown(not revevant) vs. known zero.
*
* freq - MHz.
+ * tone_type - T/C/D
* tone - Hz.
* offset - MHz.
*
@@ -544,7 +554,7 @@ int encode_position (int messaging, int compressed, double lat, double lon, int
char symtab, char symbol,
int power, int height, int gain, char *dir,
int course, int speed,
- float freq, float tone, float offset,
+ float freq, char tone_type, float tone, float offset,
char *comment,
char *presult, size_t result_size)
{
@@ -590,7 +600,7 @@ int encode_position (int messaging, int compressed, double lat, double lon, int
/* Optional frequency spec. */
if (freq != 0 || tone != 0 || offset != 0) {
- result_len += frequency_spec (freq, tone, offset, presult + result_len);
+ result_len += frequency_spec (freq, tone_type, tone, offset, presult + result_len);
}
presult[result_len] = '\0';
@@ -650,6 +660,7 @@ int encode_position (int messaging, int compressed, double lat, double lon, int
* speed - knots.
*
* freq - MHz.
+ * tone_type - T/C/D
* tone - Hz.
* offset - MHz.
*
@@ -687,7 +698,7 @@ int encode_object (char *name, int compressed, time_t thyme, double lat, double
char symtab, char symbol,
int power, int height, int gain, char *dir,
int course, int speed,
- float freq, float tone, float offset, char *comment,
+ float freq, char tone_type, float tone, float offset, char *comment,
char *presult, size_t result_size)
{
aprs_object_t *p = (aprs_object_t *) presult;
@@ -753,7 +764,7 @@ int encode_object (char *name, int compressed, time_t thyme, double lat, double
/* Optional frequency spec. */
if (freq != 0 || tone != 0 || offset != 0) {
- result_len += frequency_spec (freq, tone, offset, presult + result_len);
+ result_len += frequency_spec (freq, tone_type, tone, offset, presult + result_len);
}
presult[result_len] = '\0';
@@ -857,7 +868,7 @@ int main (int argc, char *argv[])
/*********** Position ***********/
encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&',
- 0, 0, 0, NULL, G_UNKNOWN, 0, 0, 0, 0, NULL, result, sizeof(result));
+ 0, 0, 0, NULL, G_UNKNOWN, 0, 0, 'T', 0, 0, NULL, result, sizeof(result));
dw_printf ("%s\n", result);
if (strcmp(result, "!4234.61ND07126.47W&") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; }
@@ -865,50 +876,50 @@ int main (int argc, char *argv[])
// TODO: Need to test specifying some but not all.
encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&',
- 50, 100, 6, "N", G_UNKNOWN, 0, 0, 0, 0, NULL, result, sizeof(result));
+ 50, 100, 6, "N", G_UNKNOWN, 0, 0, 'T', 0, 0, NULL, result, sizeof(result));
dw_printf ("%s\n", result);
if (strcmp(result, "!4234.61ND07126.47W&PHG7368") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; }
/* with freq & tone. minus offset, no offset, explicit simplex. */
encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&',
- 0, 0, 0, NULL, G_UNKNOWN, 0, 146.955, 74.4, -0.6, NULL, result, sizeof(result));
+ 0, 0, 0, NULL, G_UNKNOWN, 0, 146.955, 'T', 74.4, -0.6, NULL, result, sizeof(result));
dw_printf ("%s\n", result);
if (strcmp(result, "!4234.61ND07126.47W&146.955MHz T074 -060 ") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; }
encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&',
- 0, 0, 0, NULL, G_UNKNOWN, 0, 146.955, 74.4, G_UNKNOWN, NULL, result, sizeof(result));
+ 0, 0, 0, NULL, G_UNKNOWN, 0, 146.955, 'T', 74.4, G_UNKNOWN, NULL, result, sizeof(result));
dw_printf ("%s\n", result);
if (strcmp(result, "!4234.61ND07126.47W&146.955MHz T074 ") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; }
encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&',
- 0, 0, 0, NULL, G_UNKNOWN, 0, 146.955, 74.4, 0, NULL, result, sizeof(result));
+ 0, 0, 0, NULL, G_UNKNOWN, 0, 146.955, 'T', 74.4, 0, NULL, result, sizeof(result));
dw_printf ("%s\n", result);
if (strcmp(result, "!4234.61ND07126.47W&146.955MHz T074 +000 ") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; }
/* with course/speed, freq, and comment! */
encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&',
- 0, 0, 0, NULL, 180, 55, 146.955, 74.4, -0.6, "River flooding", result, sizeof(result));
+ 0, 0, 0, NULL, 180, 55, 146.955, 'T', 74.4, -0.6, "River flooding", result, sizeof(result));
dw_printf ("%s\n", result);
if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz T074 -060 River flooding") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; }
/* Course speed, no tone, + offset */
encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&',
- 0, 0, 0, NULL, 180, 55, 146.955, G_UNKNOWN, 0.6, "River flooding", result, sizeof(result));
+ 0, 0, 0, NULL, 180, 55, 146.955, 'T', G_UNKNOWN, 0.6, "River flooding", result, sizeof(result));
dw_printf ("%s\n", result);
if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz +060 River flooding") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; }
/* Course speed, no tone, + offset + altitude */
encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, 12345, 'D', '&',
- 0, 0, 0, NULL, 180, 55, 146.955, G_UNKNOWN, 0.6, "River flooding", result, sizeof(result));
+ 0, 0, 0, NULL, 180, 55, 146.955, 'T', G_UNKNOWN, 0.6, "River flooding", result, sizeof(result));
dw_printf ("%s\n", result);
if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz +060 /A=012345River flooding") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; }
encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, 12345, 'D', '&',
- 0, 0, 0, NULL, 180, 55, 146.955, 0, 0.6, "River flooding", result, sizeof(result));
+ 0, 0, 0, NULL, 180, 55, 146.955, 'T', 0, 0.6, "River flooding", result, sizeof(result));
dw_printf ("%s\n", result);
if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz Toff +060 /A=012345River flooding") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; }
@@ -917,7 +928,7 @@ int main (int argc, char *argv[])
/*********** Compressed position. ***********/
encode_position (0, 1, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&',
- 0, 0, 0, NULL, G_UNKNOWN, 0, 0, 0, 0, NULL, result, sizeof(result));
+ 0, 0, 0, NULL, G_UNKNOWN, 0, 0, 'T', 0, 0, NULL, result, sizeof(result));
dw_printf ("%s\n", result);
if (strcmp(result, "!D8yKC 0) {
- // TODO: Why not use the destination field instead of command line option?
+ // Why not use the destination field instead of command line option?
+ // For one thing, this is not in TNC-2 monitor format.
- morse_send (0, str, g_morse_wpm, 100, 100);
+ morse_send (c, str, g_morse_wpm, 100, 100);
+ }
+ else if (modem.achan[0].modem_type == MODEM_EAS) {
+
+// Generate EAS SAME signal FOR RESEARCH AND TESTING ONLY!!!
+// There could be legal consequences for sending unauhorized SAME
+// over the radio so don't do it!
+
+ // I'm expecting to see TNC 2 monitor format.
+ // The source and destination are ignored.
+ // The optional destination SSID is the number of times to repeat.
+ // The user defined data type indicator can optionally be used
+ // for compatibility with how it is received and presented to client apps.
+ // Examples:
+ // X>X-3:{DEZCZC-WXR-RWT-033019-033017-033015-033013-033011-025011-025017-033007-033005-033003-033001-025009-025027-033009+0015-1691525-KGYX/NWS-
+ // X>X:NNNN
+
+ pp = ax25_from_text (str, 1);
+ if (pp == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("\"%s\" is not valid TNC2 monitoring format.\n", str);
+ return;
+ }
+ unsigned char *pinfo;
+ int info_len = ax25_get_info (pp, &pinfo);
+ if (info_len >= 3 && strncmp((char*)pinfo, "{DE", 3) == 0) {
+ pinfo += 3;
+ info_len -= 3;
+ }
+
+ int repeat = ax25_get_ssid (pp, AX25_DESTINATION);
+ if (repeat == 0) repeat = 1;
+
+ eas_send (c, pinfo, repeat, 500, 500);
+ ax25_delete (pp);
}
else {
pp = ax25_from_text (str, 1);
@@ -135,6 +170,9 @@ static void send_packet (char *str)
}
flen = ax25_pack (pp, fbuf);
(void)flen;
+
+ // If stereo, put same thing in each channel.
+
for (c=0; c MAX_BAUD)) {
- text_color_set(DW_COLOR_ERROR);
- dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD);
- exit (EXIT_FAILURE);
- }
/* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */
/* that need to be kept in sync. Maybe it could be a common function someday. */
- if (modem.achan[0].baud == 100) {
+ if (modem.achan[0].baud == 100) { // What was this for?
modem.achan[0].modem_type = MODEM_AFSK;
modem.achan[0].mark_freq = 1615;
modem.achan[0].space_freq = 1785;
}
+ else if (modem.achan[0].baud == 0xEA5EA5) {
+ modem.achan[0].baud = 521; // Fine tuned later. 520.83333
+ // Proper fix is to make this float.
+ modem.achan[0].modem_type = MODEM_EAS;
+ modem.achan[0].mark_freq = 2083.3333; // Ideally these should be floating point.
+ modem.achan[0].space_freq = 1562.5000 ;
+ }
else if (modem.achan[0].baud < 600) {
modem.achan[0].modem_type = MODEM_AFSK;
modem.achan[0].mark_freq = 1600; // Typical for HF SSB
@@ -334,6 +380,11 @@ int main(int argc, char **argv)
text_color_set(DW_COLOR_INFO);
dw_printf ("Using scrambled baseband signal rather than AFSK.\n");
}
+ if (modem.achan[0].baud != 100 && (modem.achan[0].baud < MIN_BAUD || modem.achan[0].baud > MAX_BAUD)) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD);
+ exit (EXIT_FAILURE);
+ }
break;
case 'g': /* -g for g3ruh scrambling */
@@ -740,14 +791,23 @@ int main(int argc, char **argv)
}
else {
+ // This should send a total of 6.
+ // Note that sticking in the user defined type {DE is optional.
+
+ if (modem.achan[0].modem_type == MODEM_EAS) {
+ send_packet ("X>X-3:{DEZCZC-WXR-RWT-033019-033017-033015-033013-033011-025011-025017-033007-033005-033003-033001-025009-025027-033009+0015-1691525-KGYX/NWS-");
+ send_packet ("X>X-2:{DENNNN");
+ send_packet ("X>X:NNNN");
+ }
+ else {
/*
* Builtin default 4 packets.
*/
-
- send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! 1 of 4");
- send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! 2 of 4");
- send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! 3 of 4");
- send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! 4 of 4");
+ send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! 1 of 4");
+ send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! 2 of 4");
+ send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! 3 of 4");
+ send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! 4 of 4");
+ }
}
audio_file_close();
@@ -765,7 +825,7 @@ static void usage (char **argv)
dw_printf ("Options:\n");
dw_printf (" -a Signal amplitude in range of 0 - 200%%. Default 50.\n");
dw_printf (" -b Bits / second for data. Default is %d.\n", DEFAULT_BAUD);
- dw_printf (" -B Bits / second for data. Proper modem selected for 300, 1200, 2400, 4800, 9600.\n");
+ dw_printf (" -B Bits / second for data. Proper modem selected for 300, 1200, 2400, 4800, 9600, EAS.\n");
dw_printf (" -g Scrambled baseband rather than AFSK.\n");
dw_printf (" -j 2400 bps QPSK compatible with direwolf <= 1.5.\n");
dw_printf (" -J 2400 bps QPSK compatible with MFJ-2400.\n");
@@ -788,6 +848,7 @@ static void usage (char **argv)
dw_printf ("the default built-in message. The format should correspond to\n");
dw_printf ("the standard packet monitoring representation such as,\n\n");
dw_printf (" WB2OSZ-1>APDW12,WIDE2-2:!4237.14NS07120.83W#\n");
+ dw_printf ("User defined content can't be used with -n option.\n");
dw_printf ("\n");
dw_printf ("Example: gen_packets -o x.wav \n");
dw_printf ("\n");
diff --git a/src/gen_tone.c b/src/gen_tone.c
index 3317aa32..6a816556 100644
--- a/src/gen_tone.c
+++ b/src/gen_tone.c
@@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
-// Copyright (C) 2011, 2014, 2015, 2016, 2019 John Langner, WB2OSZ
+// Copyright (C) 2011, 2014, 2015, 2016, 2019, 2023 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -70,6 +70,7 @@ static int ticks_per_sample[MAX_CHANS]; /* Same for both channels of same soundc
static int ticks_per_bit[MAX_CHANS];
static int f1_change_per_sample[MAX_CHANS];
static int f2_change_per_sample[MAX_CHANS];
+static float samples_per_symbol[MAX_CHANS];
static short sine_table[256];
@@ -198,8 +199,11 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets)
ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / ((double)audio_config_p->achan[chan].baud * 0.5)) + 0.5);
f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5);
f2_change_per_sample[chan] = f1_change_per_sample[chan]; // Not used.
+ samples_per_symbol[chan] = 2. * (float)audio_config_p->adev[a].samples_per_sec / (float)audio_config_p->achan[chan].baud;
tone_phase[chan] = PHASE_SHIFT_45; // Just to mimic first attempt.
+ // ??? Why? We are only concerned with the difference
+ // from one symbol to the next.
break;
case MODEM_8PSK:
@@ -211,6 +215,7 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets)
ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / ((double)audio_config_p->achan[chan].baud / 3.)) + 0.5);
f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5);
f2_change_per_sample[chan] = f1_change_per_sample[chan]; // Not used.
+ samples_per_symbol[chan] = 3. * (float)audio_config_p->adev[a].samples_per_sec / (float)audio_config_p->achan[chan].baud;
break;
case MODEM_BASEBAND:
@@ -220,11 +225,23 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets)
// Tone is half baud.
ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->achan[chan].baud ) + 0.5);
f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].baud * 0.5 * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5);
+ samples_per_symbol[chan] = (float)audio_config_p->adev[a].samples_per_sec / (float)audio_config_p->achan[chan].baud;
+ break;
+
+ case MODEM_EAS: // EAS.
+
+ // TODO: Proper fix would be to use float for baud, mark, space.
+
+ ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / 520.833333333333 ) + 0.5);
+ samples_per_symbol[chan] = (int)((audio_config_p->adev[a].samples_per_sec / 520.83333) + 0.5);
+ f1_change_per_sample[chan] = (int) ((2083.33333333333 * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5);
+ f2_change_per_sample[chan] = (int) ((1562.5000000 * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5);
break;
default: // AFSK
ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->achan[chan].baud ) + 0.5);
+ samples_per_symbol[chan] = (float)audio_config_p->adev[a].samples_per_sec / (float)audio_config_p->achan[chan].baud;
f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5);
f2_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].space_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5);
break;
@@ -285,9 +302,64 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets)
*
*--------------------------------------------------------------------*/
+// Interpolate between two values.
+// My original approximation simply jumped between phases, producing a discontinuity,
+// and increasing bandwidth.
+// According to multiple sources, we should transition more gently.
+// Below see see a rough approximation of:
+// * A step function, immediately going to new value.
+// * Linear interpoation.
+// * Raised cosine. Square root of cosine is also mentioned.
+//
+// new - / --
+// | / /
+// | / |
+// | / /
+// old ------- / --
+// step linear raised cosine
+//
+// Inputs are the old (previous value), new value, and a blending control
+// 0 -> take old value
+// 1 -> take new value.
+// inbetween some sort of weighted average.
+
+static inline float interpol8 (float oldv, float newv, float bc)
+{
+ // Step function.
+ //return (newv); // 78 on 11/7
+
+ assert (bc >= 0);
+ assert (bc <= 1.1);
+
+ if (bc < 0) return (oldv);
+ if (bc > 1) return (newv);
+
+ // Linear interpolation, just for comparison.
+ //return (bc * newv + (1.0f - bc) * oldv); // 39 on 11/7
+
+ float rc = 0.5f * (cosf(bc * M_PI - M_PI) + 1.0f);
+ float rrc = bc >= 0.5f
+ ? 0.5f * (sqrtf(fabsf(cosf(bc * M_PI - M_PI))) + 1.0f)
+ : 0.5f * (-sqrtf(fabsf(cosf(bc * M_PI - M_PI))) + 1.0f);
+
+ (void)rrc;
+ return (rc * newv + (1.0f - bc) * oldv); // 49 on 11/7
+ //return (rrc * newv + (1.0f - bc) * oldv); // 55 on 11/7
+}
+
static const int gray2phase_v26[4] = {0, 1, 3, 2};
static const int gray2phase_v27[8] = {1, 0, 2, 3, 6, 7, 5, 4};
+// #define PSKIQ 1 // not ready for prime time yet.
+#if PSKIQ
+static int xmit_octant[MAX_CHANS]; // absolute phase in 45 degree units.
+static int xmit_prev_octant[MAX_CHANS]; // from previous symbol.
+
+// For PSK, we generate the final signal by combining fixed frequency cosine and
+// sine by the following weights.
+static const float ci[8] = { 1, .7071, 0, -.7071, -1, -.7071, 0, .7071 };
+static const float sq[8] = { 0, .7071, 1, .7071, 0, -.7071, -1, -.7071 };
+#endif
void tone_gen_put_bit (int chan, int dat)
{
@@ -324,14 +396,28 @@ void tone_gen_put_bit (int chan, int dat)
// All zero bits should give us steady 1800 Hz.
// All one bits should flip phase by 180 degrees each time.
+ // For V.26B, add another 45 degrees.
+ // This seems to work a little better.
dibit = (save_bit[chan] << 1) | dat;
- symbol = gray2phase_v26[dibit];
+ symbol = gray2phase_v26[dibit]; // 0 .. 3 for QPSK.
+#if PSKIQ
+ // One phase shift unit is 45 degrees.
+ // Remember what it was last time and calculate new.
+ // values 0 .. 7.
+ xmit_prev_octant[chan] = xmit_octant[chan];
+ xmit_octant[chan] += symbol * 2;
+ if (save_audio_config_p->achan[chan].v26_alternative == V26_B) {
+ xmit_octant[chan] += 1;
+ }
+ xmit_octant[chan] &= 0x7;
+#else
tone_phase[chan] += symbol * PHASE_SHIFT_90;
if (save_audio_config_p->achan[chan].v26_alternative == V26_B) {
tone_phase[chan] += PHASE_SHIFT_45;
}
+#endif
bit_count[chan]++;
}
@@ -370,7 +456,9 @@ void tone_gen_put_bit (int chan, int dat)
lfsr[chan] = (lfsr[chan] << 1) | (x & 1);
dat = x;
}
-
+#if PSKIQ
+ int blend = 1;
+#endif
do { /* until enough audio samples for this symbol. */
int sam;
@@ -395,9 +483,58 @@ void tone_gen_put_bit (int chan, int dat)
gen_tone_put_sample (chan, a, sam);
break;
+ case MODEM_EAS:
+
+ tone_phase[chan] += dat ? f1_change_per_sample[chan] : f2_change_per_sample[chan];
+ sam = sine_table[(tone_phase[chan] >> 24) & 0xff];
+ gen_tone_put_sample (chan, a, sam);
+ break;
+
case MODEM_QPSK:
- case MODEM_8PSK:
+#if DEBUG2
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("tone_gen_put_bit %d PSK\n", __LINE__);
+#endif
+ tone_phase[chan] += f1_change_per_sample[chan];
+#if PSKIQ
+#if 1 // blend JWL
+ // remove loop invariant
+ float old_i = ci[xmit_prev_octant[chan]];
+ float old_q = sq[xmit_prev_octant[chan]];
+
+ float new_i = ci[xmit_octant[chan]];
+ float new_q = sq[xmit_octant[chan]];
+
+ float b = blend / samples_per_symbol[chan]; // roughly 0 to 1
+ blend++;
+ // b = (b - 0.5) * 20 + 0.5;
+ // if (b < 0) b = 0;
+ // if (b > 1) b = 1;
+ // b = b > 0.5;
+ //b = 1; // 78 decoded with this.
+ // only 39 without.
+
+
+ //float blended_i = new_i * b + old_i * (1.0f - b);
+ //float blended_q = new_q * b + old_q * (1.0f - b);
+
+ float blended_i = interpol8 (old_i, new_i, b);
+ float blended_q = interpol8 (old_q, new_q, b);
+
+ sam = blended_i * sine_table[((tone_phase[chan] - PHASE_SHIFT_90) >> 24) & 0xff] +
+ blended_q * sine_table[(tone_phase[chan] >> 24) & 0xff];
+#else // jump
+ sam = ci[xmit_octant[chan]] * sine_table[((tone_phase[chan] - PHASE_SHIFT_90) >> 24) & 0xff] +
+ sq[xmit_octant[chan]] * sine_table[(tone_phase[chan] >> 24) & 0xff];
+#endif
+#else
+ sam = sine_table[(tone_phase[chan] >> 24) & 0xff];
+#endif
+ gen_tone_put_sample (chan, a, sam);
+ break;
+
+ case MODEM_8PSK:
#if DEBUG2
text_color_set(DW_COLOR_DEBUG);
dw_printf ("tone_gen_put_bit %d PSK\n", __LINE__);
@@ -521,6 +658,20 @@ void gen_tone_put_sample (int chan, int a, int sam) {
}
}
+void gen_tone_put_quiet_ms (int chan, int time_ms) {
+
+ int a = ACHAN2ADEV(chan); /* device for channel. */
+ int sam = 0;
+
+ int nsamples = (int) ((time_ms * (float)save_audio_config_p->adev[a].samples_per_sec / 1000.) + 0.5);
+
+ for (int j=0; j>= 1;
+ }
+}
+
+int eas_send (int chan, unsigned char *str, int repeat, int txdelay, int txtail)
+{
+ int bytes_sent = 0;
+ const int gap = 1000;
+ int gaps_sent = 0;
+
+ gen_tone_put_quiet_ms (chan, txdelay);
+
+ for (int r=0; r= 16) {
+ if (strncmp(infop+10, ":PARM.", 6) == 0) return (0);
+ if (strncmp(infop+10, ":UNIT.", 6) == 0) return (0);
+ if (strncmp(infop+10, ":EQNS.", 6) == 0) return (0);
+ if (strncmp(infop+10, ":BITS.", 6) == 0) return (0);
+ }
+ if (strlen(infop) >= 4) {
+ if (strncmp(infop+1, "BLN", 3) == 0) return (0);
+ if (strncmp(infop+1, "NWS", 3) == 0) return (0);
+ if (strncmp(infop+1, "SKY", 3) == 0) return (0);
+ if (strncmp(infop+1, "CWA", 3) == 0) return (0);
+ if (strncmp(infop+1, "BOM", 3) == 0) return (0);
+ }
+ return (1); // message, including ack, rej
+}
+
static void maybe_xmit_packet_from_igate (char *message, int to_chan)
{
@@ -1800,9 +1824,6 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan)
*gt = '\0';
}
-// FIXME NO!
- ///////ax25_get_addr_with_ssid (pp3, AX25_SOURCE, src);
-
/*
* Drop if path contains:
* NOGATE or RFONLY - means IGate should not pass them.
@@ -1843,7 +1864,7 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan)
* If we recently transmitted a 'message' from some station,
* send the position of the message sender when it comes along later.
*
- * Some refer to this as a courtesy posit report but I don't
+ * Some refer to this as a "courtesy posit report" but I don't
* think that is an official term.
*
* If we have a position report, look up the sender and see if we should
@@ -1988,10 +2009,7 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan)
#endif
stats_rf_xmit_packets++; // Any type of packet.
- // TEMP TEST: metadata temporarily allowed during testing.
-
- if (*pinfo == ':' && ! is_telem_metadata(pinfo)) {
- // temp test // if (*pinfo == ':') {
+ if (is_message_message(pinfo)) {
// We transmitted a "message." Telemetry metadata is excluded.
// Remember to pass along address of the sender later.
@@ -2449,6 +2467,8 @@ void ig_to_tx_remember (packet_t pp, int chan, int bydigi)
}
}
+
+
static int ig_to_tx_allow (packet_t pp, int chan)
{
unsigned short crc = ax25_dedupe_crc(pp);
@@ -2481,7 +2501,7 @@ static int ig_to_tx_allow (packet_t pp, int chan)
/* We have a duplicate within some time period. */
- if (*pinfo == ':' && ! is_telem_metadata((char*)pinfo)) {
+ if (is_message_message((char*)pinfo)) {
/* I think I want to avoid the duplicate suppression for "messages." */
/* Suppose we transmit a message from station X and it doesn't get an ack back. */
@@ -2530,7 +2550,7 @@ static int ig_to_tx_allow (packet_t pp, int chan)
/* the normal limit for them. */
increase_limit = 1;
- if (*pinfo == ':' && ! is_telem_metadata((char*)pinfo)) {
+ if (is_message_message((char*)pinfo)) {
increase_limit = 3;
}
diff --git a/src/il2p_header.c b/src/il2p_header.c
index 0ab34a01..94fd25ba 100644
--- a/src/il2p_header.c
+++ b/src/il2p_header.c
@@ -462,8 +462,11 @@ packet_t il2p_decode_header_type_1 (unsigned char *hdr, int num_sym_changed)
for (int i = 0; i < strlen(addrs[AX25_DESTINATION]); i++) {
if (! isupper(addrs[AX25_DESTINATION][i]) && ! isdigit(addrs[AX25_DESTINATION][i])) {
if (num_sym_changed == 0) {
- text_color_set(DW_COLOR_ERROR);
- dw_printf ("IL2P: Invalid character '%c' in destination address '%s'\n", addrs[AX25_DESTINATION][i], addrs[AX25_DESTINATION]);
+ // This can pop up sporadically when receiving random noise.
+ // Would be better to show only when debug is enabled but variable not available here.
+ // TODO: For now we will just suppress it.
+ //text_color_set(DW_COLOR_ERROR);
+ //dw_printf ("IL2P: Invalid character '%c' in destination address '%s'\n", addrs[AX25_DESTINATION][i], addrs[AX25_DESTINATION]);
}
return (NULL);
}
@@ -477,8 +480,11 @@ packet_t il2p_decode_header_type_1 (unsigned char *hdr, int num_sym_changed)
for (int i = 0; i < strlen(addrs[AX25_SOURCE]); i++) {
if (! isupper(addrs[AX25_SOURCE][i]) && ! isdigit(addrs[AX25_SOURCE][i])) {
if (num_sym_changed == 0) {
- text_color_set(DW_COLOR_ERROR);
- dw_printf ("IL2P: Invalid character '%c' in source address '%s'\n", addrs[AX25_SOURCE][i], addrs[AX25_SOURCE]);
+ // This can pop up sporadically when receiving random noise.
+ // Would be better to show only when debug is enabled but variable not available here.
+ // TODO: For now we will just suppress it.
+ //text_color_set(DW_COLOR_ERROR);
+ //dw_printf ("IL2P: Invalid character '%c' in source address '%s'\n", addrs[AX25_SOURCE][i], addrs[AX25_SOURCE]);
}
return (NULL);
}
diff --git a/src/il2p_test.c b/src/il2p_test.c
index c983daff..17995259 100644
--- a/src/il2p_test.c
+++ b/src/il2p_test.c
@@ -943,7 +943,7 @@ void tone_gen_put_bit (int chan, int data)
// This is called when a complete frame has been deserialized.
-void multi_modem_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, int is_fx25)
+void multi_modem_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, fec_type_t fec_type)
{
if (rec_count < 0) return; // Skip check before serdes test.
diff --git a/src/kiss_frame.c b/src/kiss_frame.c
index e304a838..aa581dd2 100644
--- a/src/kiss_frame.c
+++ b/src/kiss_frame.c
@@ -40,7 +40,8 @@
*
* The first byte of the frame contains:
*
- * * port number (radio channel) in upper nybble.
+ * * radio channel in upper nybble.
+ * (KISS doc uses "port" but I don't like that because it has too many meanings.)
* * command in lower nybble.
*
*
@@ -954,7 +955,7 @@ void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int
p = pmsg;
if (*p == FEND) p++;
- dw_printf ("%s %s %s KISS client application, port %d, total length = %d\n",
+ dw_printf ("%s %s %s KISS client application, channel %d, total length = %d\n",
prefix[(int)fromto], function[p[0] & 0xf], direction[(int)fromto],
(p[0] >> 4) & 0xf, msg_len);
}
diff --git a/src/log.c b/src/log.c
index c8b2f825..d7ef544b 100644
--- a/src/log.c
+++ b/src/log.c
@@ -224,7 +224,7 @@ void log_write (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_
now = time(NULL); // Get current time.
(void)gmtime_r (&now, &tm);
-
+// FIXME: https://github.com/wb2osz/direwolf/issues/473
if (g_daily_names) {
diff --git a/src/mheard.c b/src/mheard.c
index 5c20f303..f11c68f0 100644
--- a/src/mheard.c
+++ b/src/mheard.c
@@ -336,6 +336,52 @@ void mheard_save_rf (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, r
*/
hops = ax25_get_heard(pp) - AX25_SOURCE;
+/*
+ * Consider the following scenario:
+ *
+ * (1) We hear AA1PR-9 by a path of 4 digipeaters.
+ * Looking closer, it's probably only two because there are left over WIDE1-0 and WIDE2-0.
+ *
+ * Digipeater WIDE2 (probably N3LLO-3) audio level = 72(19/15) [NONE] _|||||___
+ * [0.3] AA1PR-9>APY300,K1EQX-7,WIDE1,N3LLO-3,WIDE2*,ARISS::ANSRVR :cq hotg vt aprsthursday{01<0x0d>
+ * ----- -----
+ *
+ * (2) APRS-IS sends a response to us.
+ *
+ * [ig>tx] ANSRVR>APWW11,KJ4ERJ-15*,TCPIP*,qAS,KJ4ERJ-15::AA1PR-9 :N:HOTG 161 Messages Sent{JL}
+ *
+ * (3) Here is our analysis of whether it should be sent to RF.
+ *
+ * Was message addressee AA1PR-9 heard in the past 180 minutes, with 2 or fewer digipeater hops?
+ * No, AA1PR-9 was last heard over the radio with 4 digipeater hops 0 minutes ago.
+ *
+ * The wrong hop count caused us to drop a packet that should have been transmitted.
+ * We could put in a hack to not count the "WIDEn-0" addresses.
+ * That is not correct because other prefixes could be used and we don't know
+ * what they are for other digipeaters.
+ * I think the best solution is to simply ignore the hop count.
+ * Maybe next release will have a major cleanup.
+ */
+
+ // HACK - Reduce hop count by number of used WIDEn-0 addresses.
+
+ if (hops > 1) {
+ for (int k = 0; k < ax25_get_num_repeaters(pp); k++) {
+ char digi[AX25_MAX_ADDR_LEN];
+ ax25_get_addr_no_ssid (pp, AX25_REPEATER_1 + k, digi);
+ int ssid = ax25_get_ssid (pp, AX25_REPEATER_1 + k);
+ int used = ax25_get_h (pp, AX25_REPEATER_1 + k);
+
+ //text_color_set(DW_COLOR_DEBUG);
+ //dw_printf ("Examining %s-%d used=%d.\n", digi, ssid, used);
+
+ if (used && strlen(digi) == 5 && strncmp(digi, "WIDE", 4) == 0 && isdigit(digi[4]) && ssid == 0) {
+ hops--;
+ //text_color_set(DW_COLOR_DEBUG);
+ //dw_printf ("Decrease hop count to %d for problematic %s.\n", hops, digi);
+ }
+ }
+ }
mptr = mheard_ptr(source);
if (mptr == NULL) {
@@ -571,7 +617,7 @@ void mheard_save_is (char *ptext)
* 8 for RF_CNT.
*
* time_limit - Include only stations heard within this many minutes.
- * Typically 30 or 60.
+ * Typically 180.
*
* Returns: Number to be used in the statistics report.
*
@@ -649,7 +695,7 @@ int mheard_count (int max_hops, int time_limit)
* callsign - Callsign for station.
*
* time_limit - Include only stations heard within this many minutes.
- * Typically 30 or 60.
+ * Typically 180.
*
* max_hops - Include only stations heard with this number of
* digipeater hops or less. For reporting, we might use:
diff --git a/src/multi_modem.c b/src/multi_modem.c
index d02b8c2a..d2382f1a 100644
--- a/src/multi_modem.c
+++ b/src/multi_modem.c
@@ -116,7 +116,8 @@ static struct audio_s *save_audio_config_p;
static struct {
packet_t packet_p;
alevel_t alevel;
- int is_fx25; // 1 for FX.25, 0 for regular AX.25.
+ float speed_error;
+ fec_type_t fec_type; // Type of FEC: none(0), fx25, il2p
retry_t retries; // For the old "fix bits" strategy, this is the
// number of bits that were modified to get a good CRC.
// It would be 0 to something around 4.
@@ -306,14 +307,14 @@ void multi_modem_process_sample (int chan, int audio_sample)
* display of audio level line.
* Use -2 to indicate DTMF message.)
* retries - Level of correction used.
- * is_fx25 - 1 for FX.25, 0 for normal AX.25.
+ * fec_type - none(0), fx25, il2p
*
* Description: Add to list of candidates. Best one will be picked later.
*
*--------------------------------------------------------------------*/
-void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries, int is_fx25)
+void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries, fec_type_t fec_type)
{
packet_t pp;
@@ -346,12 +347,12 @@ void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned c
pp = ax25_from_frame (fbuf, flen, alevel);
}
- multi_modem_process_rec_packet (chan, subchan, slice, pp, alevel, retries, is_fx25);
+ multi_modem_process_rec_packet (chan, subchan, slice, pp, alevel, retries, fec_type);
}
// TODO: Eliminate function above and move code elsewhere?
-void multi_modem_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, int is_fx25)
+void multi_modem_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, fec_type_t fec_type)
{
if (pp == NULL) {
text_color_set(DW_COLOR_ERROR);
@@ -386,7 +387,7 @@ void multi_modem_process_rec_packet (int chan, int subchan, int slice, packet_t
ax25_delete (pp);
}
else {
- dlq_rec_frame (chan, subchan, slice, pp, alevel, is_fx25, retries, "");
+ dlq_rec_frame (chan, subchan, slice, pp, alevel, fec_type, retries, "");
}
return;
}
@@ -406,7 +407,7 @@ void multi_modem_process_rec_packet (int chan, int subchan, int slice, packet_t
candidate[chan][subchan][slice].packet_p = pp;
candidate[chan][subchan][slice].alevel = alevel;
- candidate[chan][subchan][slice].is_fx25 = is_fx25;
+ candidate[chan][subchan][slice].fec_type = fec_type;
candidate[chan][subchan][slice].retries = retries;
candidate[chan][subchan][slice].age = 0;
candidate[chan][subchan][slice].crc = ax25_m_m_crc(pp);
@@ -443,6 +444,9 @@ static void pick_best_candidate (int chan)
int best_n, best_score;
char spectrum[MAX_SUBCHANS*MAX_SLICERS+1];
int n, j, k;
+ if (save_audio_config_p->achan[chan].num_slicers < 1) {
+ save_audio_config_p->achan[chan].num_slicers = 1;
+ }
int num_bars = save_audio_config_p->achan[chan].num_slicers * save_audio_config_p->achan[chan].num_subchan;
memset (spectrum, 0, sizeof(spectrum));
@@ -456,7 +460,7 @@ static void pick_best_candidate (int chan)
if (candidate[chan][j][k].packet_p == NULL) {
spectrum[n] = '_';
}
- else if (candidate[chan][j][k].is_fx25) {
+ else if (candidate[chan][j][k].fec_type != fec_type_none) { // FX.25 or IL2P
// FIXME: using retries both as an enum and later int too.
if ((int)(candidate[chan][j][k].retries) <= 9) {
spectrum[n] = '0' + candidate[chan][j][k].retries;
@@ -464,7 +468,7 @@ static void pick_best_candidate (int chan)
else {
spectrum[n] = '+';
}
- }
+ } // AX.25 below
else if (candidate[chan][j][k].retries == RETRY_NONE) {
spectrum[n] = '|';
}
@@ -481,8 +485,8 @@ static void pick_best_candidate (int chan)
candidate[chan][j][k].score = 0;
}
else {
- if (candidate[chan][j][k].is_fx25) {
- candidate[chan][j][k].score = 9000 - 100 * candidate[chan][j][k].retries;
+ if (candidate[chan][j][k].fec_type != fec_type_none) {
+ candidate[chan][j][k].score = 9000 - 100 * candidate[chan][j][k].retries; // has FEC
}
else {
/* Originally, this produced 0 for the PASSALL case. */
@@ -550,9 +554,9 @@ static void pick_best_candidate (int chan)
candidate[chan][j][k].packet_p);
}
else {
- dw_printf ("%d.%d.%d: ptr=%p, is_fx25=%d, retry=%d, age=%3d, crc=%04x, score=%d %s\n", chan, j, k,
+ dw_printf ("%d.%d.%d: ptr=%p, fec_type=%d, retry=%d, age=%3d, crc=%04x, score=%d %s\n", chan, j, k,
candidate[chan][j][k].packet_p,
- candidate[chan][j][k].is_fx25,
+ (int)(candidate[chan][j][k].fec_type),
(int)(candidate[chan][j][k].retries),
candidate[chan][j][k].age,
candidate[chan][j][k].crc,
@@ -611,7 +615,7 @@ static void pick_best_candidate (int chan)
dlq_rec_frame (chan, j, k,
candidate[chan][j][k].packet_p,
candidate[chan][j][k].alevel,
- candidate[chan][j][k].is_fx25,
+ candidate[chan][j][k].fec_type,
(int)(candidate[chan][j][k].retries),
spectrum);
diff --git a/src/multi_modem.h b/src/multi_modem.h
index de3061ed..51c3cde5 100644
--- a/src/multi_modem.h
+++ b/src/multi_modem.h
@@ -17,8 +17,8 @@ void multi_modem_process_sample (int c, int audio_sample);
int multi_modem_get_dc_average (int chan);
// Deprecated. Replace with ...packet
-void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries, int is_fx25);
+void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries, fec_type_t fec_type);
-void multi_modem_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, int is_fx25);
+void multi_modem_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, fec_type_t fec_type);
#endif
diff --git a/src/pfilter.c b/src/pfilter.c
index 08922f52..35767a67 100644
--- a/src/pfilter.c
+++ b/src/pfilter.c
@@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
-// Copyright (C) 2015, 2016 John Langner, WB2OSZ
+// Copyright (C) 2015, 2016, 2023 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
@@ -546,7 +546,8 @@ static int parse_filter_spec (pfstate_t *pf)
/* b - budlist */
else if (pf->token_str[0] == 'b' && ispunct(pf->token_str[1])) {
- /* Budlist - source address */
+ /* Budlist - AX.25 source address */
+ /* Could be different than source encapsulated by 3rd party header. */
char addr[AX25_MAX_ADDR_LEN];
ax25_get_addr_with_ssid (pf->pp, AX25_SOURCE, addr);
result = filt_bodgu (pf, addr);
@@ -572,7 +573,7 @@ static int parse_filter_spec (pfstate_t *pf)
else if (pf->token_str[0] == 'd' && ispunct(pf->token_str[1])) {
int n;
- // loop on all digipeaters
+ // Loop on all AX.25 digipeaters.
result = 0;
for (n = AX25_REPEATER_1; result == 0 && n < ax25_get_num_addr (pf->pp); n++) {
// Consider only those with the H (has-been-used) bit set.
@@ -599,7 +600,7 @@ static int parse_filter_spec (pfstate_t *pf)
else if (pf->token_str[0] == 'v' && ispunct(pf->token_str[1])) {
int n;
- // loop on all digipeaters (mnemonic Via)
+ // loop on all AX.25 digipeaters (mnemonic Via)
result = 0;
for (n = AX25_REPEATER_1; result == 0 && n < ax25_get_num_addr (pf->pp); n++) {
// This is different than the previous "d" filter.
@@ -623,10 +624,15 @@ static int parse_filter_spec (pfstate_t *pf)
}
}
-/* g - Addressee of message. */
+/* g - Addressee of message. e.g. "BLN*" for bulletins. */
else if (pf->token_str[0] == 'g' && ispunct(pf->token_str[1])) {
- if (ax25_get_dti(pf->pp) == ':') {
+ if (pf->decoded.g_message_subtype == message_subtype_message ||
+ pf->decoded.g_message_subtype == message_subtype_ack ||
+ pf->decoded.g_message_subtype == message_subtype_rej ||
+ pf->decoded.g_message_subtype == message_subtype_bulletin ||
+ pf->decoded.g_message_subtype == message_subtype_nws ||
+ pf->decoded.g_message_subtype == message_subtype_directed_query) {
result = filt_bodgu (pf, pf->decoded.g_addressee);
if (s_debug >= 2) {
@@ -643,7 +649,7 @@ static int parse_filter_spec (pfstate_t *pf)
}
}
-/* u - unproto (destination) */
+/* u - unproto (AX.25 destination) */
else if (pf->token_str[0] == 'u' && ispunct(pf->token_str[1])) {
/* Probably want to exclude mic-e types */
@@ -668,7 +674,7 @@ static int parse_filter_spec (pfstate_t *pf)
}
}
-/* t - type: position, weather, etc. */
+/* t - packet type: position, weather, telemetry, etc. */
else if (pf->token_str[0] == 't' && ispunct(pf->token_str[1])) {
@@ -728,7 +734,7 @@ static int parse_filter_spec (pfstate_t *pf)
(void) ax25_get_info (pf->pp, (unsigned char **)(&infop));
text_color_set(DW_COLOR_DEBUG);
- if (*infop == ':' && ! is_telem_metadata(infop)) {
+ if (pf->decoded.g_packet_type == packet_type_message) {
dw_printf (" %s returns %s for message to %s\n", pf->token_str, bool2text(result), pf->decoded.g_addressee);
}
else {
@@ -832,31 +838,17 @@ static int filt_bodgu (pfstate_t *pf, char *arg)
* 0 = no
* -1 = error detected
*
- * Description: The filter is based the type filtering described here:
+ * Description: The filter is loosely based the type filtering described here:
* http://www.aprs-is.net/javAPRSFilter.aspx
*
- * Most of these simply check the first byte of the information part.
- * Trying to detect NWS information is a little trickier.
+ * Mostly use g_packet_type and g_message_subtype from decode_aprs.
+ *
+ * References:
* http://www.aprs-is.net/WX/
- * http://wxsvr.aprs.net.au/protocol-new.html
+ * http://wxsvr.aprs.net.au/protocol-new.html (has disappeared)
*
*------------------------------------------------------------------------------*/
-/* Telemetry metadata is a special case of message. */
-/* We want to categorize it as telemetry rather than message. */
-
-int is_telem_metadata (char *infop)
-{
- if (*infop != ':') return (0);
- if (strlen(infop) < 16) return (0);
- if (strncmp(infop+10, ":PARM.", 6) == 0) return (1);
- if (strncmp(infop+10, ":UNIT.", 6) == 0) return (1);
- if (strncmp(infop+10, ":EQNS.", 6) == 0) return (1);
- if (strncmp(infop+10, ":BITS.", 6) == 0) return (1);
- return (0);
-}
-
-
static int filt_t (pfstate_t *pf)
{
char src[AX25_MAX_ADDR_LEN];
@@ -873,108 +865,60 @@ static int filt_t (pfstate_t *pf)
switch (*f) {
case 'p': /* Position */
- if (*infop == '!') return (1);
- if (*infop == '/') return (1);
- if (*infop == '=') return (1);
- if (*infop == '@') return (1);
- if (*infop == '\'') return (1); // MIC-E
- if (*infop == '`') return (1); // MIC-E
-
- // What if we have "_" symbol code for weather?
- // Still consider as position.
- // The same packet can match more than one type here.
+ if (pf->decoded.g_packet_type == packet_type_position) return(1);
break;
case 'o': /* Object */
- if (*infop == ';') return (1);
+ if (pf->decoded.g_packet_type == packet_type_object) return(1);
break;
case 'i': /* Item */
- if (*infop == ')') return (1);
+ if (pf->decoded.g_packet_type == packet_type_item) return(1);
break;
- case 'm': /* Message */
- if (*infop == ':' && ! is_telem_metadata(infop)) return (1);
+ case 'm': // Any "message."
+ if (pf->decoded.g_packet_type == packet_type_message) return(1);
break;
case 'q': /* Query */
- if (*infop == '?') return (1);
+ if (pf->decoded.g_packet_type == packet_type_query) return(1);
break;
case 'c': /* station Capabilities - my extension */
/* Most often used for IGate statistics. */
- if (*infop == '<') return (1);
+ if (pf->decoded.g_packet_type == packet_type_capabilities) return(1);
break;
case 's': /* Status */
- if (*infop == '>') return (1);
+ if (pf->decoded.g_packet_type == packet_type_status) return(1);
break;
- case 't': /* Telemetry */
- if (*infop == 'T') return (1);
- if (is_telem_metadata(infop)) return (1);
+ case 't': /* Telemetry data or metadata */
+ if (pf->decoded.g_packet_type == packet_type_telemetry) return(1);
break;
case 'u': /* User-defined */
- if (*infop == '{') return (1);
+ if (pf->decoded.g_packet_type == packet_type_userdefined) return(1);
break;
- case 'h': /* third party Header - my extension */
- if (*infop == '}') return (1);
+ case 'h': /* has third party Header - my extension */
+ if (pf->decoded.g_has_thirdparty_header) return (1);
break;
case 'w': /* Weather */
- if (*infop == '*') return (1); // Peet Bros
- if (*infop == '_') return (1); // Weather report, no position.
- if (strncmp(infop, "!!", 2) == 0) return(1); // Ultimeter 2000.
-
- /* '$' is normally raw GPS. Check for special case. */
-
- if (strncmp(infop, "$ULTW", 5) == 0) return (1);
-
- /* Positions !=/@ with symbol code _ are weather. */
-
- if (strchr("!=/@", *infop) != NULL &&
- pf->decoded.g_symbol_code == '_') return (1);
+ if (pf->decoded.g_packet_type == packet_type_weather) return(1);
+ /* Positions !=/@ with symbol code _ are weather. */
/* Object with _ symbol is also weather. APRS protocol spec page 66. */
+ // Can't use *infop because it would not work with 3rd party header.
- if (*infop == ';' &&
- pf->decoded.g_symbol_code == '_') return (1);
-
-// TODO: need more test cases at end for new weather cases.
-
+ if ((pf->decoded.g_packet_type == packet_type_position ||
+ pf->decoded.g_packet_type == packet_type_object) && pf->decoded.g_symbol_code == '_') return (1);
break;
case 'n': /* NWS format */
-/*
- * This is the interesting case.
- * The source must be exactly 6 upper case letters, no SSID.
- */
- if (strlen(src) != 6) break;
- if (! isupper(src[0])) break;
- if (! isupper(src[1])) break;
- if (! isupper(src[2])) break;
- if (! isupper(src[3])) break;
- if (! isupper(src[4])) break;
- if (! isupper(src[5])) break;
-/*
- * We can have a "message" with addressee starting with NWS, SKY, or BOM (Australian version.)
- */
- if (strncmp(infop, ":NWS", 4) == 0) return (1);
- if (strncmp(infop, ":SKY", 4) == 0) return (1);
- if (strncmp(infop, ":BOM", 4) == 0) return (1);
-/*
- * Or we can have an object.
- * It's not exactly clear how to distinguish this from other objects.
- * It looks like the first 3 characters of the source should be the same
- * as the first 3 characters of the addressee.
- */
- if (infop[0] == ';' &&
- infop[1] == src[0] &&
- infop[2] == src[1] &&
- infop[3] == src[2]) return (1);
+ if (pf->decoded.g_packet_type == packet_type_nws) return(1);
break;
default:
@@ -990,6 +934,7 @@ static int filt_t (pfstate_t *pf)
+
/*------------------------------------------------------------------------------
*
* Name: filt_r
@@ -1327,8 +1272,41 @@ static int filt_s (pfstate_t *pf)
*
* IMHO, the rules here are too restrictive.
*
- * (1) The APRS-IS would send a "message" to my IGate only if the addressee
+ * The APRS-IS would send a "message" to my IGate only if the addressee
* has been heard nearby recently. 180 minutes, I believe.
+ * Why would I not want to transmit it?
+ *
+ * Discussion: In retrospect, I think this is far too complicated.
+ * In a future release, I think at options other than time should be removed.
+ * Messages have more value than most packets. Why reduce the chance of successful delivery?
+ *
+ * Consider the following scenario:
+ *
+ * (1) We hear AA1PR-9 by a path of 4 digipeaters.
+ * Looking closer, it's probably only two because there are left over WIDE1-0 and WIDE2-0.
+ *
+ * Digipeater WIDE2 (probably N3LLO-3) audio level = 72(19/15) [NONE] _|||||___
+ * [0.3] AA1PR-9>APY300,K1EQX-7,WIDE1,N3LLO-3,WIDE2*,ARISS::ANSRVR :cq hotg vt aprsthursday{01<0x0d>
+ *
+ * (2) APRS-IS sends a response to us.
+ *
+ * [ig>tx] ANSRVR>APWW11,KJ4ERJ-15*,TCPIP*,qAS,KJ4ERJ-15::AA1PR-9 :N:HOTG 161 Messages Sent{JL}
+ *
+ * (3) Here is our analysis of whether it should be sent to RF.
+ *
+ * Was message addressee AA1PR-9 heard in the past 180 minutes, with 2 or fewer digipeater hops?
+ * No, AA1PR-9 was last heard over the radio with 4 digipeater hops 0 minutes ago.
+ *
+ * The wrong hop count caused us to drop a packet that should have been transmitted.
+ * We could put in a hack to not count the "WIDE*-0" addresses.
+ * That is not correct because other prefixes could be used and we don't know
+ * what they are for other digipeaters.
+ * I think the best solution is to simply ignore the hop count.
+ *
+ * Release 1.7: I got overly ambitious and now realize this is just giving people too much
+ * "rope to hang themselves," drop messages unexpectedly, and accidentally break messaging.
+ * Change documentation to mention only the time limit.
+ * The other functionality will be undocumented and maybe disappear over time.
*
*------------------------------------------------------------------------------*/
@@ -1357,9 +1335,9 @@ static int filt_i (pfstate_t *pf)
double km = G_UNKNOWN;
- char src[AX25_MAX_ADDR_LEN];
- char *infop = NULL;
- int info_len;
+ //char src[AX25_MAX_ADDR_LEN];
+ //char *infop = NULL;
+ //int info_len;
//char *f;
//char addressee[AX25_MAX_ADDR_LEN];
@@ -1433,20 +1411,7 @@ static int filt_i (pfstate_t *pf)
* Get source address and info part.
* Addressee has already been extracted into pf->decoded.g_addressee.
*/
-
- memset (src, 0, sizeof(src));
- ax25_get_addr_with_ssid (pf->pp, AX25_SOURCE, src);
- info_len = ax25_get_info (pf->pp, (unsigned char **)(&infop));
-
- if (infop == NULL) return (0);
- if (info_len < 1) return (0);
-
-// Determine packet type. We are interested only in "message."
-// Telemetry metadata is not considered a message.
-
- if (*infop != ':') return (0);
- if (is_telem_metadata(infop)) return (0);
-
+ if (pf->decoded.g_packet_type != packet_type_message) return(0);
#if defined(PFTEST) || defined(DIGITEST) // TODO: test functionality too, not just syntax.
@@ -1486,7 +1451,7 @@ static int filt_i (pfstate_t *pf)
* the past minute, rather than the usual 180 minutes for the addressee.
*/
- was_heard = mheard_was_recently_nearby ("source", src, 1, 0, G_UNKNOWN, G_UNKNOWN, G_UNKNOWN);
+ was_heard = mheard_was_recently_nearby ("source", pf->decoded.g_src, 1, 0, G_UNKNOWN, G_UNKNOWN, G_UNKNOWN);
if (was_heard) return (0);
@@ -1640,24 +1605,29 @@ int main ()
pftest (112, "t/t", "WM1X>APU25N:@210147z4235.39N/07106.58W_359/000g000t027r000P000p000h89b10234/WX REPORT {UIV32N}<0x0d>", 0);
pftest (113, "t/w", "WM1X>APU25N:@210147z4235.39N/07106.58W_359/000g000t027r000P000p000h89b10234/WX REPORT {UIV32N}<0x0d>", 1);
- /* Telemetry metadata is a special case of message. */
+ /* Telemetry metadata should not be classified as message. */
pftest (114, "t/t", "KJ4SNT>APMI04::KJ4SNT :PARM.Vin,Rx1h,Dg1h,Eff1h,Rx10m,O1,O2,O3,O4,I1,I2,I3,I4", 1);
pftest (115, "t/m", "KJ4SNT>APMI04::KJ4SNT :PARM.Vin,Rx1h,Dg1h,Eff1h,Rx10m,O1,O2,O3,O4,I1,I2,I3,I4", 0);
pftest (116, "t/t", "KB1GKN-10>APRX27,UNCAN,WIDE1*:T#491,4.9,0.3,25.0,0.0,1.0,00000000", 1);
+ /* Bulletins should not be considered to be messages. Was bug in 1.6. */
+ pftest (117, "t/m", "A>B::W1AW :test", 1);
+ pftest (118, "t/m", "A>B::BLN :test", 0);
+ pftest (119, "t/m", "A>B::NWS :test", 0);
- pftest (120, "t/p", "CWAPID>APRS::NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", 0);
+ // https://www.aprs-is.net/WX/
+ pftest (121, "t/p", "CWAPID>APRS::NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", 0);
pftest (122, "t/p", "CWAPID>APRS::SKYCWA :DDHHMMz,ADVISETYPE,zcs{seq#", 0);
pftest (123, "t/p", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", 0);
pftest (124, "t/n", "CWAPID>APRS::NWS-TTTTT:DDHHMMz,ADVISETYPE,zcs{seq#", 1);
pftest (125, "t/n", "CWAPID>APRS::SKYCWA :DDHHMMz,ADVISETYPE,zcs{seq#", 1);
- pftest (126, "t/n", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", 1);
+ //pftest (126, "t/n", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", 1);
pftest (127, "t/", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", 0);
pftest (128, "t/c", "S0RCE>DEST:DEST:DEST:}thirdpartyheaderwhatever", 1);
- pftest (131, "t/c", "S0RCE>DEST:}thirdpartyheaderwhatever", 0);
+ pftest (130, "t/h", "S0RCE>DEST:}WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+ pftest (131, "t/c", "S0RCE>DEST:}WB2OSZ-5>APDW12,DIGI1,DIGI2*,DIGI3,DIGI4:!4237.14NS07120.83W#PHG7140Chelmsford MA", 0);
pftest (140, "r/42.6/-71.3/10", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
pftest (141, "r/42.6/-71.3/10", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", 0);
@@ -1712,13 +1682,13 @@ int main ()
pftest (203, "t/w t/w", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1);
pftest (204, "r/42.6/-71.3", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", -1);
- pftest (220, "i/30/8/42.6/-71.3/50", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
- pftest (222, "i/30/8/42.6/-71.3/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1);
- pftest (223, "i/30/8/42.6/-71.3", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1);
- pftest (224, "i/30/8/42.6/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1);
- pftest (225, "i/30/8/42.6", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1);
- pftest (226, "i/30/8/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
- pftest (227, "i/30/8", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
+ pftest (210, "i/30/8/42.6/-71.3/50", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
+ pftest (212, "i/30/8/42.6/-71.3/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1);
+ pftest (213, "i/30/8/42.6/-71.3", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1);
+ pftest (214, "i/30/8/42.6/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1);
+ pftest (215, "i/30/8/42.6", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1);
+ pftest (216, "i/30/8/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
+ pftest (217, "i/30/8", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
// FIXME: behaves differently on Windows and Linux. Why?
// On Windows we have our own version of strsep because it's not in the MS library.
@@ -1726,7 +1696,12 @@ int main ()
//pftest (228, "i/30/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
pftest (229, "i/30", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
- pftest (230, "i/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1);
+ pftest (230, "i/30", "X>X:}WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
+ pftest (231, "i/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", -1);
+
+ // Besure bulletins and telemetry metadata don't get included.
+ pftest (234, "i/30", "KJ4SNT>APMI04::KJ4SNT :PARM.Vin,Rx1h,Dg1h,Eff1h,Rx10m,O1,O2,O3,O4,I1,I2,I3,I4", 0);
+ pftest (235, "i/30", "A>B::BLN :test", 0);
pftest (240, "s/", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1);
pftest (241, "s/'/O/-/#/_", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1);
@@ -1736,12 +1711,36 @@ int main ()
pftest (245, "s//", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1);
pftest (246, "s///", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1);
+ // Third party header - done properly in 1.7.
+ // Packet filter t/h is no longer a mutually exclusive packet type.
+ // Now it is an independent attribute and the encapsulated part is evaluated.
+
+ pftest (250, "o/home", "A>B:}WB2OSZ>APDW12,WIDE1-1,WIDE2-1:;home *111111z4237.14N/07120.83W-Chelmsford MA", 1);
+ pftest (251, "t/p", "A>B:}W1WRA-7>TRSY3T,WIDE1-1,WIDE2-1:`c-:l!hK\\>\"4b}=<0x0d>", 1);
+ pftest (252, "i/180", "A>B:}WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
+ pftest (253, "t/m", "A>B:}WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
+ pftest (254, "r/42.6/-71.3/10", "A>B:}WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
+ pftest (254, "r/42.6/-71.3/10", "A>B:}WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", 0);
+ pftest (255, "t/h", "KB1GKN-10>APRX27,UNCAN,WIDE1*:T#491,4.9,0.3,25.0,0.0,1.0,00000000", 0);
+ pftest (256, "t/h", "A>B:}KB1GKN-10>APRX27,UNCAN,WIDE1*:T#491,4.9,0.3,25.0,0.0,1.0,00000000", 1);
+ pftest (258, "t/t", "A>B:}KB1GKN-10>APRX27,UNCAN,WIDE1*:T#491,4.9,0.3,25.0,0.0,1.0,00000000", 1);
+ pftest (259, "t/t", "A>B:}KJ4SNT>APMI04::KJ4SNT :PARM.Vin,Rx1h,Dg1h,Eff1h,Rx10m,O1,O2,O3,O4,I1,I2,I3,I4", 1);
+
+ pftest (270, "g/BLN*", "WB2OSZ>APDW17::BLN1xxxxx:bulletin text", 1);
+ pftest (271, "g/BLN*", "A>B:}WB2OSZ>APDW17::BLN1xxxxx:bulletin text", 1);
+ pftest (272, "g/BLN*", "A>B:}WB2OSZ>APDW17::W1AW :xxxx", 0);
+
+ pftest (273, "g/NWS*", "WB2OSZ>APDW17::NWS-xxxxx:weather bulletin", 1);
+ pftest (274, "g/NWS*", "A>B:}WB2OSZ>APDW17::NWS-xxxxx:weather bulletin", 1);
+ pftest (275, "g/NWS*", "A>B:}WB2OSZ>APDW17::W1AW :xxxx", 0);
+
+// TODO: add b/ with 3rd party header.
-// TODO: to be continued...
+// TODO: to be continued... directed query ...
if (error_count > 0) {
text_color_set (DW_COLOR_ERROR);
- dw_printf ("\nPacket Filtering Test - FAILED!\n");
+ dw_printf ("\nPacket Filtering Test - FAILED! %d errors\n", error_count);
exit (EXIT_FAILURE);
}
text_color_set (DW_COLOR_REC);
diff --git a/src/ptt.c b/src/ptt.c
index 3afef5e4..af746626 100644
--- a/src/ptt.c
+++ b/src/ptt.c
@@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
-// Copyright (C) 2011, 2013, 2014, 2015, 2016, 2017 John Langner, WB2OSZ
+// Copyright (C) 2011, 2013, 2014, 2015, 2016, 2017, 2023 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
@@ -162,6 +162,10 @@
#include
#endif
+#ifdef USE_GPIOD
+#include
+#endif
+
/* So we can have more common code for fd. */
typedef int HANDLE;
#define INVALID_HANDLE_VALUE (-1)
@@ -176,6 +180,7 @@ typedef int HANDLE;
#include "audio.h"
#include "ptt.h"
#include "dlq.h"
+#include "demod.h" // to mute recv audio during xmit if half duplex.
#if __WIN32__
@@ -467,6 +472,20 @@ void export_gpio(int ch, int ot, int invert, int direction)
text_color_set(DW_COLOR_ERROR);
dw_printf ("Error writing \"%s\" to %s, errno=%d\n", stemp, gpio_export_path, e);
dw_printf ("%s\n", strerror(e));
+
+ if (e == 22) {
+ // It appears that error 22 occurs when sysfs gpio is not available.
+ // (See https://github.com/wb2osz/direwolf/issues/503)
+ //
+ // The solution might be to use the new gpiod approach.
+
+ dw_printf ("It looks like gpio with sysfs is not supported on this operating system.\n");
+ dw_printf ("Rather than the following form, in the configuration file,\n");
+ dw_printf (" PTT GPIO %s\n", stemp);
+ dw_printf ("try using gpiod form instead. e.g.\n");
+ dw_printf (" PTT GPIOD gpiochip0 %s\n", stemp);
+ dw_printf ("You can get a list of gpio chip names and corresponding I/O lines with \"gpioinfo\" command.\n");
+ }
exit (1);
}
}
@@ -633,6 +652,31 @@ void export_gpio(int ch, int ot, int invert, int direction)
get_access_to_gpio (gpio_value_path);
}
+#if defined(USE_GPIOD)
+int gpiod_probe(const char *chip_name, int line_number)
+{
+ struct gpiod_chip *chip;
+ chip = gpiod_chip_open_by_name(chip_name);
+ if (chip == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Can't open GPIOD chip %s.\n", chip_name);
+ return -1;
+ }
+
+ struct gpiod_line *line;
+ line = gpiod_chip_get_line(chip, line_number);
+ if (line == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Can't get GPIOD line %d.\n", line_number);
+ return -1;
+ }
+ if (ptt_debug_level >= 2) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf("GPIOD probe OK. Chip: %s line: %d\n", chip_name, line_number);
+ }
+ return 0;
+}
+#endif /* USE_GPIOD */
#endif /* not __WIN32__ */
@@ -649,7 +693,8 @@ void export_gpio(int ch, int ot, int invert, int direction)
* ptt_method 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.
+ * PTT_METHOD_GPIO - general purpose I/O (sysfs).
+ * PTT_METHOD_GPIOD - general purpose I/O (libgpiod).
* PTT_METHOD_LPT - Parallel printer port.
* PTT_METHOD_HAMLIB - HAMLib rig control.
* PTT_METHOD_CM108 - GPIO pins of CM108 etc. USB Audio.
@@ -728,12 +773,13 @@ void ptt_init (struct audio_s *audio_config_p)
if (ptt_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
- dw_printf ("ch=%d, %s method=%d, device=%s, line=%d, gpio=%d, lpt_bit=%d, invert=%d\n",
+ dw_printf ("ch=%d, %s method=%d, device=%s, line=%d, name=%s, gpio=%d, lpt_bit=%d, invert=%d\n",
ch,
otnames[ot],
audio_config_p->achan[ch].octrl[ot].ptt_method,
audio_config_p->achan[ch].octrl[ot].ptt_device,
audio_config_p->achan[ch].octrl[ot].ptt_line,
+ audio_config_p->achan[ch].octrl[ot].out_gpio_name,
audio_config_p->achan[ch].octrl[ot].out_gpio_num,
audio_config_p->achan[ch].octrl[ot].ptt_lpt_bit,
audio_config_p->achan[ch].octrl[ot].ptt_invert);
@@ -879,7 +925,28 @@ void ptt_init (struct audio_s *audio_config_p)
if (using_gpio) {
get_access_to_gpio ("/sys/class/gpio/export");
}
-
+#if defined(USE_GPIOD)
+ // GPIOD
+ for (ch = 0; ch < MAX_CHANS; ch++) {
+ if (save_audio_config_p->chan_medium[ch] == MEDIUM_RADIO) {
+ for (int ot = 0; ot < NUM_OCTYPES; ot++) {
+ if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_GPIOD) {
+ const char *chip_name = audio_config_p->achan[ch].octrl[ot].out_gpio_name;
+ int line_number = audio_config_p->achan[ch].octrl[ot].out_gpio_num;
+ int rc = gpiod_probe(chip_name, line_number);
+ if (rc < 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Disable PTT for channel %d\n", ch);
+ audio_config_p->achan[ch].octrl[ot].ptt_method = PTT_METHOD_NONE;
+ } else {
+ // Set initial state off ptt_set will invert output signal if appropriate.
+ ptt_set (ot, ch, 0);
+ }
+ }
+ }
+ }
+ }
+#endif /* USE_GPIOD */
/*
* We should now be able to create the device nodes for
* the pins we want to use.
@@ -990,6 +1057,8 @@ void ptt_init (struct audio_s *audio_config_p)
for (ot = 0; ot < NUM_OCTYPES; ot++) {
if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_HAMLIB) {
if (ot == OCTYPE_PTT) {
+ int err = -1;
+ int tries = 0;
/* For "AUTO" model, try to guess what is out there. */
@@ -1054,13 +1123,24 @@ void ptt_init (struct audio_s *audio_config_p)
rig[ch][ot]->state.rigport.parm.serial.parity = RIG_PARITY_NONE;
rig[ch][ot]->state.rigport.parm.serial.handshake = RIG_HANDSHAKE_NONE;
}
- int err = rig_open(rig[ch][ot]);
+ tries = 0;
+ do {
+ // Try up to 5 times, Hamlib can take a moment to finish init
+ err = rig_open(rig[ch][ot]);
+ if (++tries > 5) {
+ break;
+ } else if (err != RIG_OK) {
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("Retrying Hamlib Rig open...\n");
+ sleep (5);
+ }
+ } while (err != RIG_OK);
if (err != RIG_OK) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Hamlib Rig open error %d: %s\n", err, rigerror(err));
rig_cleanup (rig[ch][ot]);
rig[ch][ot] = NULL;
- continue;
+ exit (1);
}
/* Successful. Later code should check for rig[ch][ot] not NULL. */
@@ -1109,7 +1189,27 @@ void ptt_init (struct audio_s *audio_config_p)
if (audio_config_p->chan_medium[ch] == MEDIUM_RADIO) {
if(audio_config_p->achan[ch].octrl[OCTYPE_PTT].ptt_method == PTT_METHOD_NONE) {
text_color_set(DW_COLOR_INFO);
- dw_printf ("Note: PTT not configured for channel %d. (Ignore this if using VOX.)\n", ch);
+ dw_printf ("\n");
+ dw_printf ("Note: PTT not configured for channel %d. (OK if using VOX.)\n", ch);
+ dw_printf ("When using VOX, ensure that it adds very little delay (e.g. 10-20) milliseconds\n");
+ dw_printf ("between the time that transmit audio ends and PTT is deactivated.\n");
+ dw_printf ("For example, if using a SignaLink USB, turn the DLY control all the\n");
+ dw_printf ("way counter clockwise.\n");
+ dw_printf ("\n");
+ dw_printf ("Using VOX built in to the radio is a VERY BAD idea. This is intended\n");
+ dw_printf ("for voice operation, with gaps in the sound, and typically has a delay of about a\n");
+ dw_printf ("half second between the time the audio stops and the transmitter is turned off.\n");
+ dw_printf ("When using APRS your transmiter will be sending a quiet carrier for\n");
+ dw_printf ("about a half second after your packet ends. This may interfere with the\n");
+ dw_printf ("the next station to transmit. This is being inconsiderate.\n");
+ dw_printf ("\n");
+ dw_printf ("If you are trying to use VOX with connected mode packet, expect\n");
+ dw_printf ("frustration and disappointment. Connected mode involves rapid responses\n");
+ dw_printf ("which you will probably miss because your transmitter is still on when\n");
+ dw_printf ("the response is being transmitted.\n");
+ dw_printf ("\n");
+ dw_printf ("Read the User Guide 'Transmit Timing' section for more details.\n");
+ dw_printf ("\n");
}
}
}
@@ -1141,6 +1241,8 @@ void ptt_init (struct audio_s *audio_config_p)
*
*--------------------------------------------------------------------*/
+// JWL - save status and new get_ptt function.
+
void ptt_set (int ot, int chan, int ptt_signal)
{
@@ -1164,6 +1266,19 @@ void ptt_set (int ot, int chan, int ptt_signal)
return;
}
+// New in 1.7.
+// A few people have a really bad audio cross talk situation where they receive their own transmissions.
+// It usually doesn't cause a problem but it is confusing to look at.
+// "half duplex" setting applied only to the transmit logic. i.e. wait for clear channel before sending.
+// Receiving was still active.
+// I think the simplest solution is to mute/unmute the audio input at this point if not full duplex.
+
+#ifndef TEST
+ if ( ot == OCTYPE_PTT && ! save_audio_config_p->achan[chan].fulldup) {
+ demod_mute_input (chan, ptt_signal);
+ }
+#endif
+
/*
* The data link state machine has an interest in activity on the radio channel.
* This is a very convenient place to get that information.
@@ -1269,6 +1384,18 @@ void ptt_set (int ot, int chan, int ptt_signal)
close (fd);
}
+
+#if defined(USE_GPIOD)
+ if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_GPIOD) {
+ const char *chip = save_audio_config_p->achan[chan].octrl[ot].out_gpio_name;
+ int line = save_audio_config_p->achan[chan].octrl[ot].out_gpio_num;
+ int rc = gpiod_ctxless_set_value(chip, line, ptt, false, "direwolf", NULL, NULL);
+ if (ptt_debug_level >= 1) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf("PTT_METHOD_GPIOD chip: %s line: %d ptt: %d rc: %d\n", chip, line, ptt, rc);
+ }
+ }
+#endif /* USE_GPIOD */
#endif
/*
diff --git a/src/recv.c b/src/recv.c
index 9873bbd6..49040e55 100644
--- a/src/recv.c
+++ b/src/recv.c
@@ -207,7 +207,7 @@ static void * recv_adev_thread (void *arg)
int eof;
/* This audio device can have one (mono) or two (stereo) channels. */
- /* Find number of the first channel. */
+ /* Find number of the first channel and number of channels. */
int first_chan = ADEVFIRSTCHAN(a);
int num_chan = save_pa->adev[a].num_channels;
@@ -234,6 +234,8 @@ static void * recv_adev_thread (void *arg)
if (audio_sample >= 256 * 256)
eof = 1;
+ // Future? provide more flexible mapping.
+ // i.e. for each valid channel where audio_source[] is first_chan+c.
multi_modem_process_sample(first_chan + c, audio_sample);
@@ -262,14 +264,14 @@ static void * recv_adev_thread (void *arg)
aprs_tt_button (first_chan + c, tt);
}
}
- }
+ } // for c is just 0 or 0 then 1
/* When a complete frame is accumulated, */
/* dlq_rec_frame, is called. */
/* recv_process, below, drains the queue. */
- }
+ } // while !eof on audio stream
// What should we do now?
// Seimply terminate the application?
@@ -337,7 +339,7 @@ void recv_process (void)
* - Digipeater.
*/
- app_process_rec_packet (pitem->chan, pitem->subchan, pitem->slice, pitem->pp, pitem->alevel, pitem->is_fx25, pitem->retries, pitem->spectrum);
+ app_process_rec_packet (pitem->chan, pitem->subchan, pitem->slice, pitem->pp, pitem->alevel, pitem->fec_type, pitem->retries, pitem->spectrum);
/*
diff --git a/src/rrbb.c b/src/rrbb.c
index 2047d69f..e787dae5 100644
--- a/src/rrbb.c
+++ b/src/rrbb.c
@@ -51,8 +51,8 @@
#define MAGIC2 0x56788765
-static int new_count = 0;
-static int delete_count = 0;
+volatile static int new_count = 0;
+volatile static int delete_count = 0;
/***********************************************************************************
@@ -425,6 +425,50 @@ alevel_t rrbb_get_audio_level (rrbb_t b)
+/***********************************************************************************
+ *
+ * Name: rrbb_set_speed_error
+ *
+ * Purpose: Set speed error of the received frame.
+ *
+ * Inputs: b Handle for bit array.
+ * speed_error In percentage.
+ *
+ ***********************************************************************************/
+
+void rrbb_set_speed_error (rrbb_t b, float speed_error)
+{
+ assert (b != NULL);
+ assert (b->magic1 == MAGIC1);
+ assert (b->magic2 == MAGIC2);
+
+ b->speed_error = speed_error;
+}
+
+
+/***********************************************************************************
+ *
+ * Name: rrbb_get_speed_error
+ *
+ * Purpose: Get speed error of the received frame.
+ *
+ * Inputs: b Handle for bit array.
+ *
+ * Returns: speed error in percentage.
+ *
+ ***********************************************************************************/
+
+float rrbb_get_speed_error (rrbb_t b)
+{
+ assert (b != NULL);
+ assert (b->magic1 == MAGIC1);
+ assert (b->magic2 == MAGIC2);
+
+ return (b->speed_error);
+}
+
+
+
/***********************************************************************************
*
* Name: rrbb_get_is_scrambled
@@ -488,6 +532,7 @@ int rrbb_get_prev_descram (rrbb_t b)
}
+
/* end rrbb.c */
diff --git a/src/rrbb.h b/src/rrbb.h
index 4b283726..894a448f 100644
--- a/src/rrbb.h
+++ b/src/rrbb.h
@@ -33,6 +33,7 @@ typedef struct rrbb_s {
int slice; /* Which slicer. */
alevel_t alevel; /* Received audio level at time of frame capture. */
+ float speed_error; /* Received data speed error as percentage. */
unsigned int len; /* Current number of samples in array. */
int is_scrambled; /* Is data scrambled G3RUH / K9NG style? */
@@ -84,6 +85,9 @@ int rrbb_get_slice (rrbb_t b);
void rrbb_set_audio_level (rrbb_t b, alevel_t alevel);
alevel_t rrbb_get_audio_level (rrbb_t b);
+void rrbb_set_speed_error (rrbb_t b, float speed_error);
+float rrbb_get_speed_error (rrbb_t b);
+
int rrbb_get_is_scrambled (rrbb_t b);
int rrbb_get_descram_state (rrbb_t b);
int rrbb_get_prev_descram (rrbb_t b);
diff --git a/src/server.c b/src/server.c
index da20d0df..866b58ae 100644
--- a/src/server.c
+++ b/src/server.c
@@ -820,7 +820,7 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl
/* Stick in extra byte for the "TNC" to use. */
- agwpe_msg.data[0] = 0;
+ agwpe_msg.data[0] = chan << 4; // Was 0. Fixed in 1.8.
memcpy (agwpe_msg.data + 1, fbuf, (size_t)flen);
if (debug_client) {
@@ -1559,6 +1559,7 @@ static THREAD_F cmd_listen_thread (void *arg)
case MEDIUM_RADIO:
{
+ // Misleading if using stdin or udp.
char stemp[100];
int a = ACHAN2ADEV(j);
// If I was really ambitious, some description could be provided.
@@ -1593,12 +1594,7 @@ static THREAD_F cmd_listen_thread (void *arg)
break;
default:
- {
- // could elaborate with hostname, etc.
- char stemp[100];
- snprintf (stemp, sizeof(stemp), "Port%d INVALID CHANNEL;", j+1);
- strlcat (reply.info, stemp, sizeof(reply.info));
- }
+ ; // Only list valid channels.
break;
} // switch
@@ -1784,11 +1780,17 @@ static THREAD_F cmd_listen_thread (void *arg)
// 00=Port 1
// 16=Port 2
//
- // I don't know what that means; we already a port number in the header.
+ // The seems to be redundant; we already a port number in the header.
// Anyhow, the original code here added one to cmd.data to get the
// first byte of the frame. Unfortunately, it did not subtract one from
// cmd.hdr.data_len so we ended up sending an extra byte.
+ // TODO: Right now I just use the port (channel) number in the header.
+ // What if the second one is inconsistent?
+ // - Continue to ignore port number at beginning of data?
+ // - Use second one instead?
+ // - Error message if a mismatch?
+
memset (&alevel, 0xff, sizeof(alevel));
pp = ax25_from_frame ((unsigned char *)cmd.data+1, data_len - 1, alevel);
@@ -1816,6 +1818,11 @@ static THREAD_F cmd_listen_thread (void *arg)
break;
+ case 'P': /* Application Login */
+
+ // Silently ignore it.
+ break;
+
case 'X': /* Register CallSign */
{
@@ -1937,8 +1944,10 @@ static THREAD_F cmd_listen_thread (void *arg)
case 'D': /* Send Connected Data */
{
- char callsigns[2][AX25_MAX_ADDR_LEN];
- const int num_calls = 2;
+ char callsigns[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
+ memset (callsigns, 0, sizeof(callsigns));
+ const int num_calls = 2; // only first 2 used. Digipeater path
+ // must be remembered from connect request.
strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE]));
strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE]));
@@ -1951,8 +1960,9 @@ static THREAD_F cmd_listen_thread (void *arg)
case 'd': /* Disconnect, Terminate an AX.25 Connection */
{
- char callsigns[2][AX25_MAX_ADDR_LEN];
- const int num_calls = 2;
+ char callsigns[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
+ memset (callsigns, 0, sizeof(callsigns));
+ const int num_calls = 2; // only first 2 used.
strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE]));
strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE]));
@@ -2102,15 +2112,14 @@ static THREAD_F cmd_listen_thread (void *arg)
{
- char callsigns[2][AX25_MAX_ADDR_LEN];
- const int num_calls = 2;
+ char callsigns[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
+ memset (callsigns, 0, sizeof(callsigns));
+ const int num_calls = 2; // only first 2 used.
strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE]));
strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE]));
- // Issue 169. Proper implementation for 'Y'.
dlq_outstanding_frames_request (callsigns, num_calls, cmd.hdr.portx, client);
-
}
break;
diff --git a/src/tt_user.c b/src/tt_user.c
index a73d6a46..edbc67d0 100644
--- a/src/tt_user.c
+++ b/src/tt_user.c
@@ -847,7 +847,7 @@ static void xmit_object_report (int i, int first_time)
0,0,0,NULL, G_UNKNOWN, G_UNKNOWN, /* PHGD, Course/Speed */
strlen(tt_user[i].freq) > 0 ? atof(tt_user[i].freq) : G_UNKNOWN,
strlen(tt_user[i].ctcss) > 0 ? atof(tt_user[i].ctcss) : G_UNKNOWN,
- G_UNKNOWN, /* CTCSS */
+ 'T', G_UNKNOWN, /* CTCSS */
info_comment, object_info, sizeof(object_info));
strlcat (stemp, object_info, sizeof(stemp));
diff --git a/src/walk96.c b/src/walk96.c
index 9fc791f8..6438fbc1 100644
--- a/src/walk96.c
+++ b/src/walk96.c
@@ -156,7 +156,7 @@ static void walk96 (int fix, double lat, double lon, float knots, float course,
'/', '=',
G_UNKNOWN, G_UNKNOWN, G_UNKNOWN, "", // PHGd
(int)roundf(course), (int)roundf(knots),
- 445.925, 0, 0,
+ 445.925, 'T', 0, 0,
comment,
info, sizeof(info));
diff --git a/systemd/direwolf.service b/systemd/direwolf.service
index d4109a04..c3380fac 100644
--- a/systemd/direwolf.service
+++ b/systemd/direwolf.service
@@ -1,6 +1,7 @@
[Unit]
Description=Direwolf Sound Card-based AX.25 TNC
After=sound.target
+After=network.target
[Service]
EnvironmentFile=/etc/sysconfig/direwolf
@@ -22,3 +23,5 @@ ReadWritePaths=/var/log/direwolf
[Install]
WantedBy=multi-user.target
DefaultInstance=1
+
+# alternate version: https://www.f4fxl.org/start-direwolf-at-boot-the-systemd-way/
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index a95cbaf1..da732ac8 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -26,6 +26,7 @@ set(TEST_CHECK-MODEM2400-a_FILE "check-modem2400-a")
set(TEST_CHECK-MODEM2400-b_FILE "check-modem2400-b")
set(TEST_CHECK-MODEM2400-g_FILE "check-modem2400-g")
set(TEST_CHECK-MODEM4800_FILE "check-modem4800")
+set(TEST_CHECK-MODEMEAS_FILE "check-modemeas")
# generate the scripts that run the tests
@@ -101,6 +102,12 @@ configure_file(
@ONLY
)
+configure_file(
+ "${CUSTOM_TEST_SCRIPTS_DIR}/${TEST_CHECK-MODEMEAS_FILE}"
+ "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEMEAS_FILE}${CUSTOM_SCRIPT_SUFFIX}"
+ @ONLY
+ )
+
# global includes
# not ideal but not so slow
@@ -140,6 +147,7 @@ list(APPEND dtest_SOURCES
${CUSTOM_SRC_DIR}/tq.c
${CUSTOM_SRC_DIR}/textcolor.c
${CUSTOM_SRC_DIR}/decode_aprs.c
+ ${CUSTOM_SRC_DIR}/deviceid.c
${CUSTOM_SRC_DIR}/dwgpsnmea.c
${CUSTOM_SRC_DIR}/dwgps.c
${CUSTOM_SRC_DIR}/dwgpsd.c
@@ -225,6 +233,7 @@ list(APPEND pftest_SOURCES
${CUSTOM_SRC_DIR}/textcolor.c
${CUSTOM_SRC_DIR}/fcs_calc.c
${CUSTOM_SRC_DIR}/decode_aprs.c
+ ${CUSTOM_SRC_DIR}/deviceid.c
${CUSTOM_SRC_DIR}/dwgpsnmea.c
${CUSTOM_SRC_DIR}/dwgps.c
${CUSTOM_SRC_DIR}/dwgpsd.c
@@ -498,6 +507,7 @@ add_test(check-modem2400-a "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM2400-a_F
add_test(check-modem2400-b "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM2400-b_FILE}${CUSTOM_SCRIPT_SUFFIX}")
add_test(check-modem2400-g "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM2400-g_FILE}${CUSTOM_SCRIPT_SUFFIX}")
add_test(check-modem4800 "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM4800_FILE}${CUSTOM_SCRIPT_SUFFIX}")
+add_test(check-modemeas "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEMEAS_FILE}${CUSTOM_SCRIPT_SUFFIX}")
@@ -514,6 +524,7 @@ if(OPTIONAL_TEST)
${CUSTOM_SRC_DIR}/pfilter.c
${CUSTOM_SRC_DIR}/telemetry.c
${CUSTOM_SRC_DIR}/decode_aprs.c
+ ${CUSTOM_SRC_DIR}/deviceid.c.c
${CUSTOM_SRC_DIR}/dwgpsnmea.c
${CUSTOM_SRC_DIR}/dwgps.c
${CUSTOM_SRC_DIR}/dwgpsd.c
@@ -566,6 +577,7 @@ if(OPTIONAL_TEST)
${CUSTOM_SRC_DIR}/fcs_calc.c
${CUSTOM_SRC_DIR}/ax25_pad.c
${CUSTOM_SRC_DIR}/decode_aprs.c
+ ${CUSTOM_SRC_DIR}/deviceid.c
${CUSTOM_SRC_DIR}/dwgpsnmea.c
${CUSTOM_SRC_DIR}/dwgps.c
${CUSTOM_SRC_DIR}/dwgpsd.c
diff --git a/test/scripts/check-modemeas b/test/scripts/check-modemeas
new file mode 100755
index 00000000..c2a88539
--- /dev/null
+++ b/test/scripts/check-modemeas
@@ -0,0 +1,4 @@
+@CUSTOM_SHELL_SHABANG@
+
+@GEN_PACKETS_BIN@ -B EAS -o testeas.wav
+@ATEST_BIN@ -B EAS -L 6 -G 6 testeas.wav