Merge branch 'wb2osz:dev' into dev

This commit is contained in:
dvanderlocht 2023-12-09 16:03:01 +01:00 committed by GitHub
commit 16cd23a8db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
84 changed files with 2815 additions and 845 deletions

162
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,162 @@
name: 'build direwolf'
on:
# permit to manually trigger the CI
workflow_dispatch:
inputs:
cmake_flags:
description: 'Custom CMAKE flags'
required: false
push:
paths-ignore:
- '.github/**'
pull_request:
paths-ignore:
- '.github/**'
jobs:
build:
name: ${{ matrix.config.name }}
runs-on: ${{ matrix.config.os }}
strategy:
fail-fast: false
matrix:
config:
- {
name: 'Windows Latest MinGW 64bit',
os: windows-latest,
cc: 'x86_64-w64-mingw32-gcc',
cxx: 'x86_64-w64-mingw32-g++',
ar: 'x86_64-w64-mingw32-ar',
windres: 'x86_64-w64-mingw32-windres',
arch: 'x86_64',
build_type: 'Release',
cmake_extra_flags: '-G "MinGW Makefiles"'
}
- {
name: 'Windows 2019 MinGW 32bit',
os: windows-2019,
cc: 'i686-w64-mingw32-gcc',
cxx: 'i686-w64-mingw32-g++',
ar: 'i686-w64-mingw32-ar',
windres: 'i686-w64-mingw32-windres',
arch: 'i686',
build_type: 'Release',
cmake_extra_flags: '-G "MinGW Makefiles"'
}
- {
name: 'macOS latest',
os: macos-latest,
cc: 'clang',
cxx: 'clang++',
arch: 'x86_64',
build_type: 'Release',
cmake_extra_flags: ''
}
- {
name: 'Ubuntu latest Debug',
os: ubuntu-latest,
cc: 'gcc',
cxx: 'g++',
arch: 'x86_64',
build_type: 'Debug',
cmake_extra_flags: ''
}
- {
name: 'Ubuntu 22.04',
os: ubuntu-22.04,
cc: 'gcc',
cxx: 'g++',
arch: 'x86_64',
build_type: 'Release',
cmake_extra_flags: ''
}
- {
name: 'Ubuntu 20.04',
os: ubuntu-20.04,
cc: 'gcc',
cxx: 'g++',
arch: 'x86_64',
build_type: 'Release',
cmake_extra_flags: ''
}
steps:
- name: checkout
uses: actions/checkout@v2
with:
fetch-depth: 8
- name: dependency
shell: bash
run: |
# this is not perfect but enought for now
if [ "$RUNNER_OS" == "Linux" ]; then
sudo apt-get update
sudo apt-get install libasound2-dev libudev-dev libhamlib-dev gpsd
elif [ "$RUNNER_OS" == "macOS" ]; then
# just to simplify I use homebrew but
# we can use macports (latest direwolf is already available as port)
brew install portaudio hamlib gpsd
elif [ "$RUNNER_OS" == "Windows" ]; then
# add the folder to PATH
echo "C:\msys64\mingw32\bin" >> $GITHUB_PATH
fi
- name: create build environment
run: |
cmake -E make_directory ${{github.workspace}}/build
- name: configure
shell: bash
working-directory: ${{github.workspace}}/build
run: |
if [ "$RUNNER_OS" == "Windows" ]; then
export CC=${{ matrix.config.cc }}
export CXX=${{ matrix.config.cxx }}
export AR=${{ matrix.config.ar }}
export WINDRES=${{ matrix.config.windres }}
fi
cmake $GITHUB_WORKSPACE \
-DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \
-DCMAKE_C_COMPILER=${{ matrix.config.cc }} \
-DCMAKE_CXX_COMPILER=${{ matrix.config.cxx }} \
-DCMAKE_CXX_FLAGS="-Werror" -DUNITTEST=1 \
${{ matrix.config.cmake_extra_flags }} \
${{ github.event.inputs.cmake_flags }}
- name: build
shell: bash
working-directory: ${{github.workspace}}/build
run: |
if [ "$RUNNER_OS" == "Windows" ]; then
export CC=${{ matrix.config.cc }}
export CXX=${{ matrix.config.cxx }}
export AR=${{ matrix.config.ar }}
export WINDRES=${{ matrix.config.windres }}
fi
cmake --build . --config ${{ matrix.config.build_type }} \
${{ github.event.inputs.cmake_flags }}
- name: test
continue-on-error: true
shell: bash
working-directory: ${{github.workspace}}/build
run: |
ctest -C ${{ matrix.config.build_type }} \
--parallel 2 --output-on-failure \
${{ github.event.inputs.cmake_flags }}
- name: package
shell: bash
working-directory: ${{github.workspace}}/build
run: |
if [ "$RUNNER_OS" == "Windows" ] || [ "$RUNNER_OS" == "macOS" ]; then
make package
fi
- name: archive binary
uses: actions/upload-artifact@v2
with:
name: direwolf_${{ matrix.config.os }}_${{ matrix.config.arch }}_${{ github.sha }}
path: |
${{github.workspace}}/build/direwolf-*.zip
${{github.workspace}}/build/direwolf.conf
${{github.workspace}}/build/src/*
${{github.workspace}}/build/CMakeCache.txt
!${{github.workspace}}/build/src/cmake_install.cmake
!${{github.workspace}}/build/src/CMakeFiles
!${{github.workspace}}/build/src/Makefile

73
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -0,0 +1,73 @@
# 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"
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: [ 'cpp', '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@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# 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@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
- run: |
mkdir build
cd build
cmake -DUNITTEST=1 ..
make
make test
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

2
.gitignore vendored
View File

@ -109,5 +109,5 @@ $RECYCLE.BIN/
*.dSYM *.dSYM
# cmake # cmake
build/ build*/
tmp/ tmp/

View File

@ -2,14 +2,39 @@
# Revision History # # Revision History #
## Version 1.7 -- Under Development ('dev' branch) ## ## 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: ### ### New Features: ###
- 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.
- Limited support for CM109/CM119 GPIO PTT on Windows.
- 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). 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):
> 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) - 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)
@ -17,13 +42,28 @@
- The BEACON configuration now recognizes the SOURCE= option. This replaces the AX.25 source address rather than using the MYCALL value for the channel. This is useful for sending more than 5 analog telemetry channels. Use two, or more, source addresses with up to 5 analog channels each. - The BEACON configuration now recognizes the SOURCE= option. This replaces the AX.25 source address rather than using the MYCALL value for the channel. This is useful for sending more than 5 analog telemetry channels. Use two, or more, source addresses with up to 5 analog channels each.
- For more flexibility, the FX.25 transmit property can now be set individually by channel, rather than having a global setting for all channels. The -X on the command line applies only to channel 0. For other channels you need to add a new line to the configuration file. - For more flexibility, the FX.25 transmit property can now be set individually by channel, rather than having a global setting for all channels. The -X on the command line applies only to channel 0. For other channels you need to add a new line to the configuration file. You can specify a specific number of parity bytes (16, 32, 64) or 1 to choose automatically based on packet size.
> After: "CHANNEL 1" (or other channel) > After: "CHANNEL 1" (or other channel)
> >
> Add: "FX25TX 1" (or 16 or 32 or 64) > 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 ## ## Version 1.6 -- October 2020 ##
### New Build Procedure: ### ### New Build Procedure: ###

View File

@ -4,7 +4,7 @@ project(direwolf)
# configure version # configure version
set(direwolf_VERSION_MAJOR "1") set(direwolf_VERSION_MAJOR "1")
set(direwolf_VERSION_MINOR "7") set(direwolf_VERSION_MINOR "8")
set(direwolf_VERSION_PATCH "0") set(direwolf_VERSION_PATCH "0")
set(direwolf_VERSION_SUFFIX "Development") set(direwolf_VERSION_SUFFIX "Development")
@ -167,15 +167,16 @@ elseif(APPLE)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_MACOS_DNSSD") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_MACOS_DNSSD")
elseif (WIN32) elseif (WIN32)
if(NOT VS2015 AND NOT VS2017) if(C_MSVC)
message(FATAL_ERROR "You must use Microsoft Visual Studio 2015 or 2017 as compiler") if (NOT VS2015 AND NOT VS2017 AND NOT VS2019)
message(FATAL_ERROR "You must use Microsoft Visual Studio 2015, 2017 or 2019 as compiler")
else()
# compile with full multicore
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
set(CUSTOM_SHELL_BIN "")
endif()
endif() endif()
# compile with full multicore
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
set(CUSTOM_SHELL_BIN "")
endif() endif()
if (C_CLANG OR C_GCC) if (C_CLANG OR C_GCC)
@ -264,9 +265,33 @@ endif(WIN32 OR CYGWIN)
# requirements # requirements
include(CheckSymbolExists) include(CheckSymbolExists)
# Some platforms provide their own strlcpy & strlcat. (BSD, MacOSX) # Some platforms provide their own strlcpy & strlcat. (BSD, MacOSX)
# Others don't so we provide our own. (Most, but not all Linux) # Others don't so we provide our own. (Windows, most, but not all Linux)
# Define the preprocessor macro so libgps does not supply its own version. # 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) check_symbol_exists(strlcpy string.h HAVE_STRLCPY)
if(HAVE_STRLCPY) if(HAVE_STRLCPY)
add_compile_options(-DHAVE_STRLCPY) add_compile_options(-DHAVE_STRLCPY)
@ -295,6 +320,14 @@ else()
set(HAMLIB_LIBRARIES "") set(HAMLIB_LIBRARIES "")
endif() 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) if(LINUX)
find_package(ALSA REQUIRED) find_package(ALSA REQUIRED)
if(ALSA_FOUND) if(ALSA_FOUND)

View File

@ -9,11 +9,11 @@ Why waste $200 and settle for mediocre receive performance from a 1980's technol
![](tnc-test-cd-results.png) ![](tnc-test-cd-results.png)
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. 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.
![](fx25.png) ![](fx25.png)
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.
@ -80,7 +80,21 @@ It can also be used as a virtual TNC for other applications such as [APRSIS32](h
- **Standard 300, 1200 & 9600 bps modems and more.** - **Modems:**
300 bps AFSK for HF
1200 bps AFSK most common for VHF/UHF
2400 & 4800 bps PSK
9600 bps GMSK/G3RUH
AIS reception
EAS SAME reception
- **DTMF ("Touch Tone") Decoding and Encoding.** - **DTMF ("Touch Tone") Decoding and Encoding.**
@ -100,12 +114,16 @@ It can also be used as a virtual TNC for other applications such as [APRSIS32](h
## Documentation ## ## Documentation ##
[Stable Version](https://github.com/wb2osz/direwolf/tree/master/doc) [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)
[Power Point presentation](https://github.com/wb2osz/direwolf-presentation) -- Why not give a talk at a local club meeting? [Additional Topics](https://github.com/wb2osz/direwolf-doc)
[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 ## ## Installation ##

View File

@ -9,7 +9,7 @@ if(AVAHI_CLIENT_LIBRARY)
set(AVAHI_CLIENT_FOUND TRUE) set(AVAHI_CLIENT_FOUND TRUE)
endif() endif()
FIND_PACKAGE_HANDLE_STANDARD_ARGS(AVAHI DEFAULT_MSG AVAHI_COMMON_FOUND AVAHI_CLIENT_FOUND) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Avahi DEFAULT_MSG AVAHI_COMMON_FOUND AVAHI_CLIENT_FOUND)
if (AVAHI_FOUND) if (AVAHI_FOUND)
set(AVAHI_INCLUDE_DIRS ${AVAHI_UI_INCLUDE_DIR}) set(AVAHI_INCLUDE_DIRS ${AVAHI_UI_INCLUDE_DIR})

View File

@ -5,7 +5,9 @@ elseif(NOT DEFINED C_GCC AND CMAKE_CXX_COMPILER_ID MATCHES "GNU")
set(C_GCC 1) set(C_GCC 1)
elseif(NOT DEFINED C_MSVC AND CMAKE_CXX_COMPILER_ID MATCHES "MSVC") elseif(NOT DEFINED C_MSVC AND CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
set(C_MSVC 1) set(C_MSVC 1)
if(MSVC_VERSION GREATER 1910 AND MSVC_VERSION LESS 1919) if(MSVC_VERSION GREATER 1919 AND MSVC_VERSION LESS 1926)
set(VS2019 ON)
elseif(MSVC_VERSION GREATER 1910 AND MSVC_VERSION LESS 1919)
set(VS2017 ON) set(VS2017 ON)
elseif(MSVC_VERSION GREATER 1899 AND MSVC_VERSION LESS 1910) elseif(MSVC_VERSION GREATER 1899 AND MSVC_VERSION LESS 1910)
set(VS2015 ON) set(VS2015 ON)

View File

@ -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})

View File

@ -52,7 +52,7 @@ find_library(HAMLIB_LIBRARY
) )
include(FindPackageHandleStandardArgs) include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(HAMLIB find_package_handle_standard_args(hamlib
DEFAULT_MSG DEFAULT_MSG
HAMLIB_LIBRARY HAMLIB_LIBRARY
HAMLIB_INCLUDE_DIR HAMLIB_INCLUDE_DIR

View File

@ -65,7 +65,7 @@ find_path(UDEV_INCLUDE_DIR
) )
include(FindPackageHandleStandardArgs) include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(UDEV find_package_handle_standard_args(udev
DEFAULT_MSG DEFAULT_MSG
UDEV_LIBRARY UDEV_LIBRARY
UDEV_INCLUDE_DIR UDEV_INCLUDE_DIR

View File

@ -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. # 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"

View File

@ -29,16 +29,21 @@
%C%# Extensive documentation can be found here: %C%# Extensive documentation can be found here:
%C%# Stable release - https://github.com/wb2osz/direwolf/tree/master/doc %C%# Stable release - https://github.com/wb2osz/direwolf/tree/master/doc
%C%# Latest development - https://github.com/wb2osz/direwolf/tree/dev/doc %C%# Latest development - https://github.com/wb2osz/direwolf/tree/dev/doc
%C%# Additional topics - https://github.com/wb2osz/direwolf-doc
%C%# %C%#
%W%# The complete documentation set can also be found in the doc folder. %W%# The basic documentation set can also be found in the doc folder.
%L%# The complete documentation set can also be found in %L%# The basic documentation set can also be found in
%L%# /usr/local/share/doc/direwolf/ or /usr/share/doc/direwolf/ %L%# /usr/local/share/doc/direwolf/ or /usr/share/doc/direwolf/
%L%# Concise "man" pages are also available for Linux. %L%# Concise "man" pages are also available for Linux.
%M%# /usr/local/share/doc/direwolf/ or /usr/share/doc/direwolf/ %M%# /usr/local/share/doc/direwolf/ or /usr/share/doc/direwolf/
%M%# Concise "man" pages are also available for Mac OSX. %M%# Concise "man" pages are also available for Mac OSX.
%C%# %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%# 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%#
%C%# These are the most likely settings you might change: %C%# These are the most likely settings you might change:
%C%# %C%#
@ -93,6 +98,11 @@
%C%# Many people will simply use the default sound device. %C%# Many people will simply use the default sound device.
%C%# Some might want to use an alternative device by choosing it here. %C%# Some might want to use an alternative device by choosing it here.
%C%# %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% ---------- Windows ----------
%R% %R%
%W%# When the Windows version starts up, it displays something like %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%# It can be up to 6 letters and digits with an optional ssid.
%C%# The APRS specification requires that it be upper case. %C%# The APRS specification requires that it be upper case.
%C%# %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% %C%
%C%MYCALL N0CALL %C%MYCALL N0CALL
%C% %C%
%C%# %C%#
%C%# Pick a suitable modem speed based on your situation. %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%# 2400 QPSK compatible with MFJ-2400, and probably PK232-2400 & KPC-2400.
%C%# 300 Low speed for HF SSB. Default tones 1600 & 1800. %C%# 300 Low speed for HF SSB. Default tones 1600 & 1800.
%C%# EAS Emergency Alert System (EAS) Specific Area Message Encoding (SAME). %C%# EAS Emergency Alert System (EAS) Specific Area Message Encoding (SAME).
@ -263,6 +273,10 @@
%C% %C%
%C%#DTMF %C%#DTMF
%C% %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%# 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%# 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. %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
%C%#PTT COM1 RTS -DTR %C%#PTT COM1 RTS -DTR
%L%#PTT /dev/ttyUSB0 RTS %L%#PTT /dev/ttyUSB0 RTS
%L%#PTT /dev/ttyUSB0 RTS -DTR
%C% %C%
%L%# %L%#
%L%# On Linux, you can also use general purpose I/O pins if %L%# On Linux, you can also use general purpose I/O pins if
@ -301,7 +316,7 @@
%L% %L%
%L%#PTT GPIO 25 %L%#PTT GPIO 25
%L% %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%# as the PTT signal. This could be used to light up an LED like a normal TNC.
%C% %C%
%C%#DCD COM1 -DTR %C%#DCD COM1 -DTR
@ -369,11 +384,11 @@
%W% %W%
%C%# %C%#
%C%# It is sometimes possible to recover frames with a bad FCS. %C%# It is sometimes possible to recover frames with a bad FCS.
%C%# This applies to all channels. %C%# This is not a global setting.
%C%# It applies only the the most recent CHANNEL specified.
%C%# %C%#
%C%# 0 [NONE] - Don't try to repair. %C%# 0 - Don't try to repair. (default)
%C%# 1 [SINGLE] - Attempt to fix single bit error. (default) %C%# 1 - Attempt to fix single bit error.
%C%# ... see User Guide for more values and in-depth discussion.
%C%# %C%#
%C% %C%
%C%#FIX_BITS 0 %C%#FIX_BITS 0
@ -398,15 +413,21 @@
%C%# Example: %C%# Example:
%C%# %C%#
%C%# This results in a broadcast once every 10 minutes. %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%# The others are kept local.
%C%# %C%#
%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=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%#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%#
%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%# With UTM coordinates instead of latitude and longitude.
%C% %C%
%C%#PBEACON delay=1 every=10 overlay=S symbol="digi" zone=19T easting=307477 northing=4720178 %C%#PBEACON delay=1 every=10 overlay=S symbol="digi" zone=19T easting=307477 northing=4720178
@ -442,17 +463,11 @@
%C%# the "#" from the beginning of the line below. %C%# the "#" from the beginning of the line below.
%C%# %C%#
%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%
%C%# See User Guide for more explanation of what this means and how %C%# See User Guide and "APRS-Digipeaters.pdf" for more explanation of what
%C%# it can be customized for your particular needs. %C%# this means and how it can be customized for your particular needs.
%C% %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%
%C%# Traditional connected mode packet radio uses a different %C%# Traditional connected mode packet radio uses a different
%C%# type of digipeating. See User Guide for details. %C%# type of digipeating. See User Guide for details.
@ -486,6 +501,7 @@
%C%# without sending it over the air and relying on someone else to %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%# 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%# 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%
%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=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 %C%#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=T lat=42^37.14N long=071^20.83W
@ -494,13 +510,14 @@
%C%# To relay messages from the Internet to radio, you need to add %C%# To relay messages from the Internet to radio, you need to add
%C%# one more option with the transmit channel number and a VIA path. %C%# one more option with the transmit channel number and a VIA path.
%C% %C%
%C%#IGTXVIA 0 WIDE1-1 %C%#IGTXVIA 0 WIDE1-1,WIDE2-1
%C% %C%
%C% %C%
%C%# Finally, we don't want to flood the radio channel. %C%# Finally, we don't want to flood the radio channel.
%C%# The IGate function will limit the number of packets transmitted %C%# The IGate function will limit the number of packets transmitted
%C%# during 1 minute and 5 minute intervals. If a limit would %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%# 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%
%C%IGTXLIMIT 6 10 %C%IGTXLIMIT 6 10
%C% %C%
@ -517,82 +534,4 @@
%C%# %C%#
%C%# See separate "APRStt-Implementation-Notes" document for details. %C%# See separate "APRStt-Implementation-Notes" document for details.
%C%# %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%

View File

@ -1,4 +1,19 @@
# #
# 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. # The destination field is often used to identify the manufacturer/model.
# These are not hardcoded into Dire Wolf. Instead they are read from # 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.txt at application start up time.
@ -6,24 +21,13 @@
# The original permanent symbols are built in but the "new" symbols, # The original permanent symbols are built in but the "new" symbols,
# using overlays, are often updated. These are also read from files. # 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) include(ExternalProject)
set(TOCALLS_TXT "tocalls.txt") set(TOCALLS_TXT "tocalls.txt")
set(TOCALLS_TXT_BKP "tocalls.txt.old")
set(TOCALLS_URL "http://www.aprs.org/aprs11/tocalls.txt")
set(SYMBOLS-NEW_TXT "symbols-new.txt") 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 "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") set(CUSTOM_BINARY_DATA_DIR "${CMAKE_BINARY_DIR}/data")
# we can also move to a separate cmake file and use file(download) # we can also move to a separate cmake file and use file(download)
@ -32,63 +36,6 @@ file(COPY "${CUSTOM_DATA_DIR}/${TOCALLS_TXT}" DESTINATION "${CUSTOM_BINARY_DATA_
file(COPY "${CUSTOM_DATA_DIR}/${SYMBOLS-NEW_TXT}" 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}") 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_TXT}" DESTINATION ${INSTALL_DATA_DIR})
install(FILES "${CUSTOM_BINARY_DATA_DIR}/${SYMBOLS-NEW_TXT}" 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}) install(FILES "${CUSTOM_BINARY_DATA_DIR}/${SYMBOLSX_TXT}" DESTINATION ${INSTALL_DATA_DIR})

View File

@ -4,6 +4,7 @@ APRS TO-CALL VERSION NUMBERS 14 Dec 2021
WB4APR WB4APR
</title> </title>
<version_notes> <version_notes>
07 Jun 23 Added APK005 for Kenwood TH-D75
14 Dec 21 Added APATAR ATA-R APRS Digipeater by TA7W/OH2UDS and TA6AEU 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 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 18 Sep 21 Added APCSS for AMSAT Cubesat Simulator https://cubesatsim.org
@ -156,6 +157,7 @@ yourselves and keep me informed.
APK APK0xx Kenwood TH-D7's APK APK0xx Kenwood TH-D7's
APK003 Kenwood TH-D72 APK003 Kenwood TH-D72
APK004 Kenwood TH-D74 APK004 Kenwood TH-D74
APK005 Kenwood TH-D75
APK1xx Kenwood D700's APK1xx Kenwood D700's
APK102 Kenwood D710 APK102 Kenwood D710
APKRAM KRAMstuff.com - Mark. G7LEU APKRAM KRAMstuff.com - Mark. G7LEU
@ -278,6 +280,7 @@ yourselves and keep me informed.
APZ247 for UPRS NR0Q APZ247 for UPRS NR0Q
APZ0xx Xastir (old versions. See APX) APZ0xx Xastir (old versions. See APX)
APZMAJ Martyn M1MAJ DeLorme inReach Tracker APZMAJ Martyn M1MAJ DeLorme inReach Tracker
APZMDM github/codec2_talkie - product code not registered
APZMDR for HaMDR trackers - hessu * hes.iki.fi] APZMDR for HaMDR trackers - hessu * hes.iki.fi]
APZPAD Smart Palm APZPAD Smart Palm
APZTKP TrackPoint, Nick N0LP (Balloon tracking)(depricated) APZTKP TrackPoint, Nick N0LP (Balloon tracking)(depricated)

View File

@ -1,4 +1,4 @@
# Documentation for Dire Wolf # # Documentation for Dire Wolf #
Click on the document name to view in your web browser or the link following to download the PDF file. 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. 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. 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? ## ## Questions? Experiences to share? ##
Here are some good places to ask questions and share your experiences: Here are some good places to ask questions and share your experiences:

Binary file not shown.

Binary file not shown.

View File

@ -32,9 +32,22 @@ if(LINUX)
) )
endif() endif()
add_library(misc STATIC # Add_library doesn't like to get an empty source file list.
${misc_SOURCES} # 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 elseif(WIN32 OR CYGWIN) # windows

View File

@ -208,7 +208,8 @@ typedef unsigned long int reg_syntax_t;
some interfaces). When a regexp is compiled, the syntax used is some interfaces). When a regexp is compiled, the syntax used is
stored in the pattern buffer, so changing this does not affect stored in the pattern buffer, so changing this does not affect
already-compiled regexps. */ 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. /* Define combinations of the above bits for the standard possibilities.
(The [[[ comments delimit what gets put into the Texinfo file, so (The [[[ comments delimit what gets put into the Texinfo file, so

View File

@ -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. 4800 bps uses 8PSK based on V.27 standard.
.P .P
9600 bps and up uses K9NG/G3RUH standard. 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
.RE .RE
.PD .PD

View File

@ -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. 4800 bps uses 8PSK based on V.27 standard.
.P .P
9600 bps and up uses K9NG/G3RUH standard. 9600 bps and up uses K9NG/G3RUH standard.
.P
EAS for Emergency Alert System (EAS) Specific Area Message Encoding (SAME).
.RE .RE
.RE .RE
.PD .PD
@ -100,9 +102,13 @@ Send output to .wav file.
8 bit audio rather than 16. 8 bit audio rather than 16.
.TP .TP
.B "-2" .BI "-2"
2 channels of audio rather than 1. 2 channels of audio rather than 1.
.TP
.BI "-v" "max[,incr]"
Variable speed with specified maximum error and optional increment.
.SH EXAMPLES .SH EXAMPLES
.P .P

View File

@ -10,6 +10,7 @@ include_directories(
${PORTAUDIO_INCLUDE_DIRS} ${PORTAUDIO_INCLUDE_DIRS}
${SNDIO_INCLUDE_DIRS} ${SNDIO_INCLUDE_DIRS}
${CUSTOM_GEOTRANZ_DIR} ${CUSTOM_GEOTRANZ_DIR}
${GPIOD_INCLUDE_DIRS}
${CUSTOM_HIDAPI_DIR} ${CUSTOM_HIDAPI_DIR}
) )
@ -155,6 +156,7 @@ target_link_libraries(direwolf
${ALSA_LIBRARIES} ${ALSA_LIBRARIES}
${UDEV_LIBRARIES} ${UDEV_LIBRARIES}
${PORTAUDIO_LIBRARIES} ${PORTAUDIO_LIBRARIES}
${GPIOD_LIBRARIES}
${SNDIO_LIBRARIES} ${SNDIO_LIBRARIES}
${AVAHI_LIBRARIES} ${AVAHI_LIBRARIES}
) )

View File

@ -365,7 +365,7 @@ static void * tnc_listen_thread (void *arg)
} }
/* /*
* Call to/from fields are 10 bytes but contents must not exceeed 9 characters. * Call to/from fields are 10 bytes but contents must not exceed 9 characters.
* It's not guaranteed that unused bytes will contain 0 so we * It's not guaranteed that unused bytes will contain 0 so we
* don't issue error message in this case. * don't issue error message in this case.
*/ */

View File

@ -2,7 +2,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // This file is part of Dire Wolf, an amateur radio packet TNC.
// //
// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2019, 2021 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 // 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 // it under the terms of the GNU General Public License as published by
@ -23,11 +23,11 @@
* *
* Name: atest.c * Name: atest.c
* *
* Purpose: Test fixture for the AFSK demodulator. * Purpose: Test fixture for the Dire Wolf demodulators.
* *
* Inputs: Takes audio from a .WAV file instead of the audio device. * Inputs: Takes audio from a .WAV file instead of the audio device.
* *
* Description: This can be used to test the AFSK demodulator under * Description: This can be used to test the demodulators under
* controlled and reproducible conditions for tweaking. * controlled and reproducible conditions for tweaking.
* *
* For example * For example
@ -68,6 +68,7 @@
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include <getopt.h> #include <getopt.h>
#include <ctype.h>
#define ATEST_C 1 #define ATEST_C 1
@ -108,7 +109,7 @@ struct wav_header { /* .WAV file header. */
/* 8 bit samples are unsigned bytes */ /* 8 bit samples are unsigned bytes */
/* in range of 0 .. 255. */ /* in range of 0 .. 255. */
/* 16 bit samples are signed short */ /* 16 bit samples are little endian signed short */
/* in range of -32768 .. +32767. */ /* in range of -32768 .. +32767. */
static struct { static struct {
@ -276,12 +277,12 @@ int main (int argc, char *argv[])
case 'B': /* -B for data Bit rate */ case 'B': /* -B for data Bit rate */
/* Also implies modem type based on speed. */ /* 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) { 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) { else if (strcasecmp(optarg, "EAS") == 0) {
B_opt = 23456; // See special case below. B_opt = 0xEA5EA5; // See special case below.
} }
else { else {
B_opt = atoi(optarg); B_opt = atoi(optarg);
@ -425,11 +426,6 @@ int main (int argc, char *argv[])
my_audio_config.achan[0].baud = B_opt; 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, */ /* 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. */ /* that need to be kept in sync. Maybe it could be a common function someday. */
@ -438,7 +434,6 @@ int main (int argc, char *argv[])
my_audio_config.achan[0].modem_type = MODEM_AFSK; my_audio_config.achan[0].modem_type = MODEM_AFSK;
my_audio_config.achan[0].mark_freq = 1615; my_audio_config.achan[0].mark_freq = 1615;
my_audio_config.achan[0].space_freq = 1785; 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 else if (my_audio_config.achan[0].baud < 600) { // e.g. HF SSB packet
my_audio_config.achan[0].modem_type = MODEM_AFSK; my_audio_config.achan[0].modem_type = MODEM_AFSK;
@ -446,13 +441,11 @@ int main (int argc, char *argv[])
my_audio_config.achan[0].space_freq = 1800; my_audio_config.achan[0].space_freq = 1800;
// Previously we had a "D" which was fine tuned for 300 bps. // 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". // 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 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].modem_type = MODEM_AFSK;
my_audio_config.achan[0].mark_freq = DEFAULT_MARK_FREQ; my_audio_config.achan[0].mark_freq = DEFAULT_MARK_FREQ;
my_audio_config.achan[0].space_freq = DEFAULT_SPACE_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) { else if (my_audio_config.achan[0].baud < 3600) {
my_audio_config.achan[0].modem_type = MODEM_QPSK; my_audio_config.achan[0].modem_type = MODEM_QPSK;
@ -466,14 +459,14 @@ int main (int argc, char *argv[])
my_audio_config.achan[0].space_freq = 0; my_audio_config.achan[0].space_freq = 0;
strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles)); 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].modem_type = MODEM_AIS;
my_audio_config.achan[0].baud = 9600; my_audio_config.achan[0].baud = 9600;
my_audio_config.achan[0].mark_freq = 0; my_audio_config.achan[0].mark_freq = 0;
my_audio_config.achan[0].space_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. 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].modem_type = MODEM_EAS;
my_audio_config.achan[0].baud = 521; // Actually 520.83 but we have an integer field here. 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. // Will make more precise in afsk demod init.
@ -488,6 +481,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. 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. * -g option means force g3RUH regardless of speed.
*/ */
@ -759,14 +758,14 @@ int audio_get (int a)
* This is called when we have a good frame. * 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]; char stemp[500];
unsigned char *pinfo; unsigned char *pinfo;
int info_len; int info_len;
int h; int h;
char heard[AX25_MAX_ADDR_LEN]; char heard[2 * AX25_MAX_ADDR_LEN + 20];
char alevel_text[AX25_ALEVEL_TO_TEXT_SIZE]; char alevel_text[AX25_ALEVEL_TO_TEXT_SIZE];
packets_decoded_one++; packets_decoded_one++;
@ -811,29 +810,48 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev
} }
ax25_alevel_to_text (alevel, alevel_text); ax25_alevel_to_text (alevel, alevel_text);
if (my_audio_config.achan[chan].fix_bits == RETRY_NONE && my_audio_config.achan[chan].passall == 0) { /* As suggested by KJ4ERJ, if we are receiving from */
dw_printf ("%s audio level = %s %s\n", heard, alevel_text, spectrum); /* WIDEn-0, it is quite likely (but not guaranteed), that */
/* we are actually hearing the preceding station in the path. */
if (h >= AX25_REPEATER_2 &&
strncmp(heard, "WIDE", 4) == 0 &&
isdigit(heard[4]) &&
heard[5] == '\0') {
char probably_really[AX25_MAX_ADDR_LEN];
ax25_get_addr_with_ssid(pp, h-1, probably_really);
strlcat (heard, " (probably ", sizeof(heard));
strlcat (heard, probably_really, sizeof(heard));
strlcat (heard, ")", sizeof(heard));
} }
else if (is_fx25) {
dw_printf ("%s audio level = %s %s\n", heard, alevel_text, spectrum); switch (fec_type) {
}
else { case fec_type_fx25:
assert (retries >= RETRY_NONE && retries <= RETRY_MAX); dw_printf ("%s audio level = %s FX.25 %s\n", heard, alevel_text, spectrum);
dw_printf ("%s audio level = %s [%s] %s\n", heard, alevel_text, retry_text[(int)retries], 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 #endif
//#if defined(EXPERIMENT_G) || defined(EXPERIMENT_H)
// int j;
//
// for (j=0; j<MAX_SUBCHANS; j++) {
// if (spectrum[j] == '|') {
// count[j]++;
// }
// }
//#endif
// Display non-APRS packets in a different color. // Display non-APRS packets in a different color.
@ -878,7 +896,7 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev
#if 1 // temp experiment TODO: remove this. #if 0 // temp experiment
#include "decode_aprs.h" #include "decode_aprs.h"
#include "log.h" #include "log.h"
@ -887,7 +905,7 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev
decode_aprs_t A; decode_aprs_t A;
decode_aprs (&A, pp, 0, 0); decode_aprs (&A, pp, 0, NULL);
// Temp experiment to see how different systems set the RR bits in the source and destination. // Temp experiment to see how different systems set the RR bits in the source and destination.
// log_rr_bits (&A, pp); // log_rr_bits (&A, pp);

View File

@ -354,6 +354,10 @@ int audio_open (struct audio_s *pa)
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not open audio device %s for input\n%s\n", dw_printf ("Could not open audio device %s for input\n%s\n",
audio_in_name, snd_strerror(err)); audio_in_name, snd_strerror(err));
if (err == -EBUSY) {
dw_printf ("This means that some other application is using that device.\n");
dw_printf ("The solution is to identify that other application and stop it.\n");
}
return (-1); return (-1);
} }
@ -459,6 +463,10 @@ int audio_open (struct audio_s *pa)
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not open audio device %s for output\n%s\n", dw_printf ("Could not open audio device %s for output\n%s\n",
audio_out_name, snd_strerror(err)); audio_out_name, snd_strerror(err));
if (err == -EBUSY) {
dw_printf ("This means that some other application is using that device.\n");
dw_printf ("The solution is to identify that other application and stop it.\n");
}
return (-1); return (-1);
} }
@ -1532,7 +1540,7 @@ int audio_flush (int a)
* (3) Call this function, which might or might not wait long enough. * (3) Call this function, which might or might not wait long enough.
* (4) Add (1) and (2) resulting in when PTT should be turned off. * (4) Add (1) and (2) resulting in when PTT should be turned off.
* (5) Take difference between current time and desired PPT off time * (5) Take difference between current time and desired PPT off time
* and wait for additoinal time if required. * and wait for additional time if required.
* *
*----------------------------------------------------------------*/ *----------------------------------------------------------------*/

View File

@ -28,7 +28,8 @@
enum ptt_method_e { enum ptt_method_e {
PTT_METHOD_NONE, /* VOX or no transmit. */ PTT_METHOD_NONE, /* VOX or no transmit. */
PTT_METHOD_SERIAL, /* Serial port RTS or DTR. */ PTT_METHOD_SERIAL, /* Serial port RTS or DTR. */
PTT_METHOD_GPIO, /* General purpose I/O, Linux only. */ PTT_METHOD_GPIO, /* General purpose I/O using sysfs, deprecated after 2020, Linux only. */
PTT_METHOD_GPIOD, /* General purpose I/O, using libgpiod, Linux only. */
PTT_METHOD_LPT, /* Parallel printer port, Linux only. */ PTT_METHOD_LPT, /* Parallel printer port, Linux only. */
PTT_METHOD_HAMLIB, /* HAMLib, Linux only. */ PTT_METHOD_HAMLIB, /* HAMLib, Linux only. */
PTT_METHOD_CM108 }; /* GPIO pin of CM108/CM119/etc. Linux only. */ PTT_METHOD_CM108 }; /* GPIO pin of CM108/CM119/etc. Linux only. */
@ -72,18 +73,25 @@ struct audio_s {
struct adev_param_s { struct adev_param_s {
/* Properites of the sound device. */ /* Properties of the sound device. */
int defined; /* Was device defined? */ int defined; /* Was device defined? 0=no. >0 for yes. */
/* First one defaults to 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?). */ 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?). */ 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 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). */ int bits_per_sample; /* 8 (unsigned char) or 16 (signed short). */
} adev[MAX_ADEVS]; } adev[MAX_ADEVS];
@ -102,7 +110,7 @@ struct audio_s {
/* This is the probability, in per cent, of randomly corrupting it. */ /* This is the probability, in per cent, of randomly corrupting it. */
/* Normally this is 0. 25 would mean corrupt it 25% of the time. */ /* Normally this is 0. 25 would mean corrupt it 25% of the time. */
int recv_error_rate; /* Similar but the % probablity of dropping a received frame. */ int recv_error_rate; /* Similar but the % probability of dropping a received frame. */
float recv_ber; /* Receive Bit Error Rate (BER). */ float recv_ber; /* Receive Bit Error Rate (BER). */
/* Probability of inverting a bit coming out of the modem. */ /* Probability of inverting a bit coming out of the modem. */
@ -151,6 +159,17 @@ struct audio_s {
struct achan_param_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. ??? // 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. */ char mycall[AX25_MAX_ADDR_LEN]; /* Call associated with this radio channel. */
/* Could all be the same or different. */ /* Could all be the same or different. */
@ -295,6 +314,7 @@ struct audio_s {
/* the case for CubieBoard where it was longer. */ /* the case for CubieBoard where it was longer. */
/* This is filled in by ptt_init so we don't have to */ /* This is filled in by ptt_init so we don't have to */
/* recalculate it each time we access it. */ /* 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. */ /* This could probably be collapsed into ptt_device instead of being separate. */
@ -417,7 +437,8 @@ struct audio_s {
#define DEFAULT_BITS_PER_SAMPLE 16 #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. * Standard for AFSK on VHF FM.
@ -447,11 +468,11 @@ struct audio_s {
*/ */
#define DEFAULT_DWAIT 0 #define DEFAULT_DWAIT 0
#define DEFAULT_SLOTTIME 10 #define DEFAULT_SLOTTIME 10 // *10mS = 100mS
#define DEFAULT_PERSIST 63 #define DEFAULT_PERSIST 63
#define DEFAULT_TXDELAY 30 #define DEFAULT_TXDELAY 30 // *10mS = 300mS
#define DEFAULT_TXTAIL 10 #define DEFAULT_TXTAIL 10 // *10mS = 100mS
#define DEFAULT_FULLDUP 0 #define DEFAULT_FULLDUP 0 // false = half duplex
/* /*
* Interlocks are used to 'bind' channels together to share DCD and PTT signaling. * Interlocks are used to 'bind' channels together to share DCD and PTT signaling.

View File

@ -213,7 +213,21 @@ static int pa_devNN(char *deviceStr, char *_devName, size_t length, int *_devNo)
while(*cPtr) { while(*cPtr) {
cVal = *cPtr++; cVal = *cPtr++;
if(cVal == ':') break; if(cVal == ':') break;
if(((cVal >= ' ') && (cVal <= '~')) && (count < length)) {
// See Issue 417.
// Originally this copied only printable ASCII characters (space thru ~).
// That is a problem for some locales that use UTF-8 characters in the device name.
// original: if(((cVal >= ' ') && (cVal <= '~')) && (count < length)) {
// At first I was thinking we should keep the test for < ' ' but then I
// remembered that char type can be signed or unsigned depending on implementation.
// If characters are signed then a value above 0x7f would be considered negative.
// It seems to me that the test for buffer full is off by one.
// count could reach length, leaving no room for a nul terminator.
// Compare has been changed so count is limited to length minus 1.
if(count < length - 1) {
_devName[count++] = cVal; _devName[count++] = cVal;
} }
@ -1149,7 +1163,7 @@ int audio_put (int a, int c)
static double start = 0, end = 0, diff = 0; static double start = 0, end = 0, diff = 0;
if(adev[a].outbuf_len == 0) if(adev[a].outbuf_len == 0)
start = dtime_now(); start = dtime_monotonic();
#endif #endif
if(c >= 0) { if(c >= 0) {
@ -1178,7 +1192,7 @@ int audio_put (int a, int c)
#ifdef __TIMED__ #ifdef __TIMED__
count += frames; count += frames;
if(c < 0) { // When the Ax25 frames are flushed. if(c < 0) { // When the Ax25 frames are flushed.
end = dtime_now(); end = dtime_monotonic();
diff = end - start; diff = end - start;
if(count) if(count)
dw_printf ("Transfer Time:%3.9f No of Frames:%d Per frame:%3.9f speed:%f\n", dw_printf ("Transfer Time:%3.9f No of Frames:%d Per frame:%3.9f speed:%f\n",
@ -1246,7 +1260,7 @@ int audio_flush (int a)
* (3) Call this function, which might or might not wait long enough. * (3) Call this function, which might or might not wait long enough.
* (4) Add (1) and (2) resulting in when PTT should be turned off. * (4) Add (1) and (2) resulting in when PTT should be turned off.
* (5) Take difference between current time and desired PPT off time * (5) Take difference between current time and desired PPT off time
* and wait for additoinal time if required. * and wait for additional time if required.
* *
*----------------------------------------------------------------*/ *----------------------------------------------------------------*/

View File

@ -65,7 +65,6 @@
#include "audio_stats.h" #include "audio_stats.h"
#include "textcolor.h" #include "textcolor.h"
#include "dtime_now.h"
#include "demod.h" /* for alevel_t & demod_get_audio_level() */ #include "demod.h" /* for alevel_t & demod_get_audio_level() */

View File

@ -84,7 +84,7 @@ static struct audio_s *save_audio_config_p;
*/ */
/* /*
* Originally, we had an abitrary buf time of 40 mS. * Originally, we had an arbitrary buf time of 40 mS.
* *
* For mono, the buffer size was rounded up from 3528 to 4k so * For mono, the buffer size was rounded up from 3528 to 4k so
* it was really about 50 mS per buffer or about 20 per second. * it was really about 50 mS per buffer or about 20 per second.
@ -1074,7 +1074,7 @@ int audio_flush (int a)
* (3) Call this function, which might or might not wait long enough. * (3) Call this function, which might or might not wait long enough.
* (4) Add (1) and (2) resulting in when PTT should be turned off. * (4) Add (1) and (2) resulting in when PTT should be turned off.
* (5) Take difference between current time and desired PPT off time * (5) Take difference between current time and desired PPT off time
* and wait for additoinal time if required. * and wait for additional time if required.
* *
*----------------------------------------------------------------*/ *----------------------------------------------------------------*/

View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // 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 // 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 // it under the terms of the GNU General Public License as published by
@ -194,14 +194,16 @@
// Debug switches for different types of information. // Debug switches for different types of information.
// Should have command line options instead of changing source and recompiling. // 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. // 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. static int s_debug_client_app = 0; // Interaction with client application.
// dl_connect_request, dl_data_request, dl_data_indication, etc. // dl_connect_request, dl_data_request, dl_data_indication, etc.
static int s_debug_radio = 0; // Received frames and channel busy status. static int s_debug_radio = 0; // Received frames and channel busy status.
// lm_data_indication, lm_channel_busy // lm_data_indication, lm_channel_busy
static int s_debug_variables = 0; // Variables, state changes. static int s_debug_variables = 0; // Variables, state changes.
static int s_debug_retry = 0; // Related to lost I frames, REJ, SREJ, timeout, resending. static int s_debug_retry = 0; // Related to lost I frames, REJ, SREJ, timeout, resending.
@ -250,7 +252,7 @@ typedef struct ax25_dlsm_s {
// notifications about state changes. // 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. // Up to 10 addresses, same order as in frame.
int num_addr; // Number of addresses. Should be in range 2 .. 10. int num_addr; // Number of addresses. Should be in range 2 .. 10.
@ -347,7 +349,7 @@ typedef struct ax25_dlsm_s {
// Sometimes the flow chart has SAT instead of SRT. // Sometimes the flow chart has SAT instead of SRT.
// I think that is a typographical error. // I think that is a typographical error.
float t1v; // How long to wait for an acknowlegement before resending. float t1v; // How long to wait for an acknowledgement before resending.
// Value used when starting timer T1, in seconds. // Value used when starting timer T1, in seconds.
// "FRACK" parameter in some implementations. // "FRACK" parameter in some implementations.
// Typically it might be 3 seconds after frame has been // Typically it might be 3 seconds after frame has been
@ -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 PAUSE_TM201 pause_tm201(S, __func__, __LINE__)
#define RESUME_TM201 resume_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); static void dl_data_indication (ax25_dlsm_t *S, int pid, char *data, int len);
@ -1075,10 +1079,29 @@ void dl_disconnect_request (dlq_item_t *E)
case state_1_awaiting_connection: case state_1_awaiting_connection:
case state_5_awaiting_v22_connection: case state_5_awaiting_v22_connection:
// TODO: "requeue." Not sure what to do here. // Erratum: The protocol spec says "requeue." If we put disconnect req back in the
// If we put it back in the queue we will get it back again probably still in same state. // queue we will probably get it back again here while still in same state.
// Need a way to defer it until the next state change. // 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; break;
case state_2_awaiting_release: case state_2_awaiting_release:
@ -1580,13 +1603,49 @@ void dl_unregister_callsign (dlq_item_t *E)
* - Incoming connected data, from application still in the queue. * - Incoming connected data, from application still in the queue.
* - I frames which have been transmitted but not yet acknowledged. * - 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 wont 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) void dl_outstanding_frames_request (dlq_item_t *E)
{ {
ax25_dlsm_t *S; 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) { if (s_debug_client_app) {
text_color_set(DW_COLOR_DEBUG); 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); S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create);
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.
if (S == NULL) { char swapped[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
text_color_set(DW_COLOR_ERROR); memset (swapped, 0, sizeof(swapped));
dw_printf ("Can't get outstanding frames for %s -> %s, chan %d\n", E->addrs[OWNCALL], E->addrs[PEERCALL], E->chan); strlcpy (swapped[PEERCALL], E->addrs[OWNCALL], sizeof(swapped[PEERCALL]));
server_outstanding_frames_reply (E->chan, E->client, E->addrs[OWNCALL], E->addrs[PEERCALL], 0); strlcpy (swapped[OWNCALL], E->addrs[PEERCALL], sizeof(swapped[OWNCALL]));
return; 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 // 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 } // end dl_outstanding_frames_request
@ -6068,7 +6149,7 @@ static void check_need_for_response (ax25_dlsm_t *S, ax25_frame_type_t frame_typ
* *
* Outputs: S->srt New smoothed roundtrip time. * Outputs: S->srt New smoothed roundtrip time.
* *
* S->t1v How long to wait for an acknowlegement before resending. * S->t1v How long to wait for an acknowledgement before resending.
* Value used when starting timer T1, in seconds. * Value used when starting timer T1, in seconds.
* Here it is dynamically adjusted. * Here it is dynamically adjusted.
* *
@ -6167,7 +6248,7 @@ static void select_t1_value (ax25_dlsm_t *S)
// This goes up exponentially if implemented as documented! // This goes up exponentially if implemented as documented!
// For example, if we were trying to connect to a station which is not there, we // 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. // That's ridiculous. Let's try increasing it by a quarter second each time.
// We now give up after about a minute. // We now give up after about a minute.
@ -6184,12 +6265,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) { if (S->t1v < 0.99 || S->t1v > 30) {
text_color_set(DW_COLOR_ERROR); 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", 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); S->stream_id, S->rc, S->t1_remaining_when_last_stopped, old_srt, S->srt, S->t1v);
} }
#endif
} /* end select_t1_value */ } /* end select_t1_value */

View File

@ -1866,7 +1866,7 @@ packet_t ax25_get_nextp (packet_t this_p)
* *
* Inputs: this_p - Current packet object. * Inputs: this_p - Current packet object.
* *
* release_time - Time as returned by dtime_now(). * release_time - Time as returned by dtime_monotonic().
* *
*------------------------------------------------------------------------------*/ *------------------------------------------------------------------------------*/
@ -2923,7 +2923,9 @@ int ax25_alevel_to_text (alevel_t alevel, char text[AX25_ALEVEL_TO_TEXT_SIZE])
snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d(%+d/%+d)", alevel.rec, alevel.mark, alevel.space); snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d(%+d/%+d)", alevel.rec, alevel.mark, alevel.space);
} }
else if (alevel.mark == -1 && alevel.space == -1) { /* PSK - single number. */ else if ((alevel.mark == -1 && alevel.space == -1) || /* PSK */
(alevel.mark == -99 && alevel.space == -99)) { /* v. 1.7 "B" FM demodulator. */
// ?? Where does -99 come from?
snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d", alevel.rec); snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d", alevel.rec);
} }

View File

@ -162,6 +162,7 @@ void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct
int chan = g_misc_config_p->beacon[j].sendto_chan; int chan = g_misc_config_p->beacon[j].sendto_chan;
if (chan < 0) chan = 0; /* For IGate, use channel 0 call. */ if (chan < 0) chan = 0; /* For IGate, use channel 0 call. */
if (chan >= MAX_CHANS) chan = 0; // For ICHANNEL, use channel 0 call.
if (g_modem_config_p->chan_medium[chan] == MEDIUM_RADIO || if (g_modem_config_p->chan_medium[chan] == MEDIUM_RADIO ||
g_modem_config_p->chan_medium[chan] == MEDIUM_NETTNC) { g_modem_config_p->chan_medium[chan] == MEDIUM_NETTNC) {
@ -621,6 +622,7 @@ static void * beacon_thread (void *arg)
// On reboot, the time is in the past. // On reboot, the time is in the past.
// After time gets set from GPS, all beacons from that interval are sent. // After time gets set from GPS, all beacons from that interval are sent.
// FIXME: This will surely break time slotted scheduling. // FIXME: This will surely break time slotted scheduling.
// TODO: The correct fix will be using monotonic, rather than clock, time.
/* craigerl: if next beacon is scheduled in the past, then set next beacon relative to now (happens when NTP pushes clock AHEAD) */ /* craigerl: if next beacon is scheduled in the past, then set next beacon relative to now (happens when NTP pushes clock AHEAD) */
/* fixme: if NTP sets clock BACK an hour, this thread will sleep for that hour */ /* fixme: if NTP sets clock BACK an hour, this thread will sleep for that hour */
@ -805,11 +807,17 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
assert (bp->sendto_chan >= 0); assert (bp->sendto_chan >= 0);
strlcpy (mycall, g_modem_config_p->achan[bp->sendto_chan].mycall, sizeof(mycall)); if (g_modem_config_p->chan_medium[bp->sendto_chan] == MEDIUM_IGATE) { // ICHANNEL uses chan 0 mycall.
// TODO: Maybe it should be allowed to have own.
strlcpy (mycall, g_modem_config_p->achan[0].mycall, sizeof(mycall));
}
else {
strlcpy (mycall, g_modem_config_p->achan[bp->sendto_chan].mycall, sizeof(mycall));
}
if (strlen(mycall) == 0 || strcmp(mycall, "NOCALL") == 0) { if (strlen(mycall) == 0 || strcmp(mycall, "NOCALL") == 0) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("MYCALL not set for beacon in config file line %d.\n", bp->lineno); dw_printf ("MYCALL not set for beacon to chan %d in config file line %d.\n", bp->sendto_chan, bp->lineno);
return; return;
} }
@ -1046,7 +1054,7 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
text_color_set(DW_COLOR_XMIT); text_color_set(DW_COLOR_XMIT);
dw_printf ("[ig] %s\n", beacon_text); dw_printf ("[ig] %s\n", beacon_text);
igate_send_rec_packet (0, pp); igate_send_rec_packet (-1, pp); // Channel -1 to avoid RF>IS filtering.
ax25_delete (pp); ax25_delete (pp);
break; break;

View File

@ -34,6 +34,7 @@
* We have a few commercial products: * We have a few commercial products:
* *
* DINAH https://hamprojects.info/dinah/ * DINAH https://hamprojects.info/dinah/
* PAUL https://hamprojects.info/paul/
* DMK URI http://www.dmkeng.com/URI_Order_Page.htm * DMK URI http://www.dmkeng.com/URI_Order_Page.htm
* RB-USB RIM http://www.repeater-builder.com/products/usb-rim-lite.html * RB-USB RIM http://www.repeater-builder.com/products/usb-rim-lite.html
* RA-35 http://www.masterscommunications.com/products/radio-adapter/ra35.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. * with a single USB Audio Adapter, but does not automatically handle the multiple device case.
* Manual configuration needs to be used in this 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" #include "direwolf.h"
@ -178,6 +185,11 @@ static int cm108_write (char *name, int iomask, int iodata);
#define SSS_PID2 0x1607 #define SSS_PID2 0x1607
#define SSS_PID3 0x160b #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 // 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_CM119A \
|| p == CMEDIA_PID_CM119B )) \ || 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. // 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 #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. * Result of taking inventory of USB soundcards and USB HIDs.
*/ */
@ -258,7 +278,8 @@ struct thing_s {
// Oversized to silence a compiler warning. // Oversized to silence a compiler warning.
char plughw2[72]; // With name rather than number. char plughw2[72]; // With name rather than number.
char devpath[128]; // Kernel dev path. Does not include /sys mount point. 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. // 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} // \\?\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 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. * 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 * restriction of using only one GPIO pin per device and limit
* configuration to PTT only. * configuration to PTT only.
* Longer term, we might want to have DCD, and maybe other * 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 iomask = 1 << (num - 1); // 0=input, 1=output
iodata = state << (num - 1); // 0=low, 1=high iodata = state << (num - 1); // 0=low, 1=high
return (cm108_write (name, iomask, iodata)); return (cm108_write (name, iomask, iodata));
} /* end cm108_set_gpio_pin */ } /* end cm108_set_gpio_pin */

View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // 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 // 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 // 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, void config_init (char *fname, struct audio_s *p_audio_config,
struct digi_config_s *p_digi_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)); 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].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].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].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[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; channel<MAX_CHANS; channel++) { for (channel=0; channel<MAX_CHANS; channel++) {
int ot, it; int ot, it;
@ -919,10 +928,13 @@ void config_init (char *fname, struct audio_s *p_audio_config,
p_misc_config->maxframe_extended = AX25_K_MAXFRAME_EXTENDED_DEFAULT; /* Max frames to send before ACK. mod 128 "Window" size. */ p_misc_config->maxframe_extended = AX25_K_MAXFRAME_EXTENDED_DEFAULT; /* Max frames to send before ACK. mod 128 "Window" size. */
p_misc_config->maxv22 = AX25_N2_RETRY_DEFAULT / 3; /* Max SABME before falling back to SABM. */ 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. */ 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->v20_count = 0;
p_misc_config->noxid_addrs = NULL; /* Don't send XID to these stations. */ 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; p_misc_config->noxid_count = 0;
/* /*
@ -972,7 +984,8 @@ void config_init (char *fname, struct audio_s *p_audio_config,
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - Could not open config file %s\n", filepath); dw_printf ("ERROR - Could not open config file %s\n", filepath);
dw_printf ("Try using -c command line option for alternate location.\n"); 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); dw_printf ("\nReading config file %s\n", filepath);
@ -1006,6 +1019,10 @@ void config_init (char *fname, struct audio_s *p_audio_config,
* ADEVICE plughw:2,0 plughw:3,0 -- different in/out for a channel or channel pair. * 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. * 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 */ /* Note that ALSA name can contain comma such as hw:1,0 */
@ -1029,20 +1046,46 @@ void config_init (char *fname, struct audio_s *p_audio_config,
if (t == NULL) { if (t == NULL) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: Missing name of audio device for ADEVICE command on line %d.\n", line); 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; continue;
} }
p_audio_config->adev[adevice].defined = 1; p_audio_config->adev[adevice].defined = 1;
/* First channel of device is valid. */ // New case for release 1.8.
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)); if (strcmp(t, "=") == 0) {
strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out)); t = split(NULL,0);
if (t != NULL) {
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)); 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));
}
} }
} }
@ -1828,6 +1871,45 @@ void config_init (char *fname, struct audio_s *p_audio_config,
} }
p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_GPIO; p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_GPIO;
#endif #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) { else if (strcasecmp(t, "LPT") == 0) {
@ -2015,7 +2097,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 ("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 ("You must rebuild direwolf with CM108 Audio Adapter GPIO PTT support.\n");
dw_printf ("See Interface Guide for details.\n"); dw_printf ("See Interface Guide for details.\n");
rtfm();
exit (EXIT_FAILURE); exit (EXIT_FAILURE);
#endif #endif
} }
@ -2192,7 +2274,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
else { else {
p_audio_config->achan[channel].slottime = DEFAULT_SLOTTIME; p_audio_config->achan[channel].slottime = DEFAULT_SLOTTIME;
text_color_set(DW_COLOR_ERROR); 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); line, p_audio_config->achan[channel].slottime);
} }
} }
@ -2216,7 +2298,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
else { else {
p_audio_config->achan[channel].persist = DEFAULT_PERSIST; p_audio_config->achan[channel].persist = DEFAULT_PERSIST;
text_color_set(DW_COLOR_ERROR); 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); line, p_audio_config->achan[channel].persist);
} }
} }
@ -2235,6 +2317,19 @@ void config_init (char *fname, struct audio_s *p_audio_config,
} }
n = atoi(t); n = atoi(t);
if (n >= 0 && n <= 255) { if (n >= 0 && n <= 255) {
text_color_set(DW_COLOR_ERROR);
if (n == 0) {
dw_printf ("Line %d: Setting TXDELAY to 0 is a REALLY BAD idea if you want other stations to hear you.\n",
line);
dw_printf ("Line %d: See User Guide, \"Radio Channel - Transmit Timing\" for an explanation.\n",
line);
}
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.);
}
p_audio_config->achan[channel].txdelay = n; p_audio_config->achan[channel].txdelay = n;
} }
else { else {
@ -2259,6 +2354,18 @@ void config_init (char *fname, struct audio_s *p_audio_config,
} }
n = atoi(t); n = atoi(t);
if (n >= 0 && n <= 255) { if (n >= 0 && n <= 255) {
if (n == 0) {
dw_printf ("Line %d: Setting TXTAIL to 0 is a REALLY BAD idea if you want other stations to hear you.\n",
line);
dw_printf ("Line %d: See User Guide, \"Radio Channel - Transmit Timing\" for an explanation.\n",
line);
}
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.);
}
p_audio_config->achan[channel].txtail = n; p_audio_config->achan[channel].txtail = n;
} }
else { else {
@ -2399,7 +2506,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
p_audio_config->achan[channel].il2p_invert_polarity = 0; p_audio_config->achan[channel].il2p_invert_polarity = 0;
while ((t = split(NULL,0)) != NULL) { while ((t = split(NULL,0)) != NULL) {
for (char *c = t; *t != '\0'; c++) { for (char *c = t; *c != '\0'; c++) {
switch (*c) { switch (*c) {
case '+': case '+':
p_audio_config->achan[channel].il2p_invert_polarity = 0; p_audio_config->achan[channel].il2p_invert_polarity = 0;
@ -2542,7 +2649,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Preemptive digipeating DROP option is discouraged.\n", line); 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 ("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; p_digi_config->preempt[from_chan][to_chan] = PREEMPT_DROP;
t = split(NULL,0); t = split(NULL,0);
} }
@ -2550,11 +2657,11 @@ void config_init (char *fname, struct audio_s *p_audio_config,
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Preemptive digipeating MARK option is discouraged.\n", line); 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 ("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; p_digi_config->preempt[from_chan][to_chan] = PREEMPT_MARK;
t = split(NULL,0); 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; p_digi_config->preempt[from_chan][to_chan] = PREEMPT_TRACE;
t = split(NULL,0); t = split(NULL,0);
} }
@ -2809,6 +2916,12 @@ void config_init (char *fname, struct audio_s *p_audio_config,
} }
if (*t == 'i' || *t == 'I') { if (*t == 'i' || *t == 'I') {
from_chan = MAX_CHANS; 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 { else {
from_chan = isdigit(*t) ? atoi(t) : -999; from_chan = isdigit(*t) ? atoi(t) : -999;
@ -2842,6 +2955,12 @@ void config_init (char *fname, struct audio_s *p_audio_config,
} }
if (*t == 'i' || *t == 'I') { if (*t == 'i' || *t == 'I') {
to_chan = MAX_CHANS; 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 { else {
to_chan = isdigit(*t) ? atoi(t) : -999; to_chan = isdigit(*t) ? atoi(t) : -999;
@ -2893,6 +3012,9 @@ void config_init (char *fname, struct audio_s *p_audio_config,
/* /*
* CFILTER from-chan to-chan filter_specification_expression * CFILTER from-chan to-chan filter_specification_expression
*
* Why did I put this here?
* What would be a useful use case? Perhaps block by source or destination?
*/ */
else if (strcasecmp(t, "CFILTER") == 0) { else if (strcasecmp(t, "CFILTER") == 0) {
@ -4604,7 +4726,6 @@ void config_init (char *fname, struct audio_s *p_audio_config,
* *
* In version 1.2 we allow 0 to disable listening. * 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) { else if (strcasecmp(t, "AGWPORT") == 0) {
int n; int n;
@ -4615,6 +4736,13 @@ void config_init (char *fname, struct audio_s *p_audio_config,
continue; continue;
} }
n = atoi(t); 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) { if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) {
p_misc_config->agwpe_port = n; p_misc_config->agwpe_port = n;
} }
@ -5016,7 +5144,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Old style 'BEACON' has been replaced with new commands.\n", line); dw_printf ("Config file, line %d: Old style 'BEACON' has been replaced with new commands.\n", line);
dw_printf ("Use PBEACON, OBEACON, or CBEACON instead.\n"); dw_printf ("Use PBEACON, OBEACON, TBEACON, or CBEACON instead.\n");
} }
@ -5031,6 +5159,9 @@ void config_init (char *fname, struct audio_s *p_audio_config,
* New style with keywords for options. * New style with keywords for options.
*/ */
// TODO: maybe add proportional pathing so multiple beacon timing does not need to be manually constructed?
// http://www.aprs.org/newN/ProportionalPathing.txt
else if (strcasecmp(t, "PBEACON") == 0 || else if (strcasecmp(t, "PBEACON") == 0 ||
strcasecmp(t, "OBEACON") == 0 || strcasecmp(t, "OBEACON") == 0 ||
strcasecmp(t, "TBEACON") == 0 || strcasecmp(t, "TBEACON") == 0 ||
@ -5484,7 +5615,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
for (j=0; j<MAX_CHANS; j++) { for (j=0; j<MAX_CHANS; j++) {
if (p_audio_config->chan_medium[j] == MEDIUM_RADIO || p_audio_config->chan_medium[j] == MEDIUM_NETTNC) { if (p_audio_config->chan_medium[j] == MEDIUM_RADIO || p_audio_config->chan_medium[j] == MEDIUM_NETTNC) {
if (p_digi_config->filter_str[MAX_CHANS][j] == NULL) { if (p_digi_config->filter_str[MAX_CHANS][j] == NULL) {
p_digi_config->filter_str[MAX_CHANS][j] = strdup("i/60"); p_digi_config->filter_str[MAX_CHANS][j] = strdup("i/180");
} }
} }
} }
@ -5617,7 +5748,8 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
} }
else if (value[0] == 'r' || value[0] == 'R') { else if (value[0] == 'r' || value[0] == 'R') {
int n = atoi(value+1); int n = atoi(value+1);
if ( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) { if (( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE)
&& p_audio_config->chan_medium[n] != MEDIUM_IGATE) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Simulated receive on channel %d is not valid.\n", line, n); dw_printf ("Config file, line %d: Simulated receive on channel %d is not valid.\n", line, n);
continue; continue;
@ -5627,7 +5759,8 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
} }
else if (value[0] == 't' || value[0] == 'T' || value[0] == 'x' || value[0] == 'X') { else if (value[0] == 't' || value[0] == 'T' || value[0] == 'x' || value[0] == 'X') {
int n = atoi(value+1); int n = atoi(value+1);
if ( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) { if (( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE)
&& p_audio_config->chan_medium[n] != MEDIUM_IGATE) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n); dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n);
continue; continue;
@ -5638,7 +5771,8 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
} }
else { else {
int n = atoi(value); int n = atoi(value);
if ( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) { if (( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE)
&& p_audio_config->chan_medium[n] != MEDIUM_IGATE) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n); dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n);
continue; continue;
@ -5767,8 +5901,9 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
else if (strcasecmp(keyword, "POWER") == 0) { else if (strcasecmp(keyword, "POWER") == 0) {
b->power = atoi(value); b->power = atoi(value);
} }
else if (strcasecmp(keyword, "HEIGHT") == 0) { else if (strcasecmp(keyword, "HEIGHT") == 0) { // This is in feet.
b->height = atoi(value); b->height = atoi(value);
// TODO: ability to add units suffix, e.g. 10m
} }
else if (strcasecmp(keyword, "GAIN") == 0) { else if (strcasecmp(keyword, "GAIN") == 0) {
b->gain = atoi(value); b->gain = atoi(value);
@ -5851,6 +5986,10 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
/* /*
* Process symbol now that we have any later overlay. * Process symbol now that we have any later overlay.
*
* FIXME: Someone who used this was surprised to end up with Solar Powser (S-).
* overlay=S symbol="/-"
* We should complain if overlay used with symtab other than \.
*/ */
if (strlen(temp_symbol) > 0) { if (strlen(temp_symbol) > 0) {
@ -5883,19 +6022,32 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
if (b->sendto_type == SENDTO_XMIT) { if (b->sendto_type == SENDTO_XMIT) {
if ( b->sendto_chan < 0 || b->sendto_chan >= MAX_CHANS || p_audio_config->chan_medium[b->sendto_chan] == MEDIUM_NONE) { if (( b->sendto_chan < 0 || b->sendto_chan >= MAX_CHANS || p_audio_config->chan_medium[b->sendto_chan] == MEDIUM_NONE)
&& p_audio_config->chan_medium[b->sendto_chan] != MEDIUM_IGATE) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, b->sendto_chan); dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, b->sendto_chan);
return (0); return (0);
} }
if ( strcmp(p_audio_config->achan[b->sendto_chan].mycall, "") == 0 || if (p_audio_config->chan_medium[b->sendto_chan] == MEDIUM_IGATE) { // Prevent subscript out of bounds.
strcmp(p_audio_config->achan[b->sendto_chan].mycall, "NOCALL") == 0 || // Will be using call from chan 0 later.
strcmp(p_audio_config->achan[b->sendto_chan].mycall, "N0CALL") == 0 ) { if ( strcmp(p_audio_config->achan[0].mycall, "") == 0 ||
strcmp(p_audio_config->achan[0].mycall, "NOCALL") == 0 ||
strcmp(p_audio_config->achan[0].mycall, "N0CALL") == 0 ) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: MYCALL must be set for channel %d before beaconing is allowed.\n", b->sendto_chan); dw_printf ("Config file: MYCALL must be set for channel %d before beaconing is allowed.\n", 0);
return (0); return (0);
}
} else {
if ( strcmp(p_audio_config->achan[b->sendto_chan].mycall, "") == 0 ||
strcmp(p_audio_config->achan[b->sendto_chan].mycall, "NOCALL") == 0 ||
strcmp(p_audio_config->achan[b->sendto_chan].mycall, "N0CALL") == 0 ) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file: MYCALL must be set for channel %d before beaconing is allowed.\n", b->sendto_chan);
return (0);
}
} }
} }

View File

@ -214,7 +214,7 @@ struct misc_config_s {
char symbol; /* Symbol code. */ char symbol; /* Symbol code. */
float power; /* For PHG. */ float power; /* For PHG. */
float height; float height; /* HAAT in feet */
float gain; /* Original protocol spec was unclear. */ float gain; /* Original protocol spec was unclear. */
/* Addendum 1.1 clarifies it is dBi not dBd. */ /* Addendum 1.1 clarifies it is dBi not dBd. */

View File

@ -17,6 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
// TODO: Better error messages for examples here: http://lists.tapr.org/pipermail/aprssig_lists.tapr.org/2023-July/date.html
/*------------------------------------------------------------------ /*------------------------------------------------------------------
* *
@ -140,10 +141,11 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen);
* *
* quiet - Suppress error messages. * quiet - Suppress error messages.
* *
* third_party - True when parsing a third party header. * third_party_src - Specify when parsing a third party header.
* (decode_aprs is called recursively.) * (decode_aprs is called recursively.)
* This is mostly found when an IGate transmits a message * This is mostly found when an IGate transmits a message
* that came via APRS-IS. * that came via APRS-IS.
* NULL when not third party payload.
* *
* Outputs: A-> g_symbol_table, g_symbol_code, * Outputs: A-> g_symbol_table, g_symbol_code,
* g_lat, g_lon, * g_lat, g_lon,
@ -156,11 +158,10 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen);
* *
*------------------------------------------------------------------*/ *------------------------------------------------------------------*/
void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, int third_party) void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_src)
{ {
//dw_printf ("DEBUG decode_aprs quiet=%d, third_party=%d\n", quiet, third_party); //dw_printf ("DEBUG decode_aprs quiet=%d, third_party=%p\n", quiet, third_party_src);
//char dest[AX25_MAX_ADDR_LEN];
unsigned char *pinfo; unsigned char *pinfo;
int info_len; int info_len;
@ -169,15 +170,17 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, int third_party)
//dw_printf ("DEBUG decode_aprs info=\"%s\"\n", pinfo); //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; A->g_quiet = quiet;
if (isprint(*pinfo)) { if (isprint(*pinfo)) {
snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Unknown APRS Data Type Indicator \"%c\"", *pinfo); snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "ERROR!!! Unknown APRS Data Type Indicator \"%c\"", *pinfo);
} }
else { else {
snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "ERROR!!! Unknown APRS Data Type Indicator: unprintable 0x%02x", *pinfo); snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "ERROR!!! Unknown APRS Data Type Indicator: unprintable 0x%02x", *pinfo);
} }
A->g_symbol_table = '/'; /* Default to primary table. */ A->g_symbol_table = '/'; /* Default to primary table. */
@ -204,6 +207,15 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, int third_party)
A->g_footprint_lon = G_UNKNOWN; A->g_footprint_lon = G_UNKNOWN;
A->g_footprint_radius = G_UNKNOWN; A->g_footprint_radius = G_UNKNOWN;
// TODO: Complain if obsolete WIDE or RELAY is found in via path.
// TODO: complain if unused WIDEn is see in path.
// There is a report of UIDIGI decrementing ssid 1 to 0 and not marking it used.
// http://lists.tapr.org/pipermail/aprssig_lists.tapr.org/2022-May/049397.html
// TODO: Complain if used digi is found after unused. Should never happen.
// If third-party header, try to decode just the payload. // If third-party header, try to decode just the payload.
if (*pinfo == '}') { if (*pinfo == '}') {
@ -220,7 +232,13 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, int third_party)
packet_t pp_payload = ax25_from_text ((char*)pinfo+1, 0); packet_t pp_payload = ax25_from_text ((char*)pinfo+1, 0);
if (pp_payload != NULL) { if (pp_payload != NULL) {
decode_aprs (A, pp_payload, quiet, 1); // 1 means used recursively char payload_src[AX25_MAX_ADDR_LEN];
memset(payload_src, 0, sizeof(payload_src));
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); ax25_delete (pp_payload);
return; return;
} }
@ -234,8 +252,12 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, int third_party)
/* /*
* Extract source and destination including the SSID. * Extract source and destination including the SSID.
*/ */
if (third_party_src != NULL) {
ax25_get_addr_with_ssid (pp, AX25_SOURCE, A->g_src); strlcpy (A->g_src, third_party_src, sizeof(A->g_src));
}
else {
ax25_get_addr_with_ssid (pp, AX25_SOURCE, A->g_src);
}
ax25_get_addr_with_ssid (pp, AX25_DESTINATION, A->g_dest); ax25_get_addr_with_ssid (pp, AX25_DESTINATION, A->g_dest);
//dw_printf ("DEBUG decode_aprs source=%s, dest=%s\n", A->g_src, A->g_dest); //dw_printf ("DEBUG decode_aprs source=%s, dest=%s\n", A->g_src, A->g_dest);
@ -294,16 +316,17 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, int third_party)
if (strncmp((char*)pinfo, "!!", 2) == 0) if (strncmp((char*)pinfo, "!!", 2) == 0)
{ {
aprs_ultimeter (A, (char*)pinfo, info_len); aprs_ultimeter (A, (char*)pinfo, info_len); // TODO: produce obsolete error.
} }
else else
{ {
aprs_ll_pos (A, pinfo, info_len); aprs_ll_pos (A, pinfo, info_len);
} }
A->g_packet_type = packet_type_position;
break; break;
//case '#': /* Peet Bros U-II Weather station */ //case '#': /* Peet Bros U-II Weather station */ // TODO: produce obsolete error.
//case '*': /* Peet Bros U-II Weather station */ //case '*': /* Peet Bros U-II Weather station */
//break; //break;
@ -311,11 +334,13 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, int third_party)
if (strncmp((char*)pinfo, "$ULTW", 5) == 0) if (strncmp((char*)pinfo, "$ULTW", 5) == 0)
{ {
aprs_ultimeter (A, (char*)pinfo, info_len); aprs_ultimeter (A, (char*)pinfo, info_len); // TODO: produce obsolete error.
A->g_packet_type = packet_type_weather;
} }
else else
{ {
aprs_raw_nmea (A, pinfo, info_len); aprs_raw_nmea (A, pinfo, info_len);
A->g_packet_type = packet_type_position;
} }
break; break;
@ -323,17 +348,20 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, int third_party)
case '`': /* Current Mic-E Data (not used in TM-D700) */ case '`': /* Current Mic-E Data (not used in TM-D700) */
aprs_mic_e (A, pp, pinfo, info_len); aprs_mic_e (A, pp, pinfo, info_len);
A->g_packet_type = packet_type_position;
break; break;
case ')': /* Item. */ case ')': /* Item. */
aprs_item (A, pinfo, info_len); aprs_item (A, pinfo, info_len);
A->g_packet_type = packet_type_item;
break; break;
case '/': /* Position with timestamp (no APRS messaging) */ case '/': /* Position with timestamp (no APRS messaging) */
case '@': /* Position with timestamp (with APRS messaging) */ case '@': /* Position with timestamp (with APRS messaging) */
aprs_ll_pos_time (A, pinfo, info_len); aprs_ll_pos_time (A, pinfo, info_len);
A->g_packet_type = packet_type_position;
break; break;
@ -342,42 +370,76 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, int third_party)
/* Telemetry metadata. */ /* Telemetry metadata. */
aprs_message (A, pinfo, info_len, quiet); 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; break;
case ';': /* Object */ case ';': /* Object */
aprs_object (A, pinfo, info_len); aprs_object (A, pinfo, info_len);
A->g_packet_type = packet_type_object;
break; break;
case '<': /* Station Capabilities */ case '<': /* Station Capabilities */
aprs_station_capabilities (A, (char*)pinfo, info_len); aprs_station_capabilities (A, (char*)pinfo, info_len);
A->g_packet_type = packet_type_capabilities;
break; break;
case '>': /* Status Report */ case '>': /* Status Report */
aprs_status_report (A, (char*)pinfo, info_len); aprs_status_report (A, (char*)pinfo, info_len);
A->g_packet_type = packet_type_status;
break; break;
case '?': /* General Query */ case '?': /* General Query */
aprs_general_query (A, (char*)pinfo, info_len, quiet); aprs_general_query (A, (char*)pinfo, info_len, quiet);
A->g_packet_type = packet_type_query;
break; break;
case 'T': /* Telemetry */ case 'T': /* Telemetry */
aprs_telemetry (A, (char*)pinfo, info_len, quiet); aprs_telemetry (A, (char*)pinfo, info_len, quiet);
A->g_packet_type = packet_type_telemetry;
break; break;
case '_': /* Positionless Weather Report */ case '_': /* Positionless Weather Report */
aprs_positionless_weather_report (A, pinfo, info_len); aprs_positionless_weather_report (A, pinfo, info_len);
A->g_packet_type = packet_type_weather;
break; break;
case '{': /* user defined data */ case '{': /* user defined data */
aprs_user_defined (A, (char*)pinfo, info_len); aprs_user_defined (A, (char*)pinfo, info_len);
A->g_packet_type = packet_type_userdefined;
break; break;
case 't': /* Raw touch tone data - NOT PART OF STANDARD */ case 't': /* Raw touch tone data - NOT PART OF STANDARD */
@ -386,6 +448,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, int third_party)
/* Might move into user defined data, above. */ /* Might move into user defined data, above. */
aprs_raw_touch_tone (A, (char*)pinfo, info_len); aprs_raw_touch_tone (A, (char*)pinfo, info_len);
// no packet type for t/ filter
break; break;
case 'm': /* Morse Code data - NOT PART OF STANDARD */ case 'm': /* Morse Code data - NOT PART OF STANDARD */
@ -395,6 +458,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, int third_party)
/* Might move into user defined data, above. */ /* Might move into user defined data, above. */
aprs_morse_code (A, (char*)pinfo, info_len); aprs_morse_code (A, (char*)pinfo, info_len);
// no packet type for t/ filter
break; break;
//case '}': /* third party header */ //case '}': /* third party header */
@ -486,13 +550,17 @@ void decode_aprs_print (decode_aprs_t *A) {
//dw_printf ("DEBUG decode_aprs_print stemp3=%s mfr=%s\n", stemp, A->g_mfr); //dw_printf ("DEBUG decode_aprs_print stemp3=%s mfr=%s\n", stemp, A->g_mfr);
if (strlen(A->g_mfr) > 0) { if (strlen(A->g_mfr) > 0) {
if (strcmp(A->g_dest, "APRS") == 0) { if (strcmp(A->g_dest, "APRS") == 0 ||
strlcat (stemp, "\nUse of \"APRS\" in the destination field is obsolete.", sizeof(stemp)); 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, " You can help to improve the quality of APRS signals.", sizeof(stemp));
strlcat (stemp, "\nTell the sender (", sizeof(stemp)); strlcat (stemp, "\nTell the sender (", sizeof(stemp));
strlcat (stemp, A->g_src, sizeof(stemp)); strlcat (stemp, A->g_src, sizeof(stemp));
strlcat (stemp, ") to use the proper product code from", sizeof(stemp)); strlcat (stemp, ") to use the proper product identifier from", sizeof(stemp));
strlcat (stemp, " http://www.aprs.org/aprs11/tocalls.txt", sizeof(stemp)); strlcat (stemp, " https://github.com/aprsorg/aprs-deviceid ", sizeof(stemp));
} }
else { else {
strlcat (stemp, ", ", sizeof(stemp)); strlcat (stemp, ", ", sizeof(stemp));
@ -517,7 +585,7 @@ void decode_aprs_print (decode_aprs_t *A) {
/* http://eng.usna.navy.mil/~bruninga/aprs/aprs11.html */ /* http://eng.usna.navy.mil/~bruninga/aprs/aprs11.html */
/* "The Antenna Gain in the PHG format on page 28 is in dBi." */ /* "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)); strlcat (stemp, phg, sizeof(stemp));
} }
@ -527,7 +595,13 @@ void decode_aprs_print (decode_aprs_t *A) {
snprintf (rng, sizeof(rng), ", range=%.1f", A->g_range); snprintf (rng, sizeof(rng), ", range=%.1f", A->g_range);
strlcat (stemp, rng, sizeof(stemp)); strlcat (stemp, rng, sizeof(stemp));
} }
text_color_set(DW_COLOR_DECODED);
if (strncmp(stemp, "ERROR", 5) == 0) {
text_color_set(DW_COLOR_ERROR);
}
else {
text_color_set(DW_COLOR_DECODED);
}
dw_printf("%s\n", stemp); dw_printf("%s\n", stemp);
/* /*
@ -547,7 +621,6 @@ void decode_aprs_print (decode_aprs_t *A) {
* Any example was checked for each hemihemisphere using * Any example was checked for each hemihemisphere using
* http://www.amsat.org/cgi-bin/gridconv * http://www.amsat.org/cgi-bin/gridconv
*/ */
// FIXME soften language about upper case.
if (strlen(A->g_maidenhead) > 0) { if (strlen(A->g_maidenhead) > 0) {
@ -835,6 +908,32 @@ static void aprs_ll_pos (decode_aprs_t *A, unsigned char *info, int ilen)
strlcpy (A->g_data_type_desc, "Weather Report", sizeof(A->g_data_type_desc)); strlcpy (A->g_data_type_desc, "Weather Report", sizeof(A->g_data_type_desc));
weather_data (A, p->comment, TRUE); weather_data (A, p->comment, TRUE);
/*
Here is an interesting case.
The protocol spec states that a position report with symbol _ is a special case
and the information part must contain wxnow.txt format weather data.
But, here we see it being generated like a normal position report.
N8VIM>BEACON,AB1OC-10*,WIDE2-1:!4240.85N/07133.99W_PHG72604/ Pepperell, MA. WX. 442.9+ PL100<0x0d>
Didn't find wind direction in form c999.
Didn't find wind speed in form s999.
Didn't find wind gust in form g999.
Didn't find temperature in form t999.
Weather Report, WEATHER Station (blue)
N 42 40.8500, W 071 33.9900
, "PHG72604/ Pepperell, MA. WX. 442.9+ PL100"
It seems, to me, that this is a violation of the protocol spec.
Then, immediately following, we have a positionless weather report in Ultimeter format.
N8VIM>APN391,AB1OC-10*,WIDE2-1:$ULTW006F00CA01421C52275800008A00000102FA000F04A6000B002A<0x0d><0x0a>
Ultimeter, Kantronics KPC-3 rom versions
wind 6.9 mph, direction 284, temperature 32.2, barometer 29.75, humidity 76
aprs.fi merges these two together. Is that anywhere in the protocol spec or
just a heuristic added after noticing a pair of packets like this?
*/
} }
else { else {
/* Regular position report. */ /* Regular position report. */
@ -1040,7 +1139,9 @@ static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen)
* *
* Function: aprs_mic_e * 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. * Inputs: info - Pointer to Information field.
* ilen - Information field length. * ilen - Information field length.
@ -1049,31 +1150,120 @@ static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen)
* *
* Description: * 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 following encoded information:
* *
* * The 6 latitude digits. * Byte 1: Lat digit 1, message bit A
* * A 3-bit Mic-E message identifier, specifying one of 7 Standard Mic-E * Byte 2: Lat digit 2, message bit B
* Message Codes or one of 7 Custom Message Codes or an Emergency * Byte 3: Lat digit 3, message bit C
* Message Code. * Byte 4: Lat digit 4, N/S lat indicator
* * The North/South and West/East Indicators. * Byte 5: Lat digit 5, Longitude offset
* * The Longitude Offset Indicator. * Byte 6: Lat digit 6, W/E Long indicator
* * The generic APRS digipeater path code. * *
*
* "Although the destination address appears to be quite unconventional, it is * "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." * 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 `
* *
* This is up to date with the 24 Aug 16 version mentioning the TH-D74. * 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
*
* '......|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 * 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.
* *
* TODO: Destination SSID can contain generic digipeater path. * 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>
*
* ' 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. * Bugs: Doesn't handle ambiguous position. "space" treated as zero.
* Invalid data results in a message but latitude is not set to unknown. * Invalid data results in a message but latitude is not set to unknown.
@ -1352,7 +1542,7 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
} }
} }
/* 6th character of destintation indicates east / west. */ /* 6th character of destination indicates east / west. */
/* /*
* Example of apparently invalid encoding. 6th character missing. * Example of apparently invalid encoding. 6th character missing.
@ -1456,6 +1646,8 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
// This does not change very often but I'm wondering if we could parse // 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. // http://www.aprs.org/aprs12/mic-e-types.txt similar to how we use tocalls.txt.
// TODO: Use https://github.com/aprsorg/aprs-deviceid rather than hardcoding.
if (isT(*pfirst)) { if (isT(*pfirst)) {
// "legacy" formats. // "legacy" formats.
@ -1464,6 +1656,7 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
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-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 == '>' && *plast == '^') { strlcpy (A->g_mfr, "Kenwood TH-D74", sizeof(A->g_mfr)); pfirst++; plast--; }
else if (*pfirst == '>' && *plast == '&') { strlcpy (A->g_mfr, "Kenwood TH-D75", sizeof(A->g_mfr)); pfirst++; plast--; }
else if (*pfirst == '>' ) { strlcpy (A->g_mfr, "Kenwood TH-D7A", sizeof(A->g_mfr)); pfirst++; } 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 == ']' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TM-D710", sizeof(A->g_mfr)); pfirst++; plast--; }
@ -1481,6 +1674,7 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
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 == '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 == '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; } else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '1') { strlcpy (A->g_mfr, "Yaesu FTM-300D", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '5') { strlcpy (A->g_mfr, "Yaesu FTM-500D", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '`' && *(plast-1) == ' ' && *plast == 'X') { strlcpy (A->g_mfr, "AP510", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '`' && *(plast-1) == ' ' && *plast == 'X') { strlcpy (A->g_mfr, "AP510", sizeof(A->g_mfr)); pfirst++; plast-=2; }
@ -1489,6 +1683,7 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
// ' should be used for trackers (not message capable). // ' should be used for trackers (not message capable).
else if (*pfirst == '\'' && *(plast-1) == '(' && *plast == '5') { strlcpy (A->g_mfr, "Anytone D578UV", sizeof(A->g_mfr)); pfirst++; plast-=2; }
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 == '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 == '3') { strlcpy (A->g_mfr, "Byonics TinyTrack3", sizeof(A->g_mfr)); pfirst++; plast-=2; }
@ -1556,7 +1751,7 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
* Purpose: Decode "Message Format." * Purpose: Decode "Message Format."
* The word message is used loosely all over the place, but it has a very specific meaning here. * The word message is used loosely all over the place, but it has a very specific meaning here.
* *
* Inputs: info - Pointer to Information field. * Inputs: info - Pointer to Information field. Be careful not to modify it here!
* ilen - Information field length. * ilen - Information field length.
* quiet - suppress error messages. * quiet - suppress error messages.
* *
@ -1577,10 +1772,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 * It's a lot more complicated with different types of addressees
* and replies with acknowledgement or rejection. * 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:UNIT. Telemetry metadata, unit/label
* :xxxxxxxxx:EQNS. Telemetry metadata, Equation Coefficients * :xxxxxxxxx:EQNS. Telemetry metadata, Equation Coefficients
* :xxxxxxxxx:BITS. Telemetry metadata, Bit Sense/Project Name * :xxxxxxxxx:BITS. Telemetry metadata, Bit Sense/Project Name
@ -1596,7 +1800,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. * :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.
* *
*------------------------------------------------------------------*/ *------------------------------------------------------------------*/
@ -1610,7 +1815,7 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
char message[256-1-9-1]; /* Officially up to 67 characters for message text. */ char message[256-1-9-1]; /* Officially up to 67 characters for message text. */
/* Relaxing seemingly arbitrary restriction here; it doesn't need to fit on a punched card. */ /* Relaxing seemingly arbitrary restriction here; it doesn't need to fit on a punched card. */
/* Wouldn't surprise me if others did not pay attention to the limit. */ /* Wouldn't surprise me if others did not pay attention to the limit. */
/* Optional { followed by 1-5 alphanumeric characters for message number */ /* Optional '{' followed by 1-5 alphanumeric characters for message number */
/* If the first character is '?' it is a Directed Station Query. */ /* If the first character is '?' it is a Directed Station Query. */
} *p; } *p;
@ -1685,6 +1890,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)); 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. * Special message formats contain telemetry metadata.
@ -1697,23 +1958,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? * Why not use other characters after the "T" for metadata?
*/ */
if (strncmp(p->message,"PARM.",5) == 0) { else 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); 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; A->g_message_subtype = message_subtype_telem_parm;
telemetry_name_message (addressee, p->message+5); telemetry_name_message (addressee, p->message+5);
} }
else if (strncmp(p->message,"UNIT.",5) == 0) { 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; A->g_message_subtype = message_subtype_telem_unit;
telemetry_unit_label_message (addressee, p->message+5); telemetry_unit_label_message (addressee, p->message+5);
} }
else if (strncmp(p->message,"EQNS.",5) == 0) { 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; A->g_message_subtype = message_subtype_telem_eqns;
telemetry_coefficents_message (addressee, p->message+5, quiet); telemetry_coefficents_message (addressee, p->message+5, quiet);
} }
else if (strncmp(p->message,"BITS.",5) == 0) { 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; A->g_message_subtype = message_subtype_telem_bits;
telemetry_bit_sense_message (addressee, p->message+5, quiet); telemetry_bit_sense_message (addressee, p->message+5, quiet);
} }
@ -1737,11 +1998,23 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf("ERROR: \"%s\" must be lower case \"ack\"\n", p->message); 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)); else {
if (strlen(A->g_message_number) == 0) { 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); text_color_set(DW_COLOR_ERROR);
dw_printf("ERROR: Message number is missing after \"ack\".\n"); dw_printf("ERROR: Message number is missing after \"ack\".\n");
}
} }
// Xastir puts a carriage return on the end.
char *p = strchr(A->g_message_number, '\r');
if (p != NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf("The APRS protocol specification says nothing about a possible carriage return after the\n");
dw_printf("message id. Adding CR might prevent proper interoperability with with other applications.\n");
*p = '\0';
}
if (strlen(A->g_message_number) >= 3 && A->g_message_number[2] == '}') A->g_message_number[2] = '\0'; if (strlen(A->g_message_number) >= 3 && A->g_message_number[2] == '}') A->g_message_number[2] = '\0';
snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "\"%s\" ACKnowledged message number \"%s\" from \"%s\"", A->g_src, A->g_message_number, addressee); snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "\"%s\" ACKnowledged message number \"%s\" from \"%s\"", A->g_src, A->g_message_number, addressee);
A->g_message_subtype = message_subtype_ack; A->g_message_subtype = message_subtype_ack;
@ -1751,11 +2024,23 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf("ERROR: \"%s\" must be lower case \"rej\"\n", p->message); 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)); else {
if (strlen(A->g_message_number) == 0) { 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); text_color_set(DW_COLOR_ERROR);
dw_printf("ERROR: Message number is missing after \"rej\".\n"); dw_printf("ERROR: Message number is missing after \"rej\".\n");
}
} }
// Xastir puts a carriage return on the end.
char *p = strchr(A->g_message_number, '\r');
if (p != NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf("The APRS protocol specification says nothing about a possible carriage return after the\n");
dw_printf("message id. Adding CR might prevent proper interoperability with with other applications.\n");
*p = '\0';
}
if (strlen(A->g_message_number) >= 3 && A->g_message_number[2] == '}') A->g_message_number[2] = '\0'; if (strlen(A->g_message_number) >= 3 && A->g_message_number[2] == '}') A->g_message_number[2] = '\0';
snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "\"%s\" REJected message number \"%s\" from \"%s\"", A->g_src, A->g_message_number, addressee); snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "\"%s\" REJected message number \"%s\" from \"%s\"", A->g_src, A->g_message_number, addressee);
A->g_message_subtype = message_subtype_ack; A->g_message_subtype = message_subtype_ack;
@ -1776,16 +2061,26 @@ 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 // X>Y:}A>B::WA1XYX-15:Howdy y'all{toolong
else { else {
// Look for message number. // Normal messaage case. Look for message number.
char *pno = strchr(p->message, '{'); char *pno = strchr(p->message, '{');
if (pno != NULL) { if (pno != NULL) {
*pno = '\0'; strlcpy (A->g_message_number, pno+1, sizeof(A->g_message_number));
int mlen = strlen(pno+1);
// Xastir puts a carriage return on the end.
char *p = strchr(A->g_message_number, '\r');
if (p != NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf("The APRS protocol specification says nothing about a possible carriage return after the\n");
dw_printf("message id. Adding CR might prevent proper interoperability with with other applications.\n");
*p = '\0';
}
int mlen = strlen(A->g_message_number);
if (mlen < 1 || mlen > 5) { if (mlen < 1 || mlen > 5) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf("Message number \"%s\" has length outside range of 1 to 5.\n", pno+1); dw_printf("Message number \"%s\" has length outside range of 1 to 5.\n", A->g_message_number);
} }
strlcpy (A->g_message_number, pno+1, sizeof(A->g_message_number));
// TODO: Complain if not alphanumeric. // TODO: Complain if not alphanumeric.
char ack[8] = ""; char ack[8] = "";
@ -1815,6 +2110,11 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
/* No location so don't use process_comment () */ /* No location so don't use process_comment () */
strlcpy (A->g_comment, p->message, sizeof(A->g_comment)); strlcpy (A->g_comment, p->message, sizeof(A->g_comment));
// Remove message number when displaying message text.
pno = strchr(A->g_comment, '{');
if (pno != NULL) {
*pno = '\0';
}
} }
} }
@ -2314,6 +2614,20 @@ static void aprs_status_report (decode_aprs_t *A, char *info, int ilen)
* *
*------------------------------------------------------------------*/ *------------------------------------------------------------------*/
/*
https://groups.io/g/direwolf/topic/95961245#7357
What APRS queries should DireWolf respond to? Well, it should be configurable whether it responds to queries at all, in case some other application is using DireWolf as a dumb TNC (KISS or AGWPE style) and wants to handle the queries itself.
Assuming query responding is enabled, the following broadcast queries should be supported (if the corresponding data is configured in DireWolf):
?APRS (I am an APRS station)
?IGATE (I am operating as a I-gate)
?WX (I am providing local weather data in my beacon)
*/
static void aprs_general_query (decode_aprs_t *A, char *info, int ilen, int quiet) static void aprs_general_query (decode_aprs_t *A, char *info, int ilen, int quiet)
{ {
char *q2; char *q2;
@ -2466,6 +2780,28 @@ static void aprs_general_query (decode_aprs_t *A, char *info, int ilen, int quie
* *
*------------------------------------------------------------------*/ *------------------------------------------------------------------*/
/*
https://groups.io/g/direwolf/topic/95961245#7357
The following directed queries (sent as bodies of APRS text messages) would also be useful (if corresponding data configured):
?APRSP (force my current beacon)
?APRST and ?PING (trace my path to requestor)
?APRSD (all stations directly heard [no digipeat hops] by local station)
?APRSO (any Objects/Items originated by this station)
?APRSH (how often or how many times the specified 3rd station was heard by the queried station)
?APRSS (immediately send the Status message if configured) (can DireWolf do Status messages?)
Lynn KJ4ERJ and I have implemented a non-standard query which might be useful:
?VER (send the human-readable software version of the queried station)
Hope this is useful. It's just my $.02.
Andrew, KA2DDO
author of YAAC
*/
static void aprs_directed_station_query (decode_aprs_t *A, char *addressee, char *query, int quiet) static void aprs_directed_station_query (decode_aprs_t *A, char *addressee, char *query, int quiet)
{ {
//char query_type[20]; /* Does the query type always need to be exactly 5 characters? */ //char query_type[20]; /* Does the query type always need to be exactly 5 characters? */
@ -3743,9 +4079,14 @@ time_t get_timestamp (decode_aprs_t *A, char *p)
* It is composed of: * It is composed of:
* a pair of letters in range A to R. * a pair of letters in range A to R.
* a pair of digits in range of 0 to 9. * a pair of digits in range of 0 to 9.
* a pair of letters in range of A to X. * an optional pair of letters in range of A to X.
* *
* The APRS spec says that all letters must be transmitted in upper case. * The spec says:
* "All letters must be transmitted in upper case.
* Letters may be received in upper case or lower case."
*
* Typically the second set of letters is written in lower case.
* An earlier version incorrectly produced an error if lower case found.
* *
* *
* Examples from APRS spec: * Examples from APRS spec:
@ -3766,25 +4107,10 @@ int get_maidenhead (decode_aprs_t *A, char *p)
/* We have 4 characters matching the rule. */ /* We have 4 characters matching the rule. */
if (islower(p[0]) || islower(p[1])) {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Warning: Lower case letter in Maidenhead locator. Specification requires upper case.\n");
}
}
if (toupper(p[4]) >= 'A' && toupper(p[4]) <= 'X' && if (toupper(p[4]) >= 'A' && toupper(p[4]) <= 'X' &&
toupper(p[5]) >= 'A' && toupper(p[5]) <= 'X') { toupper(p[5]) >= 'A' && toupper(p[5]) <= 'X') {
/* We have 6 characters matching the rule. */ /* We have 6 characters matching the rule. */
if (islower(p[4]) || islower(p[5])) {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Warning: Lower case letter in Maidenhead locator. Specification requires upper case.\n");
}
}
return 6; return 6;
} }
@ -4113,11 +4439,8 @@ static void decode_tocall (decode_aprs_t *A, char *dest)
* models before getting to the more generic APY. * models before getting to the more generic APY.
*/ */
#if defined(__WIN32__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__APPLE__)
qsort (tocalls, num_tocalls, sizeof(struct tocalls_s), tocall_cmp); qsort (tocalls, num_tocalls, sizeof(struct tocalls_s), tocall_cmp);
#else
qsort (tocalls, num_tocalls, sizeof(struct tocalls_s), (__compar_fn_t)tocall_cmp);
#endif
} }
else { else {
if ( ! A->g_quiet) { if ( ! A->g_quiet) {
@ -4643,7 +4966,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen)
int a = A->g_comment[match[0].rm_so+2]; int a = A->g_comment[match[0].rm_so+2];
int o = A->g_comment[match[0].rm_so+3]; int o = A->g_comment[match[0].rm_so+3];
dw_printf("DAO start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo)); //dw_printf("DAO start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo));
/* /*
@ -5029,7 +5352,9 @@ int main (int argc, char *argv[])
/* Try to process it. */ /* Try to process it. */
text_color_set(DW_COLOR_REC); text_color_set(DW_COLOR_REC);
dw_printf("\n%s\n", stuff); dw_printf("\n");
ax25_safe_print (stuff, -1, 0);
dw_printf("\n");
// Do we have monitor format, KISS, or AX.25 frame? // Do we have monitor format, KISS, or AX.25 frame?
@ -5138,7 +5463,7 @@ int main (int argc, char *argv[])
ax25_safe_print ((char *)pinfo, info_len, 1); // Display non-ASCII to hexadecimal. ax25_safe_print ((char *)pinfo, info_len, 1); // Display non-ASCII to hexadecimal.
dw_printf ("\n"); dw_printf ("\n");
decode_aprs (&A, pp, 0, 0); // Extract information into structure. decode_aprs (&A, pp, 0, NULL); // Extract information into structure.
decode_aprs_print (&A); // Now print it in human readable format. decode_aprs_print (&A); // Now print it in human readable format.
@ -5159,7 +5484,7 @@ int main (int argc, char *argv[])
if (pp != NULL) { if (pp != NULL) {
decode_aprs_t A; decode_aprs_t A;
decode_aprs (&A, pp, 0, 0); // Extract information into structure. decode_aprs (&A, pp, 0, NULL); // Extract information into structure.
decode_aprs_print (&A); // Now print it in human readable format. decode_aprs_print (&A); // Now print it in human readable format.

View File

@ -24,7 +24,8 @@ typedef struct decode_aprs_s {
int g_quiet; /* Suppress error messages when decoding. */ 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]; char g_dest[AX25_MAX_ADDR_LEN];
@ -66,10 +67,30 @@ typedef struct decode_aprs_s {
/* Also for Directed Station Query which is a */ /* Also for Directed Station Query which is a */
/* special case of message. */ /* 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, enum message_subtype_e { message_subtype_invalid = 0,
message_subtype_message, message_subtype_message,
message_subtype_ack, message_subtype_ack,
message_subtype_rej, message_subtype_rej,
message_subtype_bulletin,
message_subtype_nws,
message_subtype_telem_parm, message_subtype_telem_parm,
message_subtype_telem_unit, message_subtype_telem_unit,
message_subtype_telem_eqns, message_subtype_telem_eqns,
@ -77,7 +98,7 @@ typedef struct decode_aprs_s {
message_subtype_directed_query message_subtype_directed_query
} g_message_subtype; /* Various cases of the overloaded "message." */ } g_message_subtype; /* Various cases of the overloaded "message." */
char g_message_number[8]; /* Message number. Should be 1 - 5 alphanumeric characters if used. */ char g_message_number[12]; /* Message number. Should be 1 - 5 alphanumeric characters if used. */
/* Addendum 1.1 has new format {mm} or {mm}aa with only two */ /* Addendum 1.1 has new format {mm} or {mm}aa with only two */
/* characters for message number and an ack riding piggyback. */ /* characters for message number and an ack riding piggyback. */
@ -90,8 +111,9 @@ typedef struct decode_aprs_s {
int g_power; /* Transmitter power in watts. */ int g_power; /* Transmitter power in watts. */
int g_height; /* Antenna height above average terrain, feet. */ 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 */ 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. */ float g_altitude_ft; /* Feet above median sea level. */
/* I used feet here because the APRS specification */ /* 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. */ /* more natural to the other 96% of the world. */
char g_mfr[80]; /* Manufacturer or application. */ char g_mfr[80]; /* Manufacturer or application. */
@ -142,7 +164,7 @@ typedef struct decode_aprs_s {
extern void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, int third_party); extern void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_src);
extern void decode_aprs_print (decode_aprs_t *A); extern void decode_aprs_print (decode_aprs_t *A);

View File

@ -832,7 +832,7 @@ int demod_init (struct audio_s *pa)
* *
* Name: demod_get_sample * Name: demod_get_sample
* *
* Purpose: Get one audio sample fromt the specified sound input source. * Purpose: Get one audio sample from the specified sound input source.
* *
* Inputs: a - Index for audio device. 0 = first. * Inputs: a - Index for audio device. 0 = first.
* *
@ -852,7 +852,6 @@ int demod_init (struct audio_s *pa)
#define FSK_READ_ERR (256*256) #define FSK_READ_ERR (256*256)
__attribute__((hot)) __attribute__((hot))
int demod_get_sample (int a) 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); 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) { if (save_audio_config_p->adev[a].bits_per_sample == 8) {
x1 = audio_get(a); 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)) __attribute__((hot))
void demod_process_sample (int chan, int subchan, int sam) 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 (chan >= 0 && chan < MAX_CHANS);
assert (subchan >= 0 && subchan < MAX_SUBCHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS);
if (mute_input[chan]) {
sam = 0;
};
D = &demodulator_state[chan][subchan]; D = &demodulator_state[chan][subchan];

View File

@ -8,6 +8,8 @@
int demod_init (struct audio_s *pa); int demod_init (struct audio_s *pa);
void demod_mute_input (int chan, int mute);
int demod_get_sample (int a); int demod_get_sample (int a);
void demod_process_sample (int chan, int subchan, int sam); void demod_process_sample (int chan, int subchan, int sam);
@ -15,3 +17,4 @@ void demod_process_sample (int chan, int subchan, int sam);
void demod_print_agc (int chan, int subchan); void demod_print_agc (int chan, int subchan);
alevel_t demod_get_audio_level (int chan, int subchan); alevel_t demod_get_audio_level (int chan, int subchan);

View File

@ -309,10 +309,6 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq,
D->lp_window = BP_WINDOW_TRUNCATED; D->lp_window = BP_WINDOW_TRUNCATED;
} }
D->agc_fast_attack = 0.820;
D->agc_slow_decay = 0.000214;
D->agc_fast_attack = 0.45;
D->agc_slow_decay = 0.000195;
D->agc_fast_attack = 0.70; D->agc_fast_attack = 0.70;
D->agc_slow_decay = 0.000090; D->agc_slow_decay = 0.000090;
@ -372,10 +368,16 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq,
// For scaling phase shift into normallized -1 to +1 range for mark and space. // For scaling phase shift into normallized -1 to +1 range for mark and space.
D->u.afsk.normalize_rpsam = 1.0 / (0.5 * abs(mark_freq - space_freq) * 2 * M_PI / samples_per_sec); D->u.afsk.normalize_rpsam = 1.0 / (0.5 * abs(mark_freq - space_freq) * 2 * M_PI / samples_per_sec);
// New "B" demodulator does not use AGC but demod.c needs this to derive "quick" and
// "sluggish" values for overall signal amplitude. That probably should be independent
// of these values.
D->agc_fast_attack = 0.70;
D->agc_slow_decay = 0.000090;
D->pll_locked_inertia = 0.74; D->pll_locked_inertia = 0.74;
D->pll_searching_inertia = 0.50; D->pll_searching_inertia = 0.50;
D->alevel_mark_peak = -1; // FIXME: disable display D->alevel_mark_peak = -1; // Disable received signal (m/s) display.
D->alevel_space_peak = -1; D->alevel_space_peak = -1;
break; break;
@ -868,6 +870,7 @@ static void nudge_pll (int chan, int subchan, int slice, float demod_out, struct
{ {
D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll; D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll;
// Perform the add as unsigned to avoid signed overflow error. // Perform the add as unsigned to avoid signed overflow error.
D->slicer[slice].data_clock_pll = (signed)((unsigned)(D->slicer[slice].data_clock_pll) + (unsigned)(D->pll_step_per_sample)); D->slicer[slice].data_clock_pll = (signed)((unsigned)(D->slicer[slice].data_clock_pll) + (unsigned)(D->pll_step_per_sample));
@ -901,7 +904,15 @@ static void nudge_pll (int chan, int subchan, int slice, float demod_out, struct
#endif #endif
#if 1
hdlc_rec_bit (chan, subchan, slice, demod_out > 0, 0, quality); hdlc_rec_bit (chan, subchan, slice, demod_out > 0, 0, quality);
#else // TODO: new feature to measure data speed error.
// Maybe hdlc_rec_bit could provide indication when frame starts.
hdlc_rec_bit_new (chan, subchan, slice, demod_out > 0, 0, quality,
&(D->slicer[slice].pll_nudge_total), &(D->slicer[slice].pll_symbol_count));
D->slicer[slice].pll_symbol_count++;
#endif
pll_dcd_each_symbol2 (D, chan, subchan, slice); pll_dcd_each_symbol2 (D, chan, subchan, slice);
} }
@ -912,12 +923,14 @@ static void nudge_pll (int chan, int subchan, int slice, float demod_out, struct
pll_dcd_signal_transition2 (D, slice, D->slicer[slice].data_clock_pll); pll_dcd_signal_transition2 (D, slice, D->slicer[slice].data_clock_pll);
// TODO: signed int before = (signed int)(D->slicer[slice].data_clock_pll); // Treat as signed.
if (D->slicer[slice].data_detect) { if (D->slicer[slice].data_detect) {
D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia); D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia);
} }
else { else {
D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_searching_inertia); D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_searching_inertia);
} }
// TODO: D->slicer[slice].pll_nudge_total += (int64_t)((signed int)(D->slicer[slice].data_clock_pll)) - (int64_t)before;
} }
/* /*

View File

@ -49,6 +49,7 @@
* Preemptive Digipeating (new in version 0.8) * Preemptive Digipeating (new in version 0.8)
* *
* http://www.aprs.org/aprs12/preemptive-digipeating.txt * http://www.aprs.org/aprs12/preemptive-digipeating.txt
* I ignored the part about the RR bits.
* *
*------------------------------------------------------------------*/ *------------------------------------------------------------------*/
@ -164,7 +165,34 @@ void digipeater (int from_chan, packet_t pp)
/* /*
* First pass: Look at packets being digipeated to same channel. * 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_chan<MAX_CHANS; to_chan++) { for (to_chan=0; to_chan<MAX_CHANS; to_chan++) {
@ -180,7 +208,7 @@ void digipeater (int from_chan, packet_t pp)
save_digi_config_p->filter_str[from_chan][to_chan]); save_digi_config_p->filter_str[from_chan][to_chan]);
if (result != NULL) { if (result != NULL) {
dedupe_remember (pp, to_chan); 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]++; 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]); save_digi_config_p->filter_str[from_chan][to_chan]);
if (result != NULL) { if (result != NULL) {
dedupe_remember (pp, to_chan); 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]++; 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 * If preemptive digipeating is enabled, try matching my call
* and aliases against all remaining unused digipeaters. * 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) { 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) { switch (preempt) {
case PREEMPT_DROP: /* remove all prior */ 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) { while (r2 > AX25_REPEATER_1) {
ax25_remove_addr (result, r2-1); ax25_remove_addr (result, r2-1);
r2--; r2--;
} }
break; 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--; r2--;
while (r2 >= AX25_REPEATER_1 && ax25_get_h(result,r2) == 0) { while (r2 >= AX25_REPEATER_1 && ax25_get_h(result,r2) == 0) {
ax25_set_h (result, r2); 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; 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: default:
while (r2 > AX25_REPEATER_1 && ax25_get_h(result,r2-1) == 0) { while (r2 > AX25_REPEATER_1 && ax25_get_h(result,r2-1) == 0) {
ax25_remove_addr (result, r2-1); ax25_remove_addr (result, r2-1);

View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // 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 // 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 // it under the terms of the GNU General Public License as published by
@ -128,6 +128,7 @@
#include "il2p.h" #include "il2p.h"
#include "dwsock.h" #include "dwsock.h"
#include "dns_sd_dw.h" #include "dns_sd_dw.h"
#include "dlq.h" // for fec_type_t definition.
//static int idx_decoded = 0; //static int idx_decoded = 0;
@ -138,7 +139,7 @@ static BOOL cleanup_win (int);
static void cleanup_linux (int); static void cleanup_linux (int);
#endif #endif
static void usage (char **argv); static void usage ();
#if defined(__SSE__) && !defined(__APPLE__) #if defined(__SSE__) && !defined(__APPLE__)
@ -185,7 +186,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 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_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." */ static int A_opt_ais_to_obj = 0; /* "-A" Convert received AIS to APRS "Object Report." */
@ -283,6 +284,8 @@ int main (int argc, char *argv[])
/* 1 = normal, 0 = no text colors. */ /* 1 = normal, 0 = no text colors. */
/* 2, 3, ... alternate escape sequences for different terminals. */ /* 2, 3, ... alternate escape sequences for different terminals. */
// FIXME: consider case of no space between t and number.
for (j=1; j<argc-1; j++) { for (j=1; j<argc-1; j++) {
if (strcmp(argv[j], "-t") == 0) { if (strcmp(argv[j], "-t") == 0) {
t_opt = atoi (argv[j+1]); t_opt = atoi (argv[j+1]);
@ -298,25 +301,28 @@ int main (int argc, char *argv[])
text_color_init(t_opt); text_color_init(t_opt);
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
//dw_printf ("Dire Wolf version %d.%d (%s) Beta Test 4\n", MAJOR_VERSION, MINOR_VERSION, __DATE__); //dw_printf ("Dire Wolf version %d.%d (%s) BETA TEST 7\n", MAJOR_VERSION, MINOR_VERSION, __DATE__);
dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "E", __DATE__); dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "A", __DATE__);
//dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION); //dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION);
#if defined(ENABLE_GPSD) || defined(USE_HAMLIB) || defined(USE_CM108) || USE_AVAHI_CLIENT || USE_MACOS_DNSSD #if defined(ENABLE_GPSD) || defined(USE_HAMLIB) || defined(USE_CM108) || USE_AVAHI_CLIENT || USE_MACOS_DNSSD || USE_GPIOD
dw_printf ("Includes optional support for: "); dw_printf ("Includes optional support for: ");
#if defined(ENABLE_GPSD) #if defined(ENABLE_GPSD)
dw_printf (" gpsd"); dw_printf (" gpsd");
#endif #endif
#if defined(USE_HAMLIB) #if defined(USE_HAMLIB)
dw_printf (" hamlib"); dw_printf (" hamlib");
#endif #endif
#if defined(USE_CM108) #if defined(USE_CM108)
dw_printf (" cm108-ptt"); dw_printf (" cm108-ptt");
#endif #endif
#if (USE_AVAHI_CLIENT|USE_MACOS_DNSSD) #if defined(USE_GPIOD)
dw_printf (" libgpiod");
#endif
#if (USE_AVAHI_CLIENT|USE_MACOS_DNSSD)
dw_printf (" dns-sd"); dw_printf (" dns-sd");
#endif #endif
dw_printf ("\n"); dw_printf ("\n");
#endif #endif
@ -374,16 +380,19 @@ int main (int argc, char *argv[])
// I've seen many references to people running this as root. // I've seen many references to people running this as root.
// There is no reason to do that. // There is no reason to do that.
// There is for some privileges to access the audio system, GPIO (if needed for PTT), // Ordinary users can access audio, gpio, etc. if they are in the correct groups.
// etc. but ordinary users have those abilities.
// Giving an applications permission to do things it does not need to do // Giving an applications permission to do things it does not need to do
// is a huge security risk. // is a huge security risk.
#ifndef __WIN32__ #ifndef __WIN32__
if (getuid() == 0 || geteuid() == 0) { if (getuid() == 0 || geteuid() == 0) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Dire Wolf requires only privileges available to ordinary users.\n"); for (int n=0; n<15; n++) {
dw_printf ("Running this as root is an unnecessary security risk.\n"); dw_printf ("\n");
dw_printf ("Dire Wolf requires only privileges available to ordinary users.\n");
dw_printf ("Running this as root is an unnecessary security risk.\n");
//SLEEP_SEC(1);
}
} }
#endif #endif
@ -593,7 +602,7 @@ int main (int argc, char *argv[])
case '?': case '?':
/* For '?' unknown option message was already printed. */ /* For '?' unknown option message was already printed. */
usage (argv); usage ();
break; break;
case 'd': /* Set debug option. */ case 'd': /* Set debug option. */
@ -737,7 +746,7 @@ int main (int argc, char *argv[])
/* Should not be here. */ /* Should not be here. */
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf("?? getopt returned character code 0%o ??\n", c); dw_printf("?? getopt returned character code 0%o ??\n", c);
usage (argv); usage ();
} }
} /* end while(1) for options */ } /* end while(1) for options */
@ -982,6 +991,7 @@ int main (int argc, char *argv[])
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Pointless to continue without audio device.\n"); dw_printf ("Pointless to continue without audio device.\n");
SLEEP_SEC(5); SLEEP_SEC(5);
usage ();
exit (1); exit (1);
} }
@ -1053,7 +1063,7 @@ int main (int argc, char *argv[])
audio_config.achan[x_opt_chan].mark_freq, audio_config.achan[x_opt_chan].mark_freq,
x_opt_chan); x_opt_chan);
while (n-- > 0) { while (n-- > 0) {
tone_gen_put_bit(x_opt_chan, 0); tone_gen_put_bit(x_opt_chan, 1);
} }
break; break;
case 's': // "Space" tone: -x s case 's': // "Space" tone: -x s
@ -1061,7 +1071,7 @@ int main (int argc, char *argv[])
audio_config.achan[x_opt_chan].space_freq, audio_config.achan[x_opt_chan].space_freq,
x_opt_chan); x_opt_chan);
while (n-- > 0) { while (n-- > 0) {
tone_gen_put_bit(x_opt_chan, 1); tone_gen_put_bit(x_opt_chan, 0);
} }
break; break;
case 'p': // Silence - set PTT only: -x p case 'p': // Silence - set PTT only: -x p
@ -1173,7 +1183,7 @@ int main (int argc, char *argv[])
// TODO: Use only one printf per line so output doesn't get jumbled up with stuff from other threads. // TODO: Use only one printf per line so output doesn't get jumbled up with stuff from other threads.
void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, int is_fx25, retry_t retries, char *spectrum) void app_process_rec_packet (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]; char stemp[500];
@ -1182,7 +1192,8 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
char heard[AX25_MAX_ADDR_LEN]; char heard[AX25_MAX_ADDR_LEN];
//int j; //int j;
int h; int h;
char display_retries[32]; char display_retries[32]; // Extra stuff before slice indicators.
// Can indicate FX.25/IL2P or fix_bits.
assert (chan >= 0 && chan < MAX_TOTAL_CHANS); // TOTAL for virtual channels assert (chan >= 0 && chan < MAX_TOTAL_CHANS); // TOTAL for virtual channels
assert (subchan >= -2 && subchan < MAX_SUBCHANS); assert (subchan >= -2 && subchan < MAX_SUBCHANS);
@ -1191,12 +1202,21 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
strlcpy (display_retries, "", sizeof(display_retries)); strlcpy (display_retries, "", sizeof(display_retries));
if (is_fx25) { switch (fec_type) {
; case fec_type_fx25:
} strlcpy (display_retries, " FX.25 ", sizeof(display_retries));
else if (audio_config.achan[chan].fix_bits != RETRY_NONE || audio_config.achan[chan].passall) { break;
assert (retries >= RETRY_NONE && retries <= RETRY_MAX); case fec_type_il2p:
snprintf (display_retries, sizeof(display_retries), " [%s] ", retry_text[(int)retries]); 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); ax25_format_addrs (pp, stemp);
@ -1426,7 +1446,7 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
// we still want to decode it for logging and other processing. // we still want to decode it for logging and other processing.
// Just be quiet about errors if "-qd" is set. // Just be quiet about errors if "-qd" is set.
decode_aprs (&A, pp, q_d_opt, 0); decode_aprs (&A, pp, q_d_opt, NULL);
if ( ! q_d_opt ) { if ( ! q_d_opt ) {
@ -1554,10 +1574,14 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
} }
else { else {
/* Send to Internet server if option is enabled. */ /*
/* Consider only those with correct CRC. */ * Send to the IGate processing.
* Use only those with correct CRC; We don't want to spread corrupted data!
if (ax25_is_aprs(pp) && retries == RETRY_NONE) { * Our earlier "fix bits" hack could allow corrupted information to get thru.
* 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 || fec_type == fec_type_fx25 || fec_type == fec_type_il2p) ) {
igate_send_rec_packet (chan, pp); igate_send_rec_packet (chan, pp);
} }
@ -1572,24 +1596,23 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
/* /*
* APRS digipeater. * Send to APRS digipeater.
* Use only those with correct CRC; We don't want to spread corrupted data! * Use only those with correct CRC; We don't want to spread corrupted data!
* Our earlier "fix bits" hack could allow corrupted information to get thru.
* 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 || fec_type == fec_type_fx25 || fec_type == fec_type_il2p) ) {
// TODO: Should also use anything received with FX.25 because it is known to be good.
// Our earlier "fix bits" hack could allow corrupted information to get thru.
if (ax25_is_aprs(pp) && retries == RETRY_NONE) {
digipeater (chan, pp); digipeater (chan, pp);
} }
/* /*
* Connected mode digipeater. * Connected mode digipeater.
* Use only those with correct CRC. * Use only those with correct CRC (or using FEC.)
*/ */
if (retries == RETRY_NONE) { if (retries == RETRY_NONE || fec_type == fec_type_fx25 || fec_type == fec_type_il2p) {
cdigipeater (chan, pp); cdigipeater (chan, pp);
} }
@ -1688,7 +1711,7 @@ static void usage (char **argv)
dw_printf (" d d = APRStt (DTMF to APRS object translation).\n"); dw_printf (" d d = APRStt (DTMF to APRS object translation).\n");
dw_printf (" -q Quiet (suppress output) options:\n"); dw_printf (" -q Quiet (suppress output) options:\n");
dw_printf (" h h = Heard line with the audio level.\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 (" x x = Silence FX.25 information.\n");
dw_printf (" -t n Text colors. 0=disabled. 1=default. 2,3,4,... alternatives.\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"); dw_printf (" Use 9 to test compatibility with your terminal.\n");
@ -1710,20 +1733,20 @@ static void usage (char **argv)
dw_printf ("\n"); dw_printf ("\n");
dw_printf ("After any options, there can be a single command line argument for the source of\n"); dw_printf ("After any options, there can be a single command line argument for the source of\n");
dw_printf ("received audio. This can overrides the audio input specified in the configuration file.\n"); dw_printf ("received audio. This can override the audio input specified in the configuration file.\n");
dw_printf ("\n"); dw_printf ("\n");
#if __WIN32__ #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 #else
// TODO: Could vary by platform and build options. // 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 #endif
dw_printf ("or online at https://github.com/wb2osz/direwolf/tree/master/doc\n"); 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); text_color_set(DW_COLOR_INFO);
exit (EXIT_FAILURE); exit (EXIT_FAILURE);
} }
/* end direwolf.c */ /* end direwolf.c */

View File

@ -38,9 +38,7 @@
#endif #endif
/* /*
* Previously, we could handle only a single audio device. * Maximum number of audio devices.
* This meant we could have only two radio channels.
* In version 1.2, we relax this restriction and allow more audio devices.
* Three is probably adequate for standard version. * Three is probably adequate for standard version.
* Larger reasonable numbers should also be fine. * Larger reasonable numbers should also be fine.
* *
@ -280,6 +278,8 @@ typedef pthread_mutex_t dw_mutex_t;
/* Platform differences for string functions. */ /* 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__ #if __WIN32__
char *strsep(char **stringp, const char *delim); char *strsep(char **stringp, const char *delim);
@ -287,13 +287,49 @@ char *strtok_r(char *str, const char *delim, char **saveptr);
#endif #endif
// Don't recall why I added this for everyone rather than only for Windows. // 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); char *strcasestr(const char *S, const char *FIND);
// cmake determines whether strlcpy and strlcat are available // cmake tries to determine whether strlcpy and strlcat are provided by the C runtime library.
// or if we need to supply our own. //
// ../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. #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. #ifndef HAVE_STRLCPY // Need to supply our own.
#if DEBUG_STRL #if DEBUG_STRL

View File

@ -215,10 +215,10 @@ void dlq_init (void)
* display of audio level line. * display of audio level line.
* Use -2 to indicate DTMF message.) * 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. * meaning of retries is different.
* *
* retries - Level of bit correction used. * retries - Level of correction used.
* *
* spectrum - Display of how well multiple decoders did. * 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; 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->subchan = subchan;
pnew->pp = pp; pnew->pp = pp;
pnew->alevel = alevel; pnew->alevel = alevel;
pnew->is_fx25 = is_fx25; pnew->fec_type = fec_type;
pnew->retries = retries; pnew->retries = retries;
if (spectrum == NULL) if (spectrum == NULL)
strlcpy(pnew->spectrum, "", sizeof(pnew->spectrum)); 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. * Purpose: Register callsigns that we will recognize for incoming connection requests.
* *
* Inputs: addrs - Source (owncall), destination (peercall), * Inputs: addr - Callsign to [un]register.
* and possibly digipeaters.
* *
* chan - Channel, 0 is first. * 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; 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->type = DLQ_REGISTER_CALLSIGN;
pnew->chan = chan; 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->num_addr = 1;
pnew->client = client; 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 */ } /* 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; 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->type = DLQ_UNREGISTER_CALLSIGN;
pnew->chan = chan; 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->num_addr = 1;
pnew->client = client; pnew->client = client;

View File

@ -33,10 +33,13 @@ typedef struct cdata_s {
/* Types of things that can be in queue. */ /* 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 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. */ /* A queue item. */
@ -68,7 +71,7 @@ typedef struct dlq_item_s {
alevel_t alevel; /* Audio level. */ 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. */ retry_t retries; /* Effort expended to get a valid CRC. */
/* Bits changed for regular AX.25. */ /* 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); 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_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); void dlq_channel_busy (int chan, int activity, int status);

View File

@ -25,9 +25,9 @@
/*------------------------------------------------------------------ /*------------------------------------------------------------------
* *
* Name: dtime_now * Name: dtime_realtime
* *
* Purpose: Return current time as double precision. * Purpose: Return current wall clock time as double precision.
* *
* Input: none * Input: none
* *
@ -41,10 +41,23 @@
* simply use double precision floating point to make usage * simply use double precision floating point to make usage
* easier. * easier.
* *
* NOTE: This is not a good way to calculate elapsed time because
* it can jump forward or backware via NTP or other manual setting.
*
* Use the monotonic version for measuring elapsed time.
*
* History: Originally I called this dtime_now. We ran into issues where
* we really cared about elapsed time, rather than wall clock time.
* The wall clock time could be wrong at start up time if there
* is no realtime clock or Internet access. It can then jump
* when GPS time or Internet access becomes available.
* All instances of dtime_now should be replaced by dtime_realtime
* if we want wall clock time, or dtime_monotonic if it is to be
* used for measuring elapsed time, such as between becons.
*
*---------------------------------------------------------------*/ *---------------------------------------------------------------*/
double dtime_realtime (void)
double dtime_now (void)
{ {
double result; double result;
@ -63,6 +76,10 @@ double dtime_now (void)
struct timespec ts; struct timespec ts;
#ifdef __APPLE__ #ifdef __APPLE__
// Why didn't I use clock_gettime?
// Not available before Max OSX 10.12? https://github.com/gambit/gambit/issues/293
struct timeval tp; struct timeval tp;
gettimeofday(&tp, NULL); gettimeofday(&tp, NULL);
ts.tv_nsec = tp.tv_usec * 1000; ts.tv_nsec = tp.tv_usec * 1000;
@ -75,6 +92,83 @@ double dtime_now (void)
#endif #endif
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("dtime_realtime() returns %.3f\n", result );
#endif
return (result);
}
/*------------------------------------------------------------------
*
* Name: dtime_monotonic
*
* Purpose: Return montonically increasing time, which is not influenced
* by the wall clock changing. e.g. leap seconds, NTP adjustments.
*
* Input: none
*
* Returns: Time as double precision, so we can get resolution
* finer than one second.
*
* Description: Use this when calculating elapsed time.
*
*---------------------------------------------------------------*/
double dtime_monotonic (void)
{
double result;
#if __WIN32__
// FIXME:
// This is still returning wall clock time.
// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount64
// GetTickCount64 would be ideal but it requires Vista or Server 2008.
// As far as I know, the current version of direwolf still works on XP.
//
// As a work-around, GetTickCount could be used if we add extra code to deal
// with the wrap around after about 49.7 days.
// Resolution is only about 10 or 16 milliseconds. Is that good enough?
/* 64 bit integer is number of 100 nanosecond intervals from Jan 1, 1601. */
FILETIME ft;
GetSystemTimeAsFileTime (&ft);
result = ((( (double)ft.dwHighDateTime * (256. * 256. * 256. * 256.) +
(double)ft.dwLowDateTime ) / 10000000.) - 11644473600.);
#else
/* tv_sec is seconds from Jan 1, 1970. */
struct timespec ts;
#ifdef __APPLE__
// FIXME: Does MacOS have a monotonically increasing time?
// https://stackoverflow.com/questions/41509505/clock-gettime-on-macos
struct timeval tp;
gettimeofday(&tp, NULL);
ts.tv_nsec = tp.tv_usec * 1000;
ts.tv_sec = tp.tv_sec;
#else
// This is the only case handled properly.
// Probably the only one that matters.
// It is common to have a Raspberry Pi, without Internet,
// starting up direwolf before GPS/NTP adjusts the time.
clock_gettime (CLOCK_MONOTONIC, &ts);
#endif
result = ((double)(ts.tv_sec) + (double)(ts.tv_nsec) * 0.000000001);
#endif
#if DEBUG #if DEBUG
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("dtime_now() returns %.3f\n", result ); dw_printf ("dtime_now() returns %.3f\n", result );
@ -84,6 +178,7 @@ double dtime_now (void)
} }
/*------------------------------------------------------------------ /*------------------------------------------------------------------
* *
* Name: timestamp_now * Name: timestamp_now
@ -104,7 +199,7 @@ double dtime_now (void)
void timestamp_now (char *result, int result_size, int show_ms) void timestamp_now (char *result, int result_size, int show_ms)
{ {
double now = dtime_now(); double now = dtime_realtime();
time_t t = (int)now; time_t t = (int)now;
struct tm tm; struct tm tm;
@ -150,7 +245,7 @@ void timestamp_now (char *result, int result_size, int show_ms)
void timestamp_user_format (char *result, int result_size, char *user_format) void timestamp_user_format (char *result, int result_size, char *user_format)
{ {
double now = dtime_now(); double now = dtime_realtime();
time_t t = (int)now; time_t t = (int)now;
struct tm tm; struct tm tm;
@ -191,7 +286,7 @@ void timestamp_user_format (char *result, int result_size, char *user_format)
void timestamp_filename (char *result, int result_size) void timestamp_filename (char *result, int result_size)
{ {
double now = dtime_now(); double now = dtime_realtime();
time_t t = (int)now; time_t t = (int)now;
struct tm tm; struct tm tm;

View File

@ -1,9 +1,18 @@
extern double dtime_now (void); extern double dtime_realtime (void);
extern double dtime_monotonic (void);
void timestamp_now (char *result, int result_size, int show_ms); void timestamp_now (char *result, int result_size, int show_ms);
void timestamp_user_format (char *result, int result_size, char *user_format); void timestamp_user_format (char *result, int result_size, char *user_format);
void timestamp_filename (char *result, int result_size); void timestamp_filename (char *result, int result_size);
// FIXME: remove temp workaround.
// Needs many scattered updates.
#define dtime_now dtime_realtime

View File

@ -57,17 +57,36 @@
// An incompatibility was introduced with version 7 // An API incompatibility was introduced with API version 7.
// and again with 9 and again with 10. // and again with 9.
// and again with 10.
// We deal with it by using a bunch of conditional code such as:
// #if GPSD_API_MAJOR_VERSION >= 9
// release lib version API Raspberry Pi OS
// 3.22 28 11 bullseye
// 3.23 29 12
#if GPSD_API_MAJOR_VERSION < 5 || GPSD_API_MAJOR_VERSION > 12 // release lib version API Raspberry Pi OS Testing status
#error libgps API version might be incompatible. // 3.22 28 11 bullseye OK.
// 3.23 29 12 OK.
// 3.25 30 14 OK, Jan. 2023
// Previously the compilation would fail if the API version was later
// than the last one tested. Now it is just a warning because it changes so
// often but more recent versions have not broken backward compatibility.
#define MAX_TESTED_VERSION 14
#if (GPSD_API_MAJOR_VERSION < 5) || (GPSD_API_MAJOR_VERSION > MAX_TESTED_VERSION)
#pragma message "Your version of gpsd might be incompatible with this application."
#pragma message "The libgps application program interface (API) often"
#pragma message "changes to be incompatible with earlier versions."
// I could not figure out how to do value substitution here.
#pragma message "You have libgpsd API version GPSD_API_MAJOR_VERSION."
#pragma message "The last that has been tested is MAX_TESTED_VERSION."
#pragma message "Even if this builds successfully, it might not run properly."
#endif #endif
/* /*
* Information for interface to gpsd daemon. * Information for interface to gpsd daemon.
*/ */
@ -167,6 +186,22 @@ static void * read_gpsd_thread (void *arg);
* can't find it there. Solution is to define environment variable: * can't find it there. Solution is to define environment variable:
* *
* export LD_LIBRARY_PATH=/use/local/lib * export LD_LIBRARY_PATH=/use/local/lib
*
* January 2023: Now using 64 bit Raspberry Pi OS, bullseye.
* See https://gitlab.com/gpsd/gpsd/-/blob/master/build.adoc
* Try to install in proper library place so we don't have to mess with LD_LIBRARY_PATH.
*
* (Remove any existing gpsd first so we are not mixing mismatched pieces.)
*
* sudo apt-get install libncurses5-dev
* sudo apt-get install gtk+-3.0
*
* git clone https://gitlab.com/gpsd/gpsd.git gpsd-gitlab
* cd gpsd-gitlab
* scons prefix=/usr libdir=lib/aarch64-linux-gnu
* [ scons check ]
* sudo scons udev-install
*
*/ */

View File

@ -551,6 +551,15 @@ int encode_position (int messaging, int compressed, double lat, double lon, int
int result_len = 0; int result_len = 0;
if (compressed) { if (compressed) {
// Thought:
// https://groups.io/g/direwolf/topic/92718535#6886
// When speed is zero, we could put the altitude in the compressed
// position rather than having /A=999999.
// However, the resolution would be decreased and that could be important
// when hiking in hilly terrain. It would also be confusing to
// flip back and forth between two different representations.
aprs_compressed_pos_t *p = (aprs_compressed_pos_t *)presult; aprs_compressed_pos_t *p = (aprs_compressed_pos_t *)presult;
p->dti = messaging ? '=' : '!'; p->dti = messaging ? '=' : '!';

View File

@ -2,6 +2,8 @@
#ifndef FSK_DEMOD_STATE_H #ifndef FSK_DEMOD_STATE_H
#include <stdint.h> // int64_t
#include "rpack.h" #include "rpack.h"
#include "audio.h" // for enum modem_t #include "audio.h" // for enum modem_t
@ -43,8 +45,9 @@ typedef struct cic_s {
} cic_t; } cic_t;
#define MAX_FILTER_SIZE 404 /* 401 is needed for profile A, 300 baud & 44100. Revisit someday. */ #define MAX_FILTER_SIZE 480 /* 401 is needed for profile A, 300 baud & 44100. Revisit someday. */
// Size comes out to 417 for 1200 bps with 48000 sample rate
// v1.7 - Was 404. Bump up to 480.
struct demodulator_state_s struct demodulator_state_s
{ {
@ -216,6 +219,12 @@ struct demodulator_state_s
signed int prev_d_c_pll; // Previous value of above, before signed int prev_d_c_pll; // Previous value of above, before
// incrementing, to detect overflows. // incrementing, to detect overflows.
int pll_symbol_count; // Number symbols during time nudge_total is accumulated.
int64_t pll_nudge_total; // Sum of DPLL nudge amounts.
// Both of these are cleared at start of frame.
// At end of frame, we can see if incoming
// baud rate is a little off.
int prev_demod_data; // Previous data bit detected. int prev_demod_data; // Previous data bit detected.
// Used to look for transitions. // Used to look for transitions.
float prev_demod_out_f; float prev_demod_out_f;
@ -358,7 +367,7 @@ struct demodulator_state_s
// Add a sample to the total when putting it in our array of recent samples. // Add a sample to the total when putting it in our array of recent samples.
// Subtract it from the total when it gets pushed off the end. // Subtract it from the total when it gets pushed off the end.
// We can also eliminate the need to shift them all down by using a circular buffer. // We can also eliminate the need to shift them all down by using a circular buffer.
// This only works with integers because float would have cummulated round off errors. // This only works with integers because float would have cumulated round off errors.
cic_t cic_center1; cic_t cic_center1;
cic_t cic_above; cic_t cic_above;

View File

@ -371,6 +371,7 @@ int fx25_pick_mode (int fx_mode, int dlen)
// The PRUG FX.25 TNC has additional modes that will handle larger frames // The PRUG FX.25 TNC has additional modes that will handle larger frames
// by using multiple RS blocks. This is a future possibility but needs // by using multiple RS blocks. This is a future possibility but needs
// to be coordinated with other FX.25 developers so we maintain compatibility. // to be coordinated with other FX.25 developers so we maintain compatibility.
// See https://web.tapr.org/meetings/DCC_2020/JE1WAZ/DCC-2020-PRUG-FINAL.pptx
static const int prefer[6] = { 0x04, 0x03, 0x06, 0x09, 0x05, 0x01 }; static const int prefer[6] = { 0x04, 0x03, 0x06, 0x09, 0x05, 0x01 };
for (int k = 0; k < 6; k++) { for (int k = 0; k < 6; k++) {

View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // This file is part of Dire Wolf, an amateur radio packet TNC.
// //
// Copyright (C) 2011, 2013, 2014, 2015, 2016, 2019, 2021 John Langner, WB2OSZ // Copyright (C) 2011, 2013, 2014, 2015, 2016, 2019, 2021, 2023 John Langner, WB2OSZ
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -56,6 +56,14 @@
* gen_packets -n 100 -o z2.wav * gen_packets -n 100 -o z2.wav
* atest z2.wav * atest z2.wav
* *
* Variable speed. e.g. 95% to 105% of normal speed.
* Required parameter is max % below and above normal.
* Optionally specify step other than 0.1%.
* Used to test how tolerant TNCs are to senders not
* not using exactly the right baud rate.
*
* gen_packets -v 5
* gen_packets -v 5,0.5
* *
*------------------------------------------------------------------*/ *------------------------------------------------------------------*/
@ -67,6 +75,7 @@
#include <getopt.h> #include <getopt.h>
#include <string.h> #include <string.h>
#include <assert.h> #include <assert.h>
#include <math.h>
#include "audio.h" #include "audio.h"
#include "ax25_pad.h" #include "ax25_pad.h"
@ -100,6 +109,7 @@ static float g_noise_level = 0;
static int g_morse_wpm = 0; /* Send morse code at this speed. */ static int g_morse_wpm = 0; /* Send morse code at this speed. */
static struct audio_s modem; static struct audio_s modem;
@ -108,13 +118,48 @@ static void send_packet (char *str)
packet_t pp; packet_t pp;
unsigned char fbuf[AX25_MAX_PACKET_LEN+2]; unsigned char fbuf[AX25_MAX_PACKET_LEN+2];
int flen; int flen;
int c; int c = 0; // channel number.
if (g_morse_wpm > 0) { if (g_morse_wpm > 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 { else {
pp = ax25_from_text (str, 1); pp = ax25_from_text (str, 1);
@ -125,6 +170,9 @@ static void send_packet (char *str)
} }
flen = ax25_pack (pp, fbuf); flen = ax25_pack (pp, fbuf);
(void)flen; (void)flen;
// If stereo, put same thing in each channel.
for (c=0; c<modem.adev[0].num_channels; c++) for (c=0; c<modem.adev[0].num_channels; c++)
{ {
@ -179,6 +227,9 @@ int main(int argc, char **argv)
int X_opt = 0; // send FX.25 int X_opt = 0; // send FX.25
int I_opt = -1; // send IL2P rather than AX.25, normal polarity int I_opt = -1; // send IL2P rather than AX.25, normal polarity
int i_opt = -1; // send IL2P rather than AX.25, inverted polarity int i_opt = -1; // send IL2P rather than AX.25, inverted polarity
double variable_speed_max_error = 0; // both in percent
double variable_speed_increment = 0.1;
/* /*
* Set up default values for the modem. * Set up default values for the modem.
@ -230,7 +281,7 @@ int main(int argc, char **argv)
/* ':' following option character means arg is required. */ /* ':' following option character means arg is required. */
c = getopt_long(argc, argv, "gjJm:s:a:b:B:r:n:N:o:z:82M:X:I:i:", c = getopt_long(argc, argv, "gjJm:s:a:b:B:r:n:N:o:z:82M:X:I:i:v:",
long_options, &option_index); long_options, &option_index);
if (c == -1) if (c == -1)
break; break;
@ -269,23 +320,31 @@ int main(int argc, char **argv)
// FIXME: options should not be order dependent. // FIXME: options should not be order dependent.
modem.achan[0].baud = atoi(optarg); if (strcasecmp(optarg, "EAS") == 0) {
modem.achan[0].baud = 0xEA5EA5; // See special case below.
}
else {
modem.achan[0].baud = atoi(optarg);
}
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
dw_printf ("Data rate set to %d bits / second.\n", modem.achan[0].baud); dw_printf ("Data rate set to %d bits / second.\n", modem.achan[0].baud);
if (modem.achan[0].baud != 100 && (modem.achan[0].baud < MIN_BAUD || modem.achan[0].baud > MAX_BAUD)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD);
exit (EXIT_FAILURE);
}
/* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */ /* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */
/* that need to be kept in sync. Maybe it could be a common function someday. */ /* 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].modem_type = MODEM_AFSK;
modem.achan[0].mark_freq = 1615; modem.achan[0].mark_freq = 1615;
modem.achan[0].space_freq = 1785; 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) { else if (modem.achan[0].baud < 600) {
modem.achan[0].modem_type = MODEM_AFSK; modem.achan[0].modem_type = MODEM_AFSK;
modem.achan[0].mark_freq = 1600; // Typical for HF SSB modem.achan[0].mark_freq = 1600; // Typical for HF SSB
@ -321,6 +380,11 @@ int main(int argc, char **argv)
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
dw_printf ("Using scrambled baseband signal rather than AFSK.\n"); 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; break;
case 'g': /* -g for g3ruh scrambling */ case 'g': /* -g for g3ruh scrambling */
@ -469,6 +533,16 @@ int main(int argc, char **argv)
i_opt = atoi(optarg); i_opt = atoi(optarg);
break; break;
case 'v': // Variable speed data + an - this percentage
// optional comma and increment.
variable_speed_max_error = fabs(atof(optarg));
char *q = strchr(optarg, ',');
if (q != NULL) {
variable_speed_increment = fabs(atof(q+1));
}
break;
case '?': case '?':
/* Unknown option message was already printed. */ /* Unknown option message was already printed. */
@ -479,7 +553,7 @@ int main(int argc, char **argv)
/* Should not be here. */ /* Should not be here. */
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf("?? getopt returned character code 0%o ??\n", c); dw_printf("?? getopt returned character code 0%o ??\n", (unsigned)c);
usage (argv); usage (argv);
} }
} }
@ -648,8 +722,34 @@ int main(int argc, char **argv)
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
dw_printf ("built in message...\n"); dw_printf ("built in message...\n");
//
// Generate packets with variable speed.
// This overrides any other number of packets or adding noise.
//
if (packet_count > 0) {
if (variable_speed_max_error != 0) {
int normal_speed = modem.achan[0].baud;
text_color_set(DW_COLOR_INFO);
dw_printf ("Variable speed.\n");
for (double speed_error = - variable_speed_max_error;
speed_error <= variable_speed_max_error + 0.001;
speed_error += variable_speed_increment) {
// Baud is int so we get some roundoff. Make it real?
modem.achan[0].baud = (int)round(normal_speed * (1. + speed_error / 100.));
gen_tone_init (&modem, amplitude/2, 1);
char stemp[256];
snprintf (stemp, sizeof(stemp), "WB2OSZ-15>TEST:, speed %+0.1f%% The quick brown fox jumps over the lazy dog!", speed_error);
send_packet (stemp);
}
}
else if (packet_count > 0) {
/* /*
* Generate packets with increasing noise level. * Generate packets with increasing noise level.
@ -691,14 +791,23 @@ int main(int argc, char **argv)
} }
else { 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. * 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! 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! 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! 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! 4 of 4"); }
} }
audio_file_close(); audio_file_close();
@ -716,7 +825,7 @@ static void usage (char **argv)
dw_printf ("Options:\n"); dw_printf ("Options:\n");
dw_printf (" -a <number> Signal amplitude in range of 0 - 200%%. Default 50.\n"); dw_printf (" -a <number> Signal amplitude in range of 0 - 200%%. Default 50.\n");
dw_printf (" -b <number> Bits / second for data. Default is %d.\n", DEFAULT_BAUD); dw_printf (" -b <number> Bits / second for data. Default is %d.\n", DEFAULT_BAUD);
dw_printf (" -B <number> Bits / second for data. Proper modem selected for 300, 1200, 2400, 4800, 9600.\n"); dw_printf (" -B <number> 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 (" -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 direwolf <= 1.5.\n");
dw_printf (" -J 2400 bps QPSK compatible with MFJ-2400.\n"); dw_printf (" -J 2400 bps QPSK compatible with MFJ-2400.\n");
@ -730,6 +839,7 @@ static void usage (char **argv)
dw_printf (" -o <file> Send output to .wav file.\n"); dw_printf (" -o <file> Send output to .wav file.\n");
dw_printf (" -8 8 bit audio rather than 16.\n"); dw_printf (" -8 8 bit audio rather than 16.\n");
dw_printf (" -2 2 channels (stereo) audio rather than one channel.\n"); dw_printf (" -2 2 channels (stereo) audio rather than one channel.\n");
dw_printf (" -v max[,incr] Variable speed with specified maximum error and increment.\n");
// dw_printf (" -z <number> Number of leading zero bits before frame.\n"); // dw_printf (" -z <number> Number of leading zero bits before frame.\n");
// dw_printf (" Default is 12 which is .01 seconds at 1200 bits/sec.\n"); // dw_printf (" Default is 12 which is .01 seconds at 1200 bits/sec.\n");
@ -738,6 +848,7 @@ static void usage (char **argv)
dw_printf ("the default built-in message. The format should correspond to\n"); 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 ("the standard packet monitoring representation such as,\n\n");
dw_printf (" WB2OSZ-1>APDW12,WIDE2-2:!4237.14NS07120.83W#\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 ("\n");
dw_printf ("Example: gen_packets -o x.wav \n"); dw_printf ("Example: gen_packets -o x.wav \n");
dw_printf ("\n"); dw_printf ("\n");

View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // 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 // 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 // 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 ticks_per_bit[MAX_CHANS];
static int f1_change_per_sample[MAX_CHANS]; static int f1_change_per_sample[MAX_CHANS];
static int f2_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]; 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); 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); 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. 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. 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; break;
case MODEM_8PSK: 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); 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); 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. 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; break;
case MODEM_BASEBAND: 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. // Tone is half baud.
ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->achan[chan].baud ) + 0.5); 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); 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; break;
default: // AFSK default: // AFSK
ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->achan[chan].baud ) + 0.5); 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); 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); 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; 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_v26[4] = {0, 1, 3, 2};
static const int gray2phase_v27[8] = {1, 0, 2, 3, 6, 7, 5, 4}; 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) 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 zero bits should give us steady 1800 Hz.
// All one bits should flip phase by 180 degrees each time. // 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; 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; tone_phase[chan] += symbol * PHASE_SHIFT_90;
if (save_audio_config_p->achan[chan].v26_alternative == V26_B) { if (save_audio_config_p->achan[chan].v26_alternative == V26_B) {
tone_phase[chan] += PHASE_SHIFT_45; tone_phase[chan] += PHASE_SHIFT_45;
} }
#endif
bit_count[chan]++; bit_count[chan]++;
} }
@ -370,7 +456,9 @@ void tone_gen_put_bit (int chan, int dat)
lfsr[chan] = (lfsr[chan] << 1) | (x & 1); lfsr[chan] = (lfsr[chan] << 1) | (x & 1);
dat = x; dat = x;
} }
#if PSKIQ
int blend = 1;
#endif
do { /* until enough audio samples for this symbol. */ do { /* until enough audio samples for this symbol. */
int sam; int sam;
@ -395,9 +483,58 @@ void tone_gen_put_bit (int chan, int dat)
gen_tone_put_sample (chan, a, sam); gen_tone_put_sample (chan, a, sam);
break; break;
case MODEM_QPSK: case MODEM_EAS:
case MODEM_8PSK:
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:
#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 #if DEBUG2
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("tone_gen_put_bit %d PSK\n", __LINE__); 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<nsamples; j++) {
gen_tone_put_sample (chan, a, sam);
};
// Avoid abrupt change when it starts up again.
tone_phase[chan] = 0;
}
/*------------------------------------------------------------------- /*-------------------------------------------------------------------

View File

@ -15,3 +15,5 @@ int gen_tone_init (struct audio_s *pp, int amp, int gen_packets);
void tone_gen_put_bit (int chan, int dat); void tone_gen_put_bit (int chan, int dat);
void gen_tone_put_sample (int chan, int a, int sam); void gen_tone_put_sample (int chan, int a, int sam);
void gen_tone_put_quiet_ms (int chan, int time_ms);

View File

@ -6,6 +6,7 @@
#include "ax25_pad.h" /* for packet_t, alevel_t */ #include "ax25_pad.h" /* for packet_t, alevel_t */
#include "rrbb.h" #include "rrbb.h"
#include "audio.h" /* for struct audio_s */ #include "audio.h" /* for struct audio_s */
#include "dlq.h" // for fec_type_t definition.
@ -62,6 +63,6 @@ int hdlc_rec2_try_to_fix_later (rrbb_t block, int chan, int subchan, int slice,
/* Provided by the top level application to process a complete frame. */ /* Provided by the top level application to process a complete frame. */
void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t level, int is_fx25, retry_t retries, char *spectrum); void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t level, fec_type_t fec_type, retry_t retries, char *spectrum);
#endif #endif

View File

@ -295,4 +295,82 @@ static void send_bit_nrzi (int chan, int b)
number_of_bits_sent[chan]++; number_of_bits_sent[chan]++;
} }
// The rest of this is for EAS SAME.
// This is sort of a logical place because it serializes a frame, but not in HDLC.
// We have a parallel where SAME deserialization is in hdlc_rec.
// Maybe both should be pulled out and moved to a same.c.
/*-------------------------------------------------------------------
*
* Name: eas_send
*
* Purpose: Serialize EAS SAME for transmission.
*
* Inputs: chan - Radio channel number.
* str - Character string to send.
* repeat - Number of times to repeat with 1 sec quiet between.
* txdelay - Delay (ms) from PTT to first preamble bit.
* txtail - Delay (ms) from last data bit to PTT off.
*
*
* Returns: Total number of milliseconds to activate PTT.
* This includes delays before the first character
* and after the last to avoid chopping off part of it.
*
* Description: xmit_thread calls this instead of the usual hdlc_send
* when we have a special packet that means send EAS SAME
* code.
*
*--------------------------------------------------------------------*/
static inline void eas_put_byte (int chan, unsigned char b)
{
for (int n=0; n<8; n++) {
tone_gen_put_bit (chan, (b & 1));
b >>= 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<repeat; r++ ) {
for (int j=0; j<16; j++) {
eas_put_byte (chan, 0xAB);
bytes_sent++;
}
for (unsigned char *p = str; *p != '\0'; p++) {
eas_put_byte (chan, *p);
bytes_sent++;
}
if (r < repeat-1) {
gen_tone_put_quiet_ms (chan, gap);
gaps_sent++;
}
}
gen_tone_put_quiet_ms (chan, txtail);
audio_flush(ACHAN2ADEV(chan));
int elapsed = txdelay + (int) (bytes_sent * 8 * 1.92) + (gaps_sent * gap) + txtail;
// dw_printf ("DEBUG: EAS total time = %d ms\n", elapsed);
return (elapsed);
} /* end eas_send */
/* end hdlc_send.c */ /* end hdlc_send.c */

View File

@ -3,7 +3,10 @@
// In version 1.7 an extra layer of abstraction was added here. // In version 1.7 an extra layer of abstraction was added here.
// Rather than calling hdlc_send_frame, we now use another function // Rather than calling hdlc_send_frame, we now use another function
// which sends AX.25, FX.25, or IL2P depending on // which sends AX.25, FX.25, or IL2P depending on mode.
// eas_send fits here logically because it also serializes a packet.
#include "ax25_pad.h" #include "ax25_pad.h"
#include "audio.h" #include "audio.h"
@ -12,6 +15,8 @@ int layer2_send_frame (int chan, packet_t pp, int bad_fcs, struct audio_s *audio
int layer2_preamble_postamble (int chan, int flags, int finish, struct audio_s *audio_config_p); int layer2_preamble_postamble (int chan, int flags, int finish, struct audio_s *audio_config_p);
int eas_send (int chan, unsigned char *str, int repeat, int txdelay, int txtail);
/* end hdlc_send.h */ /* end hdlc_send.h */

View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // This file is part of Dire Wolf, an amateur radio packet TNC.
// //
// Copyright (C) 2013, 2014, 2015, 2016 John Langner, WB2OSZ // Copyright (C) 2013, 2014, 2015, 2016, 2023 John Langner, WB2OSZ
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -203,6 +203,8 @@ static char * ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t S
#if ITEST #if ITEST
// TODO: Add to automated tests.
/* For unit testing. */ /* For unit testing. */
int main (int argc, char *argv[]) int main (int argc, char *argv[])
@ -326,7 +328,7 @@ static int stats_uplink_packets; /* Number of packets passed along to the IGate
/* server after filtering. */ /* server after filtering. */
static int stats_uplink_bytes; /* Total number of bytes sent to IGate server */ static int stats_uplink_bytes; /* Total number of bytes sent to IGate server */
/* including login, packets, and hearbeats. */ /* including login, packets, and heartbeats. */
static int stats_downlink_bytes; /* Total number of bytes from IGate server including */ static int stats_downlink_bytes; /* Total number of bytes from IGate server including */
/* packets, heartbeats, other messages. */ /* packets, heartbeats, other messages. */
@ -853,6 +855,9 @@ static void * connnect_thread (void *arg)
* Purpose: Send a packet to the IGate server * Purpose: Send a packet to the IGate server
* *
* Inputs: chan - Radio channel it was received on. * Inputs: chan - Radio channel it was received on.
* This is required for the RF>IS filtering.
* Beaconing (sendto=ig, chan=-1) and a client app sending
* to ICHANNEL should bypass the filtering.
* *
* recv_pp - Pointer to packet object. * recv_pp - Pointer to packet object.
* *** CALLER IS RESPONSIBLE FOR DELETING IT! ** * *** CALLER IS RESPONSIBLE FOR DELETING IT! **
@ -900,7 +905,12 @@ void igate_send_rec_packet (int chan, packet_t recv_pp)
* In that case, the payload will have TCPIP in the path and it will be dropped. * In that case, the payload will have TCPIP in the path and it will be dropped.
*/ */
if (save_digi_config_p->filter_str[chan][MAX_CHANS] != NULL) { // Apply RF>IS filtering only if it same from a radio channel.
// Beacon will be channel -1.
// Client app to ICHANNEL is outside of radio channel range.
if (chan >= 0 && chan < MAX_CHANS && // in radio channel range
save_digi_config_p->filter_str[chan][MAX_CHANS] != NULL) {
if (pfilter(chan, MAX_CHANS, save_digi_config_p->filter_str[chan][MAX_CHANS], recv_pp, 1) != 1) { if (pfilter(chan, MAX_CHANS, save_digi_config_p->filter_str[chan][MAX_CHANS], recv_pp, 1) != 1) {
@ -1219,7 +1229,7 @@ static void send_packet_to_server (packet_t pp, int chan)
* Name: send_msg_to_server * Name: send_msg_to_server
* *
* Purpose: Send something to the IGate server. * Purpose: Send something to the IGate server.
* This one function should be used for login, hearbeats, * This one function should be used for login, heartbeats,
* and packets. * and packets.
* *
* Inputs: imsg - Message. We will add CR/LF here. * Inputs: imsg - Message. We will add CR/LF here.
@ -1515,32 +1525,36 @@ static void * igate_recv_thread (void *arg)
int ichan = save_audio_config_p->igate_vchannel; int ichan = save_audio_config_p->igate_vchannel;
// Try to parse it into a packet object. // My original poorly thoughtout idea was to parse it into a packet object,
// This will contain "q constructs" and we might see an address // using the non-strict option, and send to the client app.
// with two alphnumeric characters in the SSID so we must use //
// the non-strict parsing. // A lot of things can go wrong with that approach.
// Possible problem: Up to 8 digipeaters are allowed in radio format. // (1) Up to 8 digipeaters are allowed in radio format.
// There is a potential of finding a larger number here. // There is a potential of finding a larger number here.
//
// (2) The via path can have names that are not valid in the radio format.
// e.g. qAC, T2HAKATA, N5JXS-F1.
// Non-strict parsing would force uppercase, truncate names too long,
// and drop unacceptable SSIDs.
//
// (3) The source address could be invalid for the RF address format.
// e.g. WHO-IS>APJIW4,TCPIP*,qAC,AE5PL-JF::ZL1JSH-9 :Charles Beadfield/New Zealand{583
// That is essential information that we absolutely need to preserve.
//
// I think the only correct solution is to apply a third party header
// wrapper so the original contents are preserved. This will be a little
// more work for the application developer. Search for ":}" and use only
// the part after that. At this point, I don't see any value in encoding
// information in the source/destination so I will just use "X>X:}" as a prefix
packet_t pp3 = ax25_from_text((char*)message, 0); // 0 means not strict char stemp[AX25_MAX_INFO_LEN];
strlcpy (stemp, "X>X:}", sizeof(stemp));
strlcat (stemp, (char*)message, sizeof(stemp));
packet_t pp3 = ax25_from_text(stemp, 0); // 0 means not strict
if (pp3 != NULL) { if (pp3 != NULL) {
// Should we remove the VIA path?
// For example, we might get something like this from the server.
// Lower case 'q' and non-numeric SSID are not valid for AX.25 over the air.
// K1USN-1>APWW10,TCPIP*,qAC,N5JXS-F1:T#479,100,048,002,500,000,10000000
// Should we try to retain all information and pass that along, to the best of our ability,
// to the client app, or should we remove the via path so it looks like this?
// K1USN-1>APWW10:T#479,100,048,002,500,000,10000000
// For now, keep it intact and see if it causes problems. Easy to remove like this:
// while (ax25_get_num_repeaters(pp3) > 0) {
// ax25_remove_addr (pp3, AX25_REPEATER_1);
// }
alevel_t alevel; alevel_t alevel;
memset (&alevel, 0, sizeof(alevel)); memset (&alevel, 0, sizeof(alevel));
alevel.mark = -2; // FIXME: Do we want some other special case? alevel.mark = -2; // FIXME: Do we want some other special case?
@ -1550,9 +1564,9 @@ static void * igate_recv_thread (void *arg)
// See what happens with -2 and follow up on this. // See what happens with -2 and follow up on this.
// Do we need something else here? // Do we need something else here?
int slice = 0; int slice = 0;
int is_fx25 = 0; fec_type_t fec_type = fec_type_none;
char spectrum[] = "APRS-IS"; char spectrum[] = "APRS-IS";
dlq_rec_frame (ichan, subchan, slice, pp3, alevel, is_fx25, RETRY_NONE, spectrum); dlq_rec_frame (ichan, subchan, slice, pp3, alevel, fec_type, RETRY_NONE, spectrum);
} }
else { else {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
@ -1713,7 +1727,14 @@ static void * satgate_delay_thread (void *arg)
* K1RI-2>APWW10,WIDE1-1,WIDE2-1,qAS,K1RI:/221700h/9AmA<Ct3_ sT010/002g005t045r000p023P020h97b10148 * K1RI-2>APWW10,WIDE1-1,WIDE2-1,qAS,K1RI:/221700h/9AmA<Ct3_ sT010/002g005t045r000p023P020h97b10148
* KC1BOS-2>T3PQ3S,WIDE1-1,WIDE2-1,qAR,W1TG-1:`c)@qh\>/"50}TinyTrak4 Mobile * KC1BOS-2>T3PQ3S,WIDE1-1,WIDE2-1,qAR,W1TG-1:`c)@qh\>/"50}TinyTrak4 Mobile
* *
* Notice how the final address in the header might not * This is interesting because the source is not a valid AX.25 address.
* Non-RF stations can have 2 alphanumeric characters for SSID.
* In this example, the WHO-IS server is responding to a message.
*
* WHO-IS>APJIW4,TCPIP*,qAC,AE5PL-JF::ZL1JSH-9 :Charles Beadfield/New Zealand{583
*
*
* Notice how the final digipeater address, in the header, might not
* be a valid AX.25 address. We see a 9 character address * be a valid AX.25 address. We see a 9 character address
* (with no ssid) and an ssid of two letters. * (with no ssid) and an ssid of two letters.
* We don't care because we end up discarding them before * We don't care because we end up discarding them before
@ -1728,29 +1749,61 @@ static void * satgate_delay_thread (void *arg)
* *
*--------------------------------------------------------------------*/ *--------------------------------------------------------------------*/
// It is unforunate that the : data type indicator (DTI) was overloaded with
// so many different meanings. Simply looking at the DTI is not adequate for
// determining whether a packet is a message.
// We need to exclude the other special cases of telemetry metadata,
// bulletins, and weather bulletins.
static int is_message_message (char *infop)
{
if (*infop != ':') return (0);
if (strlen(infop) < 11) return (0); // too short for : addressee :
if (strlen(infop) >= 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) static void maybe_xmit_packet_from_igate (char *message, int to_chan)
{ {
packet_t pp3;
char payload[AX25_MAX_PACKET_LEN]; /* what is max len? */
char src[AX25_MAX_ADDR_LEN]; /* Source address. */
char *pinfo = NULL;
int info_len;
int n; int n;
assert (to_chan >= 0 && to_chan < MAX_CHANS); assert (to_chan >= 0 && to_chan < MAX_CHANS);
/* /*
* Try to parse it into a packet object. * Try to parse it into a packet object; we need this for the packet filtering.
* This will contain "q constructs" and we might see an address
* with two alphnumeric characters in the SSID so we must use
* the non-strict parsing.
* *
* Bug: Up to 8 digipeaters are allowed in radio format. * We use the non-strict option because there the via path can have:
* There is a potential of finding a larger number here. * - station names longer than 6.
* - alphanumeric SSID.
* - lower case for "q constructs.
* We don't care about any of those because the via path will be discarded anyhow.
*
* The other issue, that I did not think of originally, is that the "source"
* address might not conform to AX.25 restrictions when it originally came
* from a non-RF source. For example an APRS "message" might be sent to the
* "WHO-IS" server, and the reply message would have that for the source address.
*
* Originally, I used the source address from the packet object but that was
* missing the alphanumeric SSID. This needs to be done differently.
*
* Potential Bug: Up to 8 digipeaters are allowed in radio format.
* Is there a possibility of finding a larger number here?
*/ */
pp3 = ax25_from_text(message, 0); packet_t pp3 = ax25_from_text(message, 0);
if (pp3 == NULL) { if (pp3 == NULL) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Tx IGate: Could not parse message from server.\n"); dw_printf ("Tx IGate: Could not parse message from server.\n");
@ -1758,7 +1811,18 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan)
return; return;
} }
ax25_get_addr_with_ssid (pp3, AX25_SOURCE, src); // Issue 408: The source address might not be valid AX.25 because it
// came from a non-RF station. e.g. some server responding to a message.
// We need to take source address from original rather than extracting it
// from the packet object.
char src[AX25_MAX_ADDR_LEN]; /* Source address. */
memset (src, 0, sizeof(src));
memcpy (src, message, sizeof(src)-1);
char *gt = strchr(src, '>');
if (gt != NULL) {
*gt = '\0';
}
/* /*
* Drop if path contains: * Drop if path contains:
@ -1770,8 +1834,8 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan)
ax25_get_addr_with_ssid (pp3, n + AX25_REPEATER_1, via); ax25_get_addr_with_ssid (pp3, n + AX25_REPEATER_1, via);
if (strcmp(via, "qAX") == 0 || if (strcmp(via, "qAX") == 0 || // qAX deprecated. http://www.aprs-is.net/q.aspx
strcmp(via, "TCPXX") == 0 || strcmp(via, "TCPXX") == 0 || // TCPXX deprecated.
strcmp(via, "RFONLY") == 0 || strcmp(via, "RFONLY") == 0 ||
strcmp(via, "NOGATE") == 0) { strcmp(via, "NOGATE") == 0) {
@ -1800,14 +1864,26 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan)
* If we recently transmitted a 'message' from some station, * If we recently transmitted a 'message' from some station,
* send the position of the message sender when it comes along later. * 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
* think that is an official term.
*
* If we have a position report, look up the sender and see if we should * If we have a position report, look up the sender and see if we should
* bypass the normal filtering. * bypass the normal filtering.
*
* Reference: https://www.aprs-is.net/IGating.aspx
*
* "Passing all message packets also includes passing the sending station's position
* along with the message. When APRS-IS was small, we did this using historical position
* packets. This has become problematic as it introduces historical data on to RF.
* The IGate should note the station(s) it has gated messages to RF for and pass
* the next position packet seen for that station(s) to RF."
*/ */
// TODO: Not quite this simple. Should have a function to check for position. // TODO: Not quite this simple. Should have a function to check for position.
// $ raw gps could be a position. @ could be weather data depending on symbol. // $ raw gps could be a position. @ could be weather data depending on symbol.
info_len = ax25_get_info (pp3, (unsigned char **)(&pinfo)); char *pinfo = NULL;
int info_len = ax25_get_info (pp3, (unsigned char **)(&pinfo));
int msp_special_case = 0; int msp_special_case = 0;
@ -1837,12 +1913,6 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan)
// Previously there was a debug message here about the packet being dropped by filtering. // Previously there was a debug message here about the packet being dropped by filtering.
// This is now handled better by the "-df" command line option for filtering details. // This is now handled better by the "-df" command line option for filtering details.
// TODO: clean up - remove these lines.
//if (s_debug >= 1) {
// text_color_set(DW_COLOR_INFO);
// dw_printf ("Packet from IGate to channel %d was rejected by filter: %s\n", to_chan, save_digi_config_p->filter_str[MAX_CHANS][to_chan]);
//}
ax25_delete (pp3); ax25_delete (pp3);
return; return;
} }
@ -1851,13 +1921,15 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan)
/* /*
* Remove the VIA path. * We want to discard the via path, as received from the APRS-IS, then
* replace it with TCPIP and our own call, marked as used.
*
* *
* For example, we might get something like this from the server. * For example, we might get something like this from the server.
* K1USN-1>APWW10,TCPIP*,qAC,N5JXS-F1:T#479,100,048,002,500,000,10000000<0x0d><0x0a> * K1USN-1>APWW10,TCPIP*,qAC,N5JXS-F1:T#479,100,048,002,500,000,10000000
* *
* We want to reduce it to this before wrapping it as third party traffic. * We want to transform it to this before wrapping it as third party traffic.
* K1USN-1>APWW10:T#479,100,048,002,500,000,10000000<0x0d><0x0a> * K1USN-1>APWW10,TCPIP,mycall*:T#479,100,048,002,500,000,10000000
*/ */
/* /*
@ -1885,37 +1957,24 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan)
* *
* What is the ",I" construct? * What is the ",I" construct?
* Do we care here? * Do we care here?
* Is is something new and improved that we should be using in the other direction? * Is it something new and improved that we should be using in the other direction?
*/ */
while (ax25_get_num_repeaters(pp3) > 0) { char payload[AX25_MAX_PACKET_LEN];
ax25_remove_addr (pp3, AX25_REPEATER_1);
} char dest[AX25_MAX_ADDR_LEN]; /* Destination field. */
ax25_get_addr_with_ssid (pp3, AX25_DESTINATION, dest);
snprintf (payload, sizeof(payload), "%s>%s,TCPIP,%s*:%s",
src, dest, save_audio_config_p->achan[to_chan].mycall, pinfo);
/*
* Replace the VIA path with TCPIP and my call.
* Mark my call as having been used.
*/
ax25_set_addr (pp3, AX25_REPEATER_1, "TCPIP");
ax25_set_h (pp3, AX25_REPEATER_1);
ax25_set_addr (pp3, AX25_REPEATER_2, save_audio_config_p->achan[to_chan].mycall);
ax25_set_h (pp3, AX25_REPEATER_2);
/*
* Convert to text representation.
*/
memset (payload, 0, sizeof(payload));
ax25_format_addrs (pp3, payload);
info_len = ax25_get_info (pp3, (unsigned char **)(&pinfo));
(void)(info_len);
strlcat (payload, pinfo, sizeof(payload));
#if DEBUGx #if DEBUGx
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("Tx IGate: payload=%s\n", payload); dw_printf ("Tx IGate: DEBUG payload=%s\n", payload);
#endif #endif
/* /*
* Encapsulate for sending over radio if no reason to drop it. * Encapsulate for sending over radio if no reason to drop it.
*/ */
@ -1931,19 +1990,13 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan)
*/ */
if (ig_to_tx_allow (pp3, to_chan)) { if (ig_to_tx_allow (pp3, to_chan)) {
char radio [2400]; char radio [2400];
packet_t pradio;
snprintf (radio, sizeof(radio), "%s>%s%d%d%s:}%s", snprintf (radio, sizeof(radio), "%s>%s%d%d%s:}%s",
save_audio_config_p->achan[to_chan].mycall, save_audio_config_p->achan[to_chan].mycall,
APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, APP_TOCALL, MAJOR_VERSION, MINOR_VERSION,
save_igate_config_p->tx_via, save_igate_config_p->tx_via,
payload); payload);
pradio = ax25_from_text (radio, 1); packet_t pradio = ax25_from_text (radio, 1);
/* Oops. Didn't have a check for NULL here. */
/* Could this be the cause of rare and elusive crashes in 1.2? */
if (pradio != NULL) { if (pradio != NULL) {
#if ITEST #if ITEST
@ -1956,10 +2009,7 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan)
#endif #endif
stats_rf_xmit_packets++; // Any type of packet. stats_rf_xmit_packets++; // Any type of packet.
// TEMP TEST: metadata temporarily allowed during testing. if (is_message_message(pinfo)) {
if (*pinfo == ':' && ! is_telem_metadata(pinfo)) {
// temp test // if (*pinfo == ':') {
// We transmitted a "message." Telemetry metadata is excluded. // We transmitted a "message." Telemetry metadata is excluded.
// Remember to pass along address of the sender later. // Remember to pass along address of the sender later.
@ -2417,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) static int ig_to_tx_allow (packet_t pp, int chan)
{ {
unsigned short crc = ax25_dedupe_crc(pp); unsigned short crc = ax25_dedupe_crc(pp);
@ -2449,7 +2501,7 @@ static int ig_to_tx_allow (packet_t pp, int chan)
/* We have a duplicate within some time period. */ /* 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." */ /* 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. */ /* Suppose we transmit a message from station X and it doesn't get an ack back. */
@ -2498,7 +2550,7 @@ static int ig_to_tx_allow (packet_t pp, int chan)
/* the normal limit for them. */ /* the normal limit for them. */
increase_limit = 1; increase_limit = 1;
if (*pinfo == ':' && ! is_telem_metadata((char*)pinfo)) { if (is_message_message((char*)pinfo)) {
increase_limit = 3; increase_limit = 3;
} }

View File

@ -437,7 +437,7 @@ packet_t il2p_decode_header_type_1 (unsigned char *hdr, int num_sym_changed)
// However, I have seen cases, where the error rate is very high, where the RS decoder // However, I have seen cases, where the error rate is very high, where the RS decoder
// thinks it found a valid code block by changing one symbol but it was the wrong one. // thinks it found a valid code block by changing one symbol but it was the wrong one.
// The result is trash. This shows up as address fields like 'R&G4"A' and 'TEW\ !'. // The result is trash. This shows up as address fields like 'R&G4"A' and 'TEW\ !'.
// I added a sanity check here to catch characters other than uppper case letters and digits. // I added a sanity check here to catch characters other than upper case letters and digits.
// The frame should be rejected in this case. The question is whether to discard it // The frame should be rejected in this case. The question is whether to discard it
// silently or print a message so the user can see that something strange is happening? // silently or print a message so the user can see that something strange is happening?
// My current thinking is that it should be silently ignored if the header has been // My current thinking is that it should be silently ignored if the header has been
@ -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++) { for (int i = 0; i < strlen(addrs[AX25_DESTINATION]); i++) {
if (! isupper(addrs[AX25_DESTINATION][i]) && ! isdigit(addrs[AX25_DESTINATION][i])) { if (! isupper(addrs[AX25_DESTINATION][i]) && ! isdigit(addrs[AX25_DESTINATION][i])) {
if (num_sym_changed == 0) { if (num_sym_changed == 0) {
text_color_set(DW_COLOR_ERROR); // This can pop up sporadically when receiving random noise.
dw_printf ("IL2P: Invalid character '%c' in destination address '%s'\n", addrs[AX25_DESTINATION][i], addrs[AX25_DESTINATION]); // 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); 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++) { for (int i = 0; i < strlen(addrs[AX25_SOURCE]); i++) {
if (! isupper(addrs[AX25_SOURCE][i]) && ! isdigit(addrs[AX25_SOURCE][i])) { if (! isupper(addrs[AX25_SOURCE][i]) && ! isdigit(addrs[AX25_SOURCE][i])) {
if (num_sym_changed == 0) { if (num_sym_changed == 0) {
text_color_set(DW_COLOR_ERROR); // This can pop up sporadically when receiving random noise.
dw_printf ("IL2P: Invalid character '%c' in source address '%s'\n", addrs[AX25_SOURCE][i], addrs[AX25_SOURCE]); // 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); return (NULL);
} }

View File

@ -194,7 +194,7 @@ int il2p_encode_payload (unsigned char *payload, int payload_size, int max_fec,
* Purpose: Extract original data from encoded payload. * Purpose: Extract original data from encoded payload.
* *
* Inputs: received Array of bytes. Size is unknown but in practice it * Inputs: received Array of bytes. Size is unknown but in practice it
* must not exceeed IL2P_MAX_ENCODED_SIZE. * must not exceed IL2P_MAX_ENCODED_SIZE.
* payload_size 0 to 1023. (IL2P_MAX_PAYLOAD_SIZE) * payload_size 0 to 1023. (IL2P_MAX_PAYLOAD_SIZE)
* Expected result size based on header. * Expected result size based on header.
* max_fec true for 16 parity symbols, false for automatic. * max_fec true for 16 parity symbols, false for automatic.

View File

@ -943,7 +943,7 @@ void tone_gen_put_bit (int chan, int data)
// This is called when a complete frame has been deserialized. // 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. if (rec_count < 0) return; // Skip check before serdes test.

View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // This file is part of Dire Wolf, an amateur radio packet TNC.
// //
// Copyright (C) 2013, 2014, 2017 John Langner, WB2OSZ // Copyright (C) 2013, 2014, 2017, 2023 John Langner, WB2OSZ
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -40,7 +40,8 @@
* *
* The first byte of the frame contains: * 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. * * command in lower nybble.
* *
* *
@ -611,7 +612,8 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, struct
/* Verify that the radio channel number is valid. */ /* Verify that the radio channel number is valid. */
/* Any sort of medium should be OK here. */ /* Any sort of medium should be OK here. */
if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->chan_medium[chan] == MEDIUM_NONE) { if ((chan < 0 || chan >= MAX_CHANS || save_audio_config_p->chan_medium[chan] == MEDIUM_NONE)
&& save_audio_config_p->chan_medium[chan] != MEDIUM_IGATE) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid transmit channel %d from KISS client app.\n", chan); dw_printf ("Invalid transmit channel %d from KISS client app.\n", chan);
dw_printf ("\n"); dw_printf ("\n");
@ -953,7 +955,7 @@ void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int
p = pmsg; p = pmsg;
if (*p == FEND) p++; 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], prefix[(int)fromto], function[p[0] & 0xf], direction[(int)fromto],
(p[0] >> 4) & 0xf, msg_len); (p[0] >> 4) & 0xf, msg_len);
} }

View File

@ -84,6 +84,8 @@ struct kissport_status_s {
// The default is a limit of 3 client applications at the same time. // The default is a limit of 3 client applications at the same time.
// You can increase the limit by changing the line below. // You can increase the limit by changing the line below.
// A larger number consumes more resources so don't go crazy by making it larger than needed. // A larger number consumes more resources so don't go crazy by making it larger than needed.
// TODO: Should this be moved to direwolf.h so max number of audio devices
// client apps are in the same place?
#define MAX_NET_CLIENTS 3 #define MAX_NET_CLIENTS 3

View File

@ -67,7 +67,6 @@
#include "serial_port.h" #include "serial_port.h"
#include "kiss_frame.h" #include "kiss_frame.h"
#include "dwsock.h" #include "dwsock.h"
#include "dtime_now.h"
#include "audio.h" // for DEFAULT_TXDELAY, etc. #include "audio.h" // for DEFAULT_TXDELAY, etc.
#include "dtime_now.h" #include "dtime_now.h"

View File

@ -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. now = time(NULL); // Get current time.
(void)gmtime_r (&now, &tm); (void)gmtime_r (&now, &tm);
// FIXME: https://github.com/wb2osz/direwolf/issues/473
if (g_daily_names) { if (g_daily_names) {

View File

@ -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; 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); mptr = mheard_ptr(source);
if (mptr == NULL) { if (mptr == NULL) {
@ -434,6 +480,7 @@ void mheard_save_rf (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, r
* N1HKO-10>APJI40,TCPIP*,qAC,N1HKO-JS:<IGATE,MSG_CNT=0,LOC_CNT=0 * N1HKO-10>APJI40,TCPIP*,qAC,N1HKO-JS:<IGATE,MSG_CNT=0,LOC_CNT=0
* K1RI-2>APWW10,WIDE1-1,WIDE2-1,qAS,K1RI:/221700h/9AmA<Ct3_ sT010/002g005t045r000p023P020h97b10148 * K1RI-2>APWW10,WIDE1-1,WIDE2-1,qAS,K1RI:/221700h/9AmA<Ct3_ sT010/002g005t045r000p023P020h97b10148
* KC1BOS-2>T3PQ3S,WIDE1-1,WIDE2-1,qAR,W1TG-1:`c)@qh\>/"50}TinyTrak4 Mobile * KC1BOS-2>T3PQ3S,WIDE1-1,WIDE2-1,qAR,W1TG-1:`c)@qh\>/"50}TinyTrak4 Mobile
* WHO-IS>APJIW4,TCPIP*,qAC,AE5PL-JF::WB2OSZ :C/Billerica Amateur Radio Society/MA/United States{XF}WO
* *
* Notice how the final address in the header might not * Notice how the final address in the header might not
* be a valid AX.25 address. We see a 9 character address * be a valid AX.25 address. We see a 9 character address
@ -443,6 +490,7 @@ void mheard_save_rf (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, r
* a clue about the journey taken but I don't think we care here. * a clue about the journey taken but I don't think we care here.
* *
* All we should care about here is the the source address. * All we should care about here is the the source address.
* Note that the source address might not adhere to the AX.25 format.
* *
* Description: * Description:
* *
@ -450,21 +498,25 @@ void mheard_save_rf (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, r
void mheard_save_is (char *ptext) void mheard_save_is (char *ptext)
{ {
packet_t pp;
time_t now = time(NULL); time_t now = time(NULL);
char source[AX25_MAX_ADDR_LEN]; char source[AX25_MAX_ADDR_LEN];
mheard_t *mptr;
#if 1
// It is possible that source won't adhere to the AX.25 restrictions.
// So we simply extract the source address, as text, from the beginning rather than
// using ax25_from_text() and ax25_get_addr_with_ssid().
memset (source, 0, sizeof(source));
memcpy (source, ptext, sizeof(source)-1);
char *g = strchr(source, '>');
if (g != NULL) *g = '\0';
#else
/* /*
* Try to parse it into a packet object. * Keep this here in case I want to revive it to get location.
* This will contain "q constructs" and we might see an address
* with two alphnumeric characters in the SSID so we must use
* the non-strict parsing.
*
* Bug: Up to 8 digipeaters are allowed in radio format.
* There is a potential of finding a larger number here.
*/ */
pp = ax25_from_text(ptext, 0); packet_t pp = ax25_from_text(ptext, 0);
if (pp == NULL) { if (pp == NULL) {
if (mheard_debug) { if (mheard_debug) {
@ -475,13 +527,17 @@ void mheard_save_is (char *ptext)
return; return;
} }
ax25_get_addr_with_ssid (pp, AX25_SOURCE, source); //////ax25_get_addr_with_ssid (pp, AX25_SOURCE, source);
#endif
mptr = mheard_ptr(source); mheard_t *mptr = mheard_ptr(source);
if (mptr == NULL) { if (mptr == NULL) {
int i; int i;
/* /*
* Not heard before. Add it. * Not heard before. Add it.
* Observation years later:
* Hmmmm. I wonder why I did not store the location if available.
* An earlier example has an APRSdroid station reporting location without using [ham] RF.
*/ */
if (mheard_debug) { if (mheard_debug) {
@ -537,7 +593,9 @@ void mheard_save_is (char *ptext)
mheard_dump (); mheard_dump ();
} }
#if 0
ax25_delete (pp); ax25_delete (pp);
#endif
} /* end mheard_save_is */ } /* end mheard_save_is */
@ -559,7 +617,7 @@ void mheard_save_is (char *ptext)
* 8 for RF_CNT. * 8 for RF_CNT.
* *
* time_limit - Include only stations heard within this many minutes. * 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. * Returns: Number to be used in the statistics report.
* *
@ -637,7 +695,7 @@ int mheard_count (int max_hops, int time_limit)
* callsign - Callsign for station. * callsign - Callsign for station.
* *
* time_limit - Include only stations heard within this many minutes. * 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 * max_hops - Include only stations heard with this number of
* digipeater hops or less. For reporting, we might use: * digipeater hops or less. For reporting, we might use:

View File

@ -116,7 +116,8 @@ static struct audio_s *save_audio_config_p;
static struct { static struct {
packet_t packet_p; packet_t packet_p;
alevel_t alevel; 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 retry_t retries; // For the old "fix bits" strategy, this is the
// number of bits that were modified to get a good CRC. // number of bits that were modified to get a good CRC.
// It would be 0 to something around 4. // 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. * display of audio level line.
* Use -2 to indicate DTMF message.) * Use -2 to indicate DTMF message.)
* retries - Level of correction used. * 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. * 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; 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); 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? // 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) { if (pp == NULL) {
text_color_set(DW_COLOR_ERROR); 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); ax25_delete (pp);
} }
else { else {
dlq_rec_frame (chan, subchan, slice, pp, alevel, is_fx25, retries, ""); dlq_rec_frame (chan, subchan, slice, pp, alevel, fec_type, retries, "");
} }
return; 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].packet_p = pp;
candidate[chan][subchan][slice].alevel = alevel; 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].retries = retries;
candidate[chan][subchan][slice].age = 0; candidate[chan][subchan][slice].age = 0;
candidate[chan][subchan][slice].crc = ax25_m_m_crc(pp); 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; int best_n, best_score;
char spectrum[MAX_SUBCHANS*MAX_SLICERS+1]; char spectrum[MAX_SUBCHANS*MAX_SLICERS+1];
int n, j, k; 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; int num_bars = save_audio_config_p->achan[chan].num_slicers * save_audio_config_p->achan[chan].num_subchan;
memset (spectrum, 0, sizeof(spectrum)); memset (spectrum, 0, sizeof(spectrum));
@ -456,7 +460,7 @@ static void pick_best_candidate (int chan)
if (candidate[chan][j][k].packet_p == NULL) { if (candidate[chan][j][k].packet_p == NULL) {
spectrum[n] = '_'; 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. // FIXME: using retries both as an enum and later int too.
if ((int)(candidate[chan][j][k].retries) <= 9) { if ((int)(candidate[chan][j][k].retries) <= 9) {
spectrum[n] = '0' + candidate[chan][j][k].retries; spectrum[n] = '0' + candidate[chan][j][k].retries;
@ -464,7 +468,7 @@ static void pick_best_candidate (int chan)
else { else {
spectrum[n] = '+'; spectrum[n] = '+';
} }
} } // AX.25 below
else if (candidate[chan][j][k].retries == RETRY_NONE) { else if (candidate[chan][j][k].retries == RETRY_NONE) {
spectrum[n] = '|'; spectrum[n] = '|';
} }
@ -481,8 +485,8 @@ static void pick_best_candidate (int chan)
candidate[chan][j][k].score = 0; candidate[chan][j][k].score = 0;
} }
else { else {
if (candidate[chan][j][k].is_fx25) { if (candidate[chan][j][k].fec_type != fec_type_none) {
candidate[chan][j][k].score = 9000 - 100 * candidate[chan][j][k].retries; candidate[chan][j][k].score = 9000 - 100 * candidate[chan][j][k].retries; // has FEC
} }
else { else {
/* Originally, this produced 0 for the PASSALL case. */ /* 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); candidate[chan][j][k].packet_p);
} }
else { 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].packet_p,
candidate[chan][j][k].is_fx25, (int)(candidate[chan][j][k].fec_type),
(int)(candidate[chan][j][k].retries), (int)(candidate[chan][j][k].retries),
candidate[chan][j][k].age, candidate[chan][j][k].age,
candidate[chan][j][k].crc, candidate[chan][j][k].crc,
@ -611,7 +615,7 @@ static void pick_best_candidate (int chan)
dlq_rec_frame (chan, j, k, dlq_rec_frame (chan, j, k,
candidate[chan][j][k].packet_p, candidate[chan][j][k].packet_p,
candidate[chan][j][k].alevel, candidate[chan][j][k].alevel,
candidate[chan][j][k].is_fx25, candidate[chan][j][k].fec_type,
(int)(candidate[chan][j][k].retries), (int)(candidate[chan][j][k].retries),
spectrum); spectrum);

View File

@ -17,8 +17,8 @@ void multi_modem_process_sample (int c, int audio_sample);
int multi_modem_get_dc_average (int chan); int multi_modem_get_dc_average (int chan);
// Deprecated. Replace with ...packet // 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 #endif

View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // 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 // 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 // it under the terms of the GNU General Public License as published by
@ -235,7 +235,7 @@ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp, int is_aprs)
pfstate.is_aprs = is_aprs; pfstate.is_aprs = is_aprs;
if (is_aprs) { if (is_aprs) {
decode_aprs (&pfstate.decoded, pp, 1, 0); decode_aprs (&pfstate.decoded, pp, 1, NULL);
} }
next_token(&pfstate); next_token(&pfstate);
@ -546,7 +546,8 @@ static int parse_filter_spec (pfstate_t *pf)
/* b - budlist */ /* b - budlist */
else if (pf->token_str[0] == 'b' && ispunct(pf->token_str[1])) { 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]; char addr[AX25_MAX_ADDR_LEN];
ax25_get_addr_with_ssid (pf->pp, AX25_SOURCE, addr); ax25_get_addr_with_ssid (pf->pp, AX25_SOURCE, addr);
result = filt_bodgu (pf, 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])) { else if (pf->token_str[0] == 'd' && ispunct(pf->token_str[1])) {
int n; int n;
// loop on all digipeaters // Loop on all AX.25 digipeaters.
result = 0; result = 0;
for (n = AX25_REPEATER_1; result == 0 && n < ax25_get_num_addr (pf->pp); n++) { 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. // 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])) { else if (pf->token_str[0] == 'v' && ispunct(pf->token_str[1])) {
int n; int n;
// loop on all digipeaters (mnemonic Via) // loop on all AX.25 digipeaters (mnemonic Via)
result = 0; result = 0;
for (n = AX25_REPEATER_1; result == 0 && n < ax25_get_num_addr (pf->pp); n++) { for (n = AX25_REPEATER_1; result == 0 && n < ax25_get_num_addr (pf->pp); n++) {
// This is different than the previous "d" filter. // 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])) { 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); result = filt_bodgu (pf, pf->decoded.g_addressee);
if (s_debug >= 2) { 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])) { else if (pf->token_str[0] == 'u' && ispunct(pf->token_str[1])) {
/* Probably want to exclude mic-e types */ /* 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])) { 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)); (void) ax25_get_info (pf->pp, (unsigned char **)(&infop));
text_color_set(DW_COLOR_DEBUG); 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); dw_printf (" %s returns %s for message to %s\n", pf->token_str, bool2text(result), pf->decoded.g_addressee);
} }
else { else {
@ -832,31 +838,17 @@ static int filt_bodgu (pfstate_t *pf, char *arg)
* 0 = no * 0 = no
* -1 = error detected * -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 * http://www.aprs-is.net/javAPRSFilter.aspx
* *
* Most of these simply check the first byte of the information part. * Mostly use g_packet_type and g_message_subtype from decode_aprs.
* Trying to detect NWS information is a little trickier. *
* References:
* http://www.aprs-is.net/WX/ * 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) static int filt_t (pfstate_t *pf)
{ {
char src[AX25_MAX_ADDR_LEN]; char src[AX25_MAX_ADDR_LEN];
@ -873,108 +865,60 @@ static int filt_t (pfstate_t *pf)
switch (*f) { switch (*f) {
case 'p': /* Position */ case 'p': /* Position */
if (*infop == '!') return (1); if (pf->decoded.g_packet_type == packet_type_position) return(1);
if (*infop == '/') return (1);
if (*infop == '=') return (1);
if (*infop == '@') return (1);
if (*infop == '\'') return (1); // MIC-E
if (*infop == '`') return (1); // MIC-E
// What if we have "_" symbol code for weather?
// Still consider as position.
// The same packet can match more than one type here.
break; break;
case 'o': /* Object */ case 'o': /* Object */
if (*infop == ';') return (1); if (pf->decoded.g_packet_type == packet_type_object) return(1);
break; break;
case 'i': /* Item */ case 'i': /* Item */
if (*infop == ')') return (1); if (pf->decoded.g_packet_type == packet_type_item) return(1);
break; break;
case 'm': /* Message */ case 'm': // Any "message."
if (*infop == ':' && ! is_telem_metadata(infop)) return (1); if (pf->decoded.g_packet_type == packet_type_message) return(1);
break; break;
case 'q': /* Query */ case 'q': /* Query */
if (*infop == '?') return (1); if (pf->decoded.g_packet_type == packet_type_query) return(1);
break; break;
case 'c': /* station Capabilities - my extension */ case 'c': /* station Capabilities - my extension */
/* Most often used for IGate statistics. */ /* Most often used for IGate statistics. */
if (*infop == '<') return (1); if (pf->decoded.g_packet_type == packet_type_capabilities) return(1);
break; break;
case 's': /* Status */ case 's': /* Status */
if (*infop == '>') return (1); if (pf->decoded.g_packet_type == packet_type_status) return(1);
break; break;
case 't': /* Telemetry */ case 't': /* Telemetry data or metadata */
if (*infop == 'T') return (1); if (pf->decoded.g_packet_type == packet_type_telemetry) return(1);
if (is_telem_metadata(infop)) return (1);
break; break;
case 'u': /* User-defined */ case 'u': /* User-defined */
if (*infop == '{') return (1); if (pf->decoded.g_packet_type == packet_type_userdefined) return(1);
break; break;
case 'h': /* third party Header - my extension */ case 'h': /* has third party Header - my extension */
if (*infop == '}') return (1); if (pf->decoded.g_has_thirdparty_header) return (1);
break; break;
case 'w': /* Weather */ case 'w': /* Weather */
if (*infop == '*') return (1); // Peet Bros if (pf->decoded.g_packet_type == packet_type_weather) return(1);
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);
/* Positions !=/@ with symbol code _ are weather. */
/* Object with _ symbol is also weather. APRS protocol spec page 66. */ /* 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 == ';' && if ((pf->decoded.g_packet_type == packet_type_position ||
pf->decoded.g_symbol_code == '_') return (1); pf->decoded.g_packet_type == packet_type_object) && pf->decoded.g_symbol_code == '_') return (1);
// TODO: need more test cases at end for new weather cases.
break; break;
case 'n': /* NWS format */ case 'n': /* NWS format */
/* if (pf->decoded.g_packet_type == packet_type_nws) return(1);
* 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);
break; break;
default: default:
@ -990,6 +934,7 @@ static int filt_t (pfstate_t *pf)
/*------------------------------------------------------------------------------ /*------------------------------------------------------------------------------
* *
* Name: filt_r * Name: filt_r
@ -1278,7 +1223,8 @@ static int filt_s (pfstate_t *pf)
* *
* Name: filt_i * Name: filt_i
* *
* Purpose: IGate messaging default behavior. * Purpose: IGate messaging filter.
* This would make sense only for IS>RF direction.
* *
* Inputs: pf - Pointer to current state information. * Inputs: pf - Pointer to current state information.
* token_str should contain something of format: * token_str should contain something of format:
@ -1307,7 +1253,7 @@ static int filt_s (pfstate_t *pf)
* The rest is distanced, in kilometers, from given point. * The rest is distanced, in kilometers, from given point.
* *
* Examples: * Examples:
* i/60/0 Heard in past 60 minutes directly. * i/180/0 Heard in past 3 hours directly.
* i/45 Past 45 minutes, default max digi hops. * i/45 Past 45 minutes, default max digi hops.
* i/180/3 Default time (3 hours), max 3 digi hops. * i/180/3 Default time (3 hours), max 3 digi hops.
* i/180/8/42.6/-71.3/50. * i/180/8/42.6/-71.3/50.
@ -1317,13 +1263,50 @@ static int filt_s (pfstate_t *pf)
* The basic idea is that we want to transmit a "message" only if the * The basic idea is that we want to transmit a "message" only if the
* addressee has been heard recently and is not too far away. * addressee has been heard recently and is not too far away.
* *
* That is so we can distinguish messages addressed to a specific
* station, and other sundry uses of the addressee field.
*
* After passing along a "message" we will also allow the next * After passing along a "message" we will also allow the next
* position report from the sender of the "message." * position report from the sender of the "message."
* That is done somewhere else. We are not concerned with it here. * That is done somewhere else. We are not concerned with it here.
* *
* IMHO, the rules here are too restrictive. * IMHO, the rules here are too restrictive.
* *
* FIXME -explain * 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.
* *
*------------------------------------------------------------------------------*/ *------------------------------------------------------------------------------*/
@ -1352,9 +1335,9 @@ static int filt_i (pfstate_t *pf)
double km = G_UNKNOWN; double km = G_UNKNOWN;
char src[AX25_MAX_ADDR_LEN]; //char src[AX25_MAX_ADDR_LEN];
char *infop = NULL; //char *infop = NULL;
int info_len; //int info_len;
//char *f; //char *f;
//char addressee[AX25_MAX_ADDR_LEN]; //char addressee[AX25_MAX_ADDR_LEN];
@ -1428,20 +1411,7 @@ static int filt_i (pfstate_t *pf)
* Get source address and info part. * Get source address and info part.
* Addressee has already been extracted into pf->decoded.g_addressee. * Addressee has already been extracted into pf->decoded.g_addressee.
*/ */
if (pf->decoded.g_packet_type != packet_type_message) return(0);
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 defined(PFTEST) || defined(DIGITEST) // TODO: test functionality too, not just syntax. #if defined(PFTEST) || defined(DIGITEST) // TODO: test functionality too, not just syntax.
@ -1481,7 +1451,7 @@ static int filt_i (pfstate_t *pf)
* the past minute, rather than the usual 180 minutes for the addressee. * 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); if (was_heard) return (0);
@ -1635,24 +1605,29 @@ int main ()
pftest (112, "t/t", "WM1X>APU25N:@210147z4235.39N/07106.58W_359/000g000t027r000P000p000h89b10234/WX REPORT {UIV32N}<0x0d>", 0); 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); 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 (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 (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); 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 (122, "t/p", "CWAPID>APRS::SKYCWA :DDHHMMz,ADVISETYPE,zcs{seq#", 0);
pftest (123, "t/p", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{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 (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 (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 (127, "t/", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", 0);
pftest (128, "t/c", "S0RCE>DEST:<stationcapabilities", 1); pftest (128, "t/c", "S0RCE>DEST:<stationcapabilities", 1);
pftest (129, "t/h", "S0RCE>DEST:<stationcapabilities", 0); pftest (129, "t/h", "S0RCE>DEST:<stationcapabilities", 0);
pftest (130, "t/h", "S0RCE>DEST:}thirdpartyheaderwhatever", 1); 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:}thirdpartyheaderwhatever", 0); 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 (140, "r/42.6/-71.3/10", "WB2OSZ-5>APDW12,WIDE1-1,WIDE2-1:!4237.14NS07120.83W#PHG7140Chelmsford MA", 1);
pftest (141, "r/42.6/-71.3/10", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", 0); pftest (141, "r/42.6/-71.3/10", "WA1PLE-5>APWW10,W1MHL,N8VIM,WIDE2*:@022301h4208.75N/07115.16WoAPRS-IS for Win32", 0);
@ -1707,13 +1682,13 @@ int main ()
pftest (203, "t/w t/w", "CWAPID>APRS:;CWAttttz *DDHHMMzLATLONICONADVISETYPE{seq#", -1); 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 (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 (210, "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 (212, "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 (213, "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 (214, "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 (215, "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 (216, "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 (217, "i/30/8", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1);
// FIXME: behaves differently on Windows and Linux. Why? // 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. // On Windows we have our own version of strsep because it's not in the MS library.
@ -1721,7 +1696,12 @@ int main ()
//pftest (228, "i/30/", "WB2OSZ-5>APDW14::W2UB :Happy Birthday{001", 1); //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 (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 (240, "s/", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1);
pftest (241, "s/'/O/-/#/_", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1); pftest (241, "s/'/O/-/#/_", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1);
@ -1731,12 +1711,36 @@ int main ()
pftest (245, "s//", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1); pftest (245, "s//", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1);
pftest (246, "s///", "WB2OSZ-5>APDW12:!4237.14N/07120.83WOPHG7140Chelmsford MA", -1); 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.
// TODO: to be continued... 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... directed query ...
if (error_count > 0) { if (error_count > 0) {
text_color_set (DW_COLOR_ERROR); 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); exit (EXIT_FAILURE);
} }
text_color_set (DW_COLOR_REC); text_color_set (DW_COLOR_REC);

129
src/ptt.c
View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // 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 // 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 // it under the terms of the GNU General Public License as published by
@ -162,6 +162,10 @@
#include <hamlib/rig.h> #include <hamlib/rig.h>
#endif #endif
#ifdef USE_GPIOD
#include <gpiod.h>
#endif
/* So we can have more common code for fd. */ /* So we can have more common code for fd. */
typedef int HANDLE; typedef int HANDLE;
#define INVALID_HANDLE_VALUE (-1) #define INVALID_HANDLE_VALUE (-1)
@ -176,6 +180,7 @@ typedef int HANDLE;
#include "audio.h" #include "audio.h"
#include "ptt.h" #include "ptt.h"
#include "dlq.h" #include "dlq.h"
#include "demod.h" // to mute recv audio during xmit if half duplex.
#if __WIN32__ #if __WIN32__
@ -357,6 +362,9 @@ static void get_access_to_gpio (const char *path)
* We don't have permission. * We don't have permission.
* Try a hack which requires that the user be set up to use sudo without a password. * Try a hack which requires that the user be set up to use sudo without a password.
*/ */
// FIXME: I think this was a horrible work around for some early release that
// did not give gpio permission to the pi user. This should go.
// Provide recovery instructions when there is a permission failure.
if (ptt_debug_level >= 2) { if (ptt_debug_level >= 2) {
text_color_set(DW_COLOR_ERROR); // debug message but different color so it stands out. text_color_set(DW_COLOR_ERROR); // debug message but different color so it stands out.
@ -464,6 +472,20 @@ void export_gpio(int ch, int ot, int invert, int direction)
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Error writing \"%s\" to %s, errno=%d\n", stemp, gpio_export_path, e); dw_printf ("Error writing \"%s\" to %s, errno=%d\n", stemp, gpio_export_path, e);
dw_printf ("%s\n", strerror(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); exit (1);
} }
} }
@ -494,6 +516,13 @@ void export_gpio(int ch, int ot, int invert, int direction)
* matching the pattern "gpio61_*". * matching the pattern "gpio61_*".
* *
* We are finally implementing the third choice. * We are finally implementing the third choice.
*/
/*
* Then we have the Odroid board with GPIO numbers starting around 480.
* Can we simply use those numbers?
* Apparently, the export names look like GPIOX.17
* https://wiki.odroid.com/odroid-c4/hardware/expansion_connectors#gpio_map_for_wiringpi_library
*/ */
struct dirent **file_list; struct dirent **file_list;
@ -623,6 +652,31 @@ void export_gpio(int ch, int ot, int invert, int direction)
get_access_to_gpio (gpio_value_path); 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__ */ #endif /* not __WIN32__ */
@ -639,7 +693,8 @@ void export_gpio(int ch, int ot, int invert, int direction)
* ptt_method Method for PTT signal. * ptt_method Method for PTT signal.
* PTT_METHOD_NONE - not configured. Could be using VOX. * PTT_METHOD_NONE - not configured. Could be using VOX.
* PTT_METHOD_SERIAL - serial (com) port. * 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_LPT - Parallel printer port.
* PTT_METHOD_HAMLIB - HAMLib rig control. * PTT_METHOD_HAMLIB - HAMLib rig control.
* PTT_METHOD_CM108 - GPIO pins of CM108 etc. USB Audio. * PTT_METHOD_CM108 - GPIO pins of CM108 etc. USB Audio.
@ -718,12 +773,13 @@ void ptt_init (struct audio_s *audio_config_p)
if (ptt_debug_level >= 2) { if (ptt_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG); 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, ch,
otnames[ot], otnames[ot],
audio_config_p->achan[ch].octrl[ot].ptt_method, 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_device,
audio_config_p->achan[ch].octrl[ot].ptt_line, 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].out_gpio_num,
audio_config_p->achan[ch].octrl[ot].ptt_lpt_bit, audio_config_p->achan[ch].octrl[ot].ptt_lpt_bit,
audio_config_p->achan[ch].octrl[ot].ptt_invert); audio_config_p->achan[ch].octrl[ot].ptt_invert);
@ -869,7 +925,28 @@ void ptt_init (struct audio_s *audio_config_p)
if (using_gpio) { if (using_gpio) {
get_access_to_gpio ("/sys/class/gpio/export"); 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 * We should now be able to create the device nodes for
* the pins we want to use. * the pins we want to use.
@ -980,6 +1057,8 @@ void ptt_init (struct audio_s *audio_config_p)
for (ot = 0; ot < NUM_OCTYPES; ot++) { for (ot = 0; ot < NUM_OCTYPES; ot++) {
if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_HAMLIB) { if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_HAMLIB) {
if (ot == OCTYPE_PTT) { if (ot == OCTYPE_PTT) {
int err = -1;
int tries = 0;
/* For "AUTO" model, try to guess what is out there. */ /* For "AUTO" model, try to guess what is out there. */
@ -1044,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.parity = RIG_PARITY_NONE;
rig[ch][ot]->state.rigport.parm.serial.handshake = RIG_HANDSHAKE_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) { if (err != RIG_OK) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Hamlib Rig open error %d: %s\n", err, rigerror(err)); dw_printf ("Hamlib Rig open error %d: %s\n", err, rigerror(err));
rig_cleanup (rig[ch][ot]); rig_cleanup (rig[ch][ot]);
rig[ch][ot] = NULL; rig[ch][ot] = NULL;
continue; exit (1);
} }
/* Successful. Later code should check for rig[ch][ot] not NULL. */ /* Successful. Later code should check for rig[ch][ot] not NULL. */
@ -1131,6 +1221,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) void ptt_set (int ot, int chan, int ptt_signal)
{ {
@ -1154,6 +1246,19 @@ void ptt_set (int ot, int chan, int ptt_signal)
return; 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. * The data link state machine has an interest in activity on the radio channel.
* This is a very convenient place to get that information. * This is a very convenient place to get that information.
@ -1259,6 +1364,18 @@ void ptt_set (int ot, int chan, int ptt_signal)
close (fd); 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 #endif
/* /*

View File

@ -107,7 +107,6 @@
#include "recv.h" #include "recv.h"
#include "dtmf.h" #include "dtmf.h"
#include "aprs_tt.h" #include "aprs_tt.h"
#include "dtime_now.h"
#include "ax25_link.h" #include "ax25_link.h"
@ -208,7 +207,7 @@ static void * recv_adev_thread (void *arg)
int eof; int eof;
/* This audio device can have one (mono) or two (stereo) channels. */ /* 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 first_chan = ADEVFIRSTCHAN(a);
int num_chan = save_pa->adev[a].num_channels; int num_chan = save_pa->adev[a].num_channels;
@ -235,6 +234,8 @@ static void * recv_adev_thread (void *arg)
if (audio_sample >= 256 * 256) if (audio_sample >= 256 * 256)
eof = 1; 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); multi_modem_process_sample(first_chan + c, audio_sample);
@ -263,14 +264,14 @@ static void * recv_adev_thread (void *arg)
aprs_tt_button (first_chan + c, tt); aprs_tt_button (first_chan + c, tt);
} }
} }
} } // for c is just 0 or 0 then 1
/* When a complete frame is accumulated, */ /* When a complete frame is accumulated, */
/* dlq_rec_frame, is called. */ /* dlq_rec_frame, is called. */
/* recv_process, below, drains the queue. */ /* recv_process, below, drains the queue. */
} } // while !eof on audio stream
// What should we do now? // What should we do now?
// Seimply terminate the application? // Seimply terminate the application?
@ -338,7 +339,7 @@ void recv_process (void)
* - Digipeater. * - 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);
/* /*

View File

@ -51,8 +51,8 @@
#define MAGIC2 0x56788765 #define MAGIC2 0x56788765
static int new_count = 0; volatile static int new_count = 0;
static int delete_count = 0; volatile static int delete_count = 0;
/*********************************************************************************** /***********************************************************************************
@ -429,6 +429,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 * Name: rrbb_get_is_scrambled
@ -492,6 +536,7 @@ int rrbb_get_prev_descram (rrbb_t b)
} }
/* end rrbb.c */ /* end rrbb.c */

View File

@ -33,6 +33,7 @@ typedef struct rrbb_s {
int slice; /* Which slicer. */ int slice; /* Which slicer. */
alevel_t alevel; /* Received audio level at time of frame capture. */ 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. */ unsigned int len; /* Current number of samples in array. */
int is_scrambled; /* Is data scrambled G3RUH / K9NG style? */ 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); void rrbb_set_audio_level (rrbb_t b, alevel_t alevel);
alevel_t rrbb_get_audio_level (rrbb_t b); 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_is_scrambled (rrbb_t b);
int rrbb_get_descram_state (rrbb_t b); int rrbb_get_descram_state (rrbb_t b);
int rrbb_get_prev_descram (rrbb_t b); int rrbb_get_prev_descram (rrbb_t b);

View File

@ -176,6 +176,7 @@
* You can increase the limit by changing the line below. * You can increase the limit by changing the line below.
* A larger number consumes more resources so don't go crazy by making it larger than needed. * A larger number consumes more resources so don't go crazy by making it larger than needed.
*/ */
// FIXME: Put in direwolf.h rather than in .c file. Change name to reflect AGW vs KISS. Update user guide 5.7.
#define MAX_NET_CLIENTS 3 #define MAX_NET_CLIENTS 3
@ -1420,7 +1421,7 @@ static THREAD_F cmd_listen_thread (void *arg)
} }
/* /*
* Call to/from fields are 10 bytes but contents must not exceeed 9 characters. * Call to/from fields are 10 bytes but contents must not exceed 9 characters.
* It's not guaranteed that unused bytes will contain 0 so we * It's not guaranteed that unused bytes will contain 0 so we
* don't issue error message in this case. * don't issue error message in this case.
*/ */
@ -1815,6 +1816,11 @@ static THREAD_F cmd_listen_thread (void *arg)
break; break;
case 'P': /* Application Login */
// Silently ignore it.
break;
case 'X': /* Register CallSign */ case 'X': /* Register CallSign */
{ {
@ -1936,8 +1942,10 @@ static THREAD_F cmd_listen_thread (void *arg)
case 'D': /* Send Connected Data */ case 'D': /* Send Connected Data */
{ {
char callsigns[2][AX25_MAX_ADDR_LEN]; char callsigns[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
const int num_calls = 2; 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_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE]));
strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE])); strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE]));
@ -1950,8 +1958,9 @@ static THREAD_F cmd_listen_thread (void *arg)
case 'd': /* Disconnect, Terminate an AX.25 Connection */ case 'd': /* Disconnect, Terminate an AX.25 Connection */
{ {
char callsigns[2][AX25_MAX_ADDR_LEN]; char callsigns[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
const int num_calls = 2; 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_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE]));
strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE])); strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE]));
@ -2101,15 +2110,14 @@ static THREAD_F cmd_listen_thread (void *arg)
{ {
char callsigns[2][AX25_MAX_ADDR_LEN]; char callsigns[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
const int num_calls = 2; 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_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE]));
strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, 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); dlq_outstanding_frames_request (callsigns, num_calls, cmd.hdr.portx, client);
} }
break; break;

View File

@ -681,7 +681,7 @@ void symbols_from_dest_or_src (char dti, char *src, char *dest, char *symtab, ch
// The position and object formats all contain a proper symbol and table. // The position and object formats all contain a proper symbol and table.
// There doesn't seem to be much reason to have a symbol for something without // There doesn't seem to be much reason to have a symbol for something without
// a postion because it would not show up on a map. // a position because it would not show up on a map.
// This just seems to be a remnant of something used long ago and no longer needed. // This just seems to be a remnant of something used long ago and no longer needed.
// The protocol spec mentions a "MIM tracker" but I can't find any references to it. // The protocol spec mentions a "MIM tracker" but I can't find any references to it.

View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // This file is part of Dire Wolf, an amateur radio packet TNC.
// //
// Copyright (C) 2011, 2012, 2014, 2015, 2016 John Langner, WB2OSZ // Copyright (C) 2011, 2012, 2014, 2015, 2016, 2023 John Langner, WB2OSZ
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -50,7 +50,8 @@
#include "audio.h" #include "audio.h"
#include "tq.h" #include "tq.h"
#include "dedupe.h" #include "dedupe.h"
#include "igate.h"
#include "dtime_now.h"
@ -195,6 +196,9 @@ void tq_init (struct audio_s *audio_config_p)
* *
* Inputs: chan - Channel, 0 is first. * Inputs: chan - Channel, 0 is first.
* *
* New in 1.7:
* Channel can be assigned to IGate rather than a radio.
*
* prio - Priority, use TQ_PRIO_0_HI for digipeated or * prio - Priority, use TQ_PRIO_0_HI for digipeated or
* TQ_PRIO_1_LO for normal. * TQ_PRIO_1_LO for normal.
* *
@ -247,6 +251,43 @@ void tq_append (int chan, int prio, packet_t pp)
} }
#endif #endif
// New in 1.7 - A channel can be assigned to the IGate rather than a radio.
#ifndef DIGITEST // avoid dtest link error
if (save_audio_config_p->chan_medium[chan] == MEDIUM_IGATE) {
char ts[100]; // optional time stamp.
if (strlen(save_audio_config_p->timestamp_format) > 0) {
char tstmp[100];
timestamp_user_format (tstmp, sizeof(tstmp), save_audio_config_p->timestamp_format);
strlcpy (ts, " ", sizeof(ts)); // space after channel.
strlcat (ts, tstmp, sizeof(ts));
}
else {
strlcpy (ts, "", sizeof(ts));
}
char stemp[256]; // Formated addresses.
ax25_format_addrs (pp, stemp);
unsigned char *pinfo;
int info_len = ax25_get_info (pp, &pinfo);
text_color_set(DW_COLOR_XMIT);
dw_printf ("[%d>is%s] ", chan, ts);
dw_printf ("%s", stemp); /* stations followed by : */
ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp));
dw_printf ("\n");
igate_send_rec_packet (chan, pp);
ax25_delete(pp);
return;
}
#endif
// Normal case - put in queue for radio transmission.
// Error if trying to transmit to a radio channel which was not configured.
if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->chan_medium[chan] == MEDIUM_NONE) { if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->chan_medium[chan] == MEDIUM_NONE) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan); dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan);
@ -281,8 +322,6 @@ void tq_append (int chan, int prio, packet_t pp)
* The check would allow an unlimited number of other types. * The check would allow an unlimited number of other types.
* *
* Limit was 20. Changed to 100 in version 1.2 as a workaround. * Limit was 20. Changed to 100 in version 1.2 as a workaround.
*
* Implementing the 6PACK protocol is probably the proper solution.
*/ */
if (ax25_is_aprs(pp) && tq_count(chan,prio,"","",0) > 100) { if (ax25_is_aprs(pp) && tq_count(chan,prio,"","",0) > 100) {

View File

@ -882,7 +882,7 @@ static void xmit_object_report (int i, int first_time)
* IGate. * IGate.
* *
* When transmitting over the radio, it gets sent multiple times, to help * When transmitting over the radio, it gets sent multiple times, to help
* probablity of being heard, with increasing delays between. * probability of being heard, with increasing delays between.
* *
* The other methods are reliable so we only want to send it once. * The other methods are reliable so we only want to send it once.
*/ */

View File

@ -298,7 +298,7 @@ void waypoint_send_sentence (char *name_in, double dlat, double dlong, char symt
dw_printf ("waypoint_send_sentence (\"%s\", \"%c%c\")\n", name_in, symtab, symbol); dw_printf ("waypoint_send_sentence (\"%s\", \"%c%c\")\n", name_in, symtab, symbol);
#endif #endif
// Don't waste time if no destintations specified. // Don't waste time if no destinations specified.
if (s_waypoint_serial_port_fd == MYFDERROR && if (s_waypoint_serial_port_fd == MYFDERROR &&
s_waypoint_udp_sock_fd == -1) { s_waypoint_udp_sock_fd == -1) {

View File

@ -600,7 +600,7 @@ static void * xmit_thread (void *arg)
// I don't know if this in some official specification // I don't know if this in some official specification
// somewhere, but it is generally agreed that APRS digipeaters // somewhere, but it is generally agreed that APRS digipeaters
// should send only one frame at a time rather than // should send only one frame at a time rather than
// bunding multiple frames into a single transmission. // bundling multiple frames into a single transmission.
// Discussion here: http://lists.tapr.org/pipermail/aprssig_lists.tapr.org/2021-September/049034.html // Discussion here: http://lists.tapr.org/pipermail/aprssig_lists.tapr.org/2021-September/049034.html
break; break;

View File

@ -1,6 +1,7 @@
[Unit] [Unit]
Description=Direwolf Sound Card-based AX.25 TNC Description=Direwolf Sound Card-based AX.25 TNC
After=sound.target After=sound.target
After=network.target
[Service] [Service]
EnvironmentFile=/etc/sysconfig/direwolf EnvironmentFile=/etc/sysconfig/direwolf
@ -22,3 +23,5 @@ ReadWritePaths=/var/log/direwolf
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
DefaultInstance=1 DefaultInstance=1
# alternate version: https://www.f4fxl.org/start-direwolf-at-boot-the-systemd-way/

View File

@ -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-b_FILE "check-modem2400-b")
set(TEST_CHECK-MODEM2400-g_FILE "check-modem2400-g") set(TEST_CHECK-MODEM2400-g_FILE "check-modem2400-g")
set(TEST_CHECK-MODEM4800_FILE "check-modem4800") set(TEST_CHECK-MODEM4800_FILE "check-modem4800")
set(TEST_CHECK-MODEMEAS_FILE "check-modemeas")
# generate the scripts that run the tests # generate the scripts that run the tests
@ -101,6 +102,12 @@ configure_file(
@ONLY @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 # global includes
# not ideal but not so slow # not ideal but not so slow
@ -498,6 +505,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-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-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-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}")

4
test/scripts/check-modemeas Executable file
View File

@ -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