diff --git a/CHANGES.md b/CHANGES.md
index 4196f7dd..589cd5a2 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -2,8 +2,29 @@
# Revision History #
-## Version 1.6 -- October 2020 ##
+## Version 1.7 -- Under Development ('dev' branch) ##
+
+
+### 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.
+
+- 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)
+- The transmit calibration tone (-x) command line option now accepts a radio channel number and/or a single letter mode: a = alternate tones, m = mark tone, s = space tone, p = PTT only no sound.
+
+- 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.
+
+ > After: "CHANNEL 1" (or other channel)
+ >
+ > Add: "FX25TX 1" (or 16 or 32 or 64)
+
+
+## Version 1.6 -- October 2020 ##
### New Build Procedure: ###
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9e710f52..0b6402e0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,12 +4,13 @@ project(direwolf)
# configure version
set(direwolf_VERSION_MAJOR "1")
-set(direwolf_VERSION_MINOR "6")
+set(direwolf_VERSION_MINOR "7")
set(direwolf_VERSION_PATCH "0")
-set(direwolf_VERSION_SUFFIX "")
+set(direwolf_VERSION_SUFFIX "Development")
# options
-option(FORCE_SSE "Compile with SSE instruction only" OFF)
+# See Issue 297.
+option(FORCE_SSE "Compile with SSE instruction only" ON)
option(FORCE_SSSE3 "Compile with SSSE3 instruction only" OFF)
option(FORCE_SSE41 "Compile with SSE4.1 instruction only" OFF)
option(OPTIONAL_TEST "Compile optional test (might be broken)" OFF)
@@ -31,15 +32,6 @@ set(COPYRIGHT "Copyright (c) 2019 John Langner, WB2OSZ. All rights reserved.")
add_definitions("-DCOPYRIGHT=\"${COPYRIGHT}\"")
set(IDENTIFIER "com.${COMPANY}.${APPLICATION_NAME}")
add_definitions("-DIDENTIFIER=\"${IDENTIFIER}\"")
-# raspberry as only lxterminal not xterm
-if(NOT (WIN32 OR CYGWIN))
- find_program(BINARY_TERMINAL_BIN lxterminal)
- if(BINARY_TERMINAL_BIN)
- set(APPLICATION_DESKTOP_EXEC "${BINARY_TERMINAL_BIN} -e ${CMAKE_PROJECT_NAME}")
- else()
- set(APPLICATION_DESKTOP_EXEC "xterm -e ${CMAKE_PROJECT_NAME}")
- endif()
-endif()
find_package(Git)
if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git/")
@@ -71,6 +63,7 @@ set(CUSTOM_SRC_DIR "${CMAKE_SOURCE_DIR}/src")
set(CUSTOM_EXTERNAL_DIR "${CMAKE_SOURCE_DIR}/external")
set(CUSTOM_MISC_DIR "${CUSTOM_EXTERNAL_DIR}/misc")
set(CUSTOM_REGEX_DIR "${CUSTOM_EXTERNAL_DIR}/regex")
+set(CUSTOM_HIDAPI_DIR "${CUSTOM_EXTERNAL_DIR}/hidapi")
set(CUSTOM_GEOTRANZ_DIR "${CUSTOM_EXTERNAL_DIR}/geotranz")
set(CUSTOM_DATA_DIR "${CMAKE_SOURCE_DIR}/data")
set(CUSTOM_SCRIPTS_DIR "${CMAKE_SOURCE_DIR}/scripts")
@@ -131,7 +124,7 @@ endif()
# auto include current directory
set(CMAKE_INCLUDE_CURRENT_DIR ON)
-# set OS dependant variables
+# set OS dependent variables
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(LINUX TRUE)
@@ -143,6 +136,10 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
configure_file("${CMAKE_SOURCE_DIR}/cmake/cpack/${CMAKE_PROJECT_NAME}.desktop.in"
"${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.desktop" @ONLY)
+elseif(${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
+ set(OPENBSD TRUE)
+ set(HAVE_SNDIO TRUE)
+
elseif(APPLE)
if("${CMAKE_OSX_DEPLOYMENT_TARGET}" STREQUAL "")
message(STATUS "Build for macOS target: local version")
@@ -156,6 +153,10 @@ elseif(APPLE)
set(CMAKE_MACOSX_RPATH ON)
message(STATUS "RPATH support: ${CMAKE_MACOSX_RPATH}")
+ # just blindly enable dns-sd
+ set(USE_MACOS_DNSSD ON)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_MACOS_DNSSD")
+
elseif (WIN32)
if(NOT VS2015 AND NOT VS2017)
message(FATAL_ERROR "You must use Microsoft Visual Studio 2015 or 2017 as compiler")
@@ -195,10 +196,16 @@ if (C_CLANG OR C_GCC)
# I also took out -Wextra because it spews out so much noise a serious problem was not noticed.
# It might go back in someday when I have more patience to clean up all the warnings.
#
+
+ # TODO:
+ # Try error checking -fsanitize=bounds-strict -fsanitize=leak
+ # Requires libubsan and liblsan, respectively.
+
###set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wvla -ffast-math -ftree-vectorize -D_XOPEN_SOURCE=600 -D_DEFAULT_SOURCE ${EXTRA_FLAGS}")
if(FREEBSD)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wvla -ffast-math -ftree-vectorize -D_DEFAULT_SOURCE ${EXTRA_FLAGS}")
else()
+ #set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wvla -ffast-math -ftree-vectorize -D_GNU_SOURCE -fsanitize=bounds-strict ${EXTRA_FLAGS}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wvla -ffast-math -ftree-vectorize -D_GNU_SOURCE ${EXTRA_FLAGS}")
endif()
#
@@ -246,6 +253,20 @@ else()
endif(WIN32 OR CYGWIN)
# requirements
+
+include(CheckSymbolExists)
+# Some platforms provide their own strlcpy & strlcat. (BSD, MacOSX)
+# Others don't so we provide our own. (Most, but not all Linux)
+# Define the preprocessor macro so libgps does not supply its own version.
+check_symbol_exists(strlcpy string.h HAVE_STRLCPY)
+if(HAVE_STRLCPY)
+ add_compile_options(-DHAVE_STRLCPY)
+endif()
+check_symbol_exists(strlcat string.h HAVE_STRLCAT)
+if(HAVE_STRLCAT)
+ add_compile_options(-DHAVE_STRLCAT)
+endif()
+
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
@@ -276,6 +297,17 @@ if(LINUX)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_CM108")
endif()
+ find_package(Avahi)
+ if(AVAHI_CLIENT_FOUND)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_AVAHI_CLIENT")
+ endif()
+
+elseif (HAVE_SNDIO)
+ find_package(sndio REQUIRED)
+ if(SNDIO_FOUND)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_SNDIO")
+ endif()
+
elseif (NOT WIN32 AND NOT CYGWIN)
find_package(Portaudio REQUIRED)
if(PORTAUDIO_FOUND)
@@ -287,8 +319,12 @@ else()
set(ALSA_LIBRARIES "")
set(UDEV_INCLUDE_DIRS "")
set(UDEV_LIBRARIES "")
+ # Version 1.7 supports CM108/CM119 GPIO PTT for Windows.
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_CM108")
set(PORTAUDIO_INCLUDE_DIRS "")
set(PORTAUDIO_LIBRARIES "")
+ set(SNDIO_INCLUDE_DIRS "")
+ set(SNDIO_LIBRARIES "")
endif()
# manage and fetch new data
@@ -297,6 +333,7 @@ add_subdirectory(data)
# external libraries
add_subdirectory(${CUSTOM_GEOTRANZ_DIR})
add_subdirectory(${CUSTOM_REGEX_DIR})
+add_subdirectory(${CUSTOM_HIDAPI_DIR})
add_subdirectory(${CUSTOM_MISC_DIR})
# direwolf source code and utilities
diff --git a/README.md b/README.md
index 51b29019..fc08c69a 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-
+
# Dire Wolf #
### Decoded Information from Radio Emissions for Windows Or Linux Fans ###
@@ -9,11 +9,15 @@ Why waste $200 and settle for mediocre receive performance from a 1980's technol

-Dire Wolf now includes [FX.25](https://en.wikipedia.org/wiki/FX.25_Forward_Error_Correction/) which adds Forward Error Correction (FEC) in a way that is completely compatible with existing systems. If both ends are capable of FX.25, your information will continue to get through under conditions where regular AX.25 is completely useless.
+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 is a modern software replacement for the old 1980's style TNC built with special hardware.
+Version 1.7 adds [IL2P](https://en.wikipedia.org/wiki/Improved_Layer_2_Protocol), a different method of FEC with less overhead.
+
+
+
+### Dire Wolf is a modern software replacement for the old 1980's style TNC built with special hardware. ###
Without any additional software, it can perform as:
@@ -131,15 +135,17 @@ On Debian / Ubuntu / Raspbian / Raspberry Pi OS:
sudo apt-get install cmake
sudo apt-get install libasound2-dev
sudo apt-get install libudev-dev
+ sudo apt-get install libavahi-client-dev
Or on Red Hat / Fedora / CentOS:
- sudo yum install git
- sudo yum install gcc
- sudo yum install gcc-c++
- sudo yum install make
+ sudo yum install git
+ sudo yum install gcc
+ sudo yum install gcc-c++
+ sudo yum install make
sudo yum install alsa-lib-devel
sudo yum install libudev-devel
+ sudo yum install avahi-devel
CentOS 6 & 7 currently have cmake 2.8 but we need 3.1 or later.
First you need to enable the EPEL repository. Add a symlink if you don't already have the older version and want to type cmake rather than cmake3.
@@ -190,6 +196,9 @@ Read the **User Guide** in the [**doc** directory](https://github.com/wb2osz/dir
If you have problems, post them to the [Dire Wolf packet TNC](https://groups.io/g/direwolf) discussion group.
+You can also install a pre-built version from Mac Ports. Keeping this up to date depends on volunteers who perform the packaging. This version could lag behind development.
+
+ sudo port install direwolf
## Join the conversation ##
diff --git a/cmake/cpack/direwolf.desktop.in b/cmake/cpack/direwolf.desktop.in
index 79c63aa6..0e64ff9e 100644
--- a/cmake/cpack/direwolf.desktop.in
+++ b/cmake/cpack/direwolf.desktop.in
@@ -1,10 +1,10 @@
[Desktop Entry]
Name=@APPLICATION_NAME@
Comment=APRS Soundcard TNC
-Exec=@APPLICATION_DESKTOP_EXEC@
-Icon=@CMAKE_PROJECT_NAME@_icon.png
+Exec=@CMAKE_PROJECT_NAME@
+Icon=@CMAKE_PROJECT_NAME@_icon
StartupNotify=true
-Terminal=false
+Terminal=true
Type=Application
Categories=HamRadio
Keywords=Ham Radio;APRS;Soundcard TNC;KISS;AGWPE;AX.25
\ No newline at end of file
diff --git a/cmake/modules/FindAvahi.cmake b/cmake/modules/FindAvahi.cmake
new file mode 100644
index 00000000..4a3cdd0b
--- /dev/null
+++ b/cmake/modules/FindAvahi.cmake
@@ -0,0 +1,19 @@
+
+find_library(AVAHI_COMMON_LIBRARY NAMES avahi-common PATHS ${PC_AVAHI_CLIENT_LIBRARY_DIRS})
+if(AVAHI_COMMON_LIBRARY)
+ set(AVAHI_COMMON_FOUND TRUE)
+endif()
+
+find_library(AVAHI_CLIENT_LIBRARY NAMES avahi-client PATHS ${PC_AVAHI_CLIENT_LIBRARY_DIRS})
+if(AVAHI_CLIENT_LIBRARY)
+ set(AVAHI_CLIENT_FOUND TRUE)
+endif()
+
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(AVAHI DEFAULT_MSG AVAHI_COMMON_FOUND AVAHI_CLIENT_FOUND)
+
+if (AVAHI_FOUND)
+ set(AVAHI_INCLUDE_DIRS ${AVAHI_UI_INCLUDE_DIR})
+ set(AVAHI_LIBRARIES ${AVAHI_COMMON_LIBRARY} ${AVAHI_CLIENT_LIBRARY})
+endif()
+
+mark_as_advanced(AVAHI_INCLUDE_DIRS AVAHI_LIBRARIES)
diff --git a/cmake/modules/FindCPUflags.cmake b/cmake/modules/FindCPUflags.cmake
index 802d1cc0..abb9e184 100644
--- a/cmake/modules/FindCPUflags.cmake
+++ b/cmake/modules/FindCPUflags.cmake
@@ -78,7 +78,6 @@ endfunction()
# The default will be set for maximum portability so packagers won't need to
# to anything special.
#
-set(FORCE_SSE 1)
#
# While ENABLE_GENERIC also had the desired result (for x86_64), I don't think
# it is the right approach. It prevents the detection of the architecture,
@@ -354,7 +353,12 @@ elseif(ARCHITECTURE_ARM)
if(C_MSVC)
try_run(RUN_NEON COMPILE_NEON "${CMAKE_BINARY_DIR}/tmp" "${TEST_DIR}/test_arm_neon.cxx" COMPILE_DEFINITIONS /O0)
else()
- try_run(RUN_NEON COMPILE_NEON "${CMAKE_BINARY_DIR}/tmp" "${TEST_DIR}/test_arm_neon.cxx" COMPILE_DEFINITIONS -mfpu=neon -O0)
+ if(${CMAKE_HOST_SYSTEM_PROCESSOR} STREQUAL ${CMAKE_SYSTEM_PROCESSOR})
+ try_run(RUN_NEON COMPILE_NEON "${CMAKE_BINARY_DIR}/tmp" "${TEST_DIR}/test_arm_neon.cxx" COMPILE_DEFINITIONS -mfpu=neon -O0)
+ else()
+ try_compile(COMPILE_NEON "${CMAKE_BINARY_DIR}/tmp" "${TEST_DIR}/test_arm_neon.cxx" COMPILE_DEFINITIONS -mfpu=neon -O0)
+ set(RUN_NEON 0)
+ endif()
endif()
if(COMPILE_NEON AND RUN_NEON EQUAL 0)
set(HAS_NEON ON CACHE BOOL "Architecture has NEON SIMD enabled")
diff --git a/cmake/modules/Findsndio.cmake b/cmake/modules/Findsndio.cmake
new file mode 100644
index 00000000..e7292d5d
--- /dev/null
+++ b/cmake/modules/Findsndio.cmake
@@ -0,0 +1,42 @@
+# - Try to find sndio
+#
+# SNDIO_FOUND - system has sndio
+# SNDIO_LIBRARIES - location of the library for sndio
+# SNDIO_INCLUDE_DIRS - location of the include files for sndio
+
+set(SNDIO_ROOT_DIR
+ "${SNDIO_ROOT_DIR}"
+ CACHE
+ PATH
+ "Directory to search for sndio")
+
+# no need to check pkg-config
+
+find_path(SNDIO_INCLUDE_DIRS
+ NAMES
+ sndio.h
+ PATHS
+ /usr/local/include
+ /usr/include
+ /opt/local/include
+ HINTS
+ ${SNDIO_ROOT_DIR}
+ )
+
+find_library(SNDIO_LIBRARIES
+ NAMES
+ sndio
+ PATHS
+ /usr/local/lib
+ /usr/lib
+ /usr/lib64
+ /opt/local/lib
+ HINTS
+ ${SNDIIO_ROOT_DIR}
+ )
+
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(SNDIO DEFAULT_MSG SNDIO_INCLUDE_DIRS SNDIO_LIBRARIES)
+
+mark_as_advanced(SNDIO_INCLUDE_DIRS SNDIO_LIBRARIES)
diff --git a/conf/99-direwolf-cmedia.rules b/conf/99-direwolf-cmedia.rules
index 587f6168..24e7c160 100644
--- a/conf/99-direwolf-cmedia.rules
+++ b/conf/99-direwolf-cmedia.rules
@@ -3,7 +3,7 @@
# $ ls -l /dev/hidraw*
# crw------- 1 root root 247, 0 Sep 24 09:40 /dev/hidraw0
#
-# An ordinary user, trying to acccess it will be denied.
+# An ordinary user, trying to access it will be denied.
#
# Unnecessarily running applications as root is generally a bad idea because it makes it too easy
# to accidentally trash your system. We need to relax the restrictions so ordinary users can use these devices.
diff --git a/conf/generic.conf b/conf/generic.conf
index b77d5dfa..8630ed55 100644
--- a/conf/generic.conf
+++ b/conf/generic.conf
@@ -263,10 +263,22 @@
%C%
%C%#DTMF
%C%
-%C%#
-%C%# If not using a VOX circuit, the transmitter Push to Talk (PTT)
-%C%# control is usually wired to a serial port with a suitable interface circuit.
-%C%# DON'T connect it directly!
+%L%# If using a C-Media CM108/CM119 or similar USB Audio Adapter,
+%L%# you can use a GPIO pin for PTT control. This is very convenient
+%L%# because a single USB connection is used for both audio and PTT.
+%L%# Example:
+%L%
+%L%#PTT CM108
+%L%
+%W%# If using a C-Media CM108/CM119 or similar USB Audio Adapter,
+%W%# you can use a GPIO pin for PTT control. This is very convenient
+%W%# because a single USB connection is used for both audio and PTT.
+%W%# Example:
+%W%
+%W%#PTT CM108
+%W%%C%#
+%C%# The transmitter Push to Talk (PTT) control can be wired to a serial port
+%C%# with a suitable interface circuit. DON'T connect it directly!
%C%#
%C%# For the PTT command, specify the device and either RTS or DTR.
%C%# RTS or DTR may be preceded by "-" to invert the signal.
diff --git a/data/symbols-new.txt b/data/symbols-new.txt
index 44dcb6b9..aa6fc7d2 100644
--- a/data/symbols-new.txt
+++ b/data/symbols-new.txt
@@ -1,7 +1,6 @@
-APRS SYMBOL OVERLAY and EXTENSION TABLES in APRS 1.2 17 Jun 2018
+APRS SYMBOL OVERLAY and EXTENSION TABLES in APRS 1.2 17 Mar 2021
------------------------------------------------------------------------
-
BACKGROUND: Since October 2007, overlay characters (36/symbol) are
allowed on all symbols. This allows thousands of uniquely specified
symbols instead of the original 188 (94 primary and 94 alternate).
@@ -9,7 +8,9 @@ But the master symbol document, http://aprs.org/symbols/symbolsX.txt,
only has one line per symbol. So this added overlay list below gives
us thousands of new symbol codes.
-17 Jun19: Added several overlays for RAIL symbol
+17 Mar 21 Added L& for LORA Igate
+24 Jun18: Updated CAR symbols
+17 Jun18: Added several overlays for RAIL symbol
03 Apr17: Added Methane Hazard symbol "MH"
13 Feb17: Added Ez = Emergency Power (shelter), Cars: P> = Plugin
S> = Solar powered. Moved Ham club C- to Buildings Ch.
@@ -211,14 +212,18 @@ CARS: #> (Vehicles)
/> = normal car (side view)
\> = Top view and symbol POINTS in direction of travel
#> = Reserve overlays 1-9 for numbered cars (new Aug 2014)
-B> = Battery (was E for electric)
+3> = Model 3 (Tesla)
+B> = BEV - Battery EV(was E for electric)
+D> = DIY - Do it yourself
E> = Ethanol (was electric)
F> = Fuelcell or hydrogen
-H> = Homemade
-P> = Plugin-hybrid
+H> = Hybrid
+L> = Leaf
+P> = PHEV - Plugin-hybrid
S> = Solar powered
T> = Tesla (temporary)
-V> = GM Volt (temporary)
+V> = Volt (temporary)
+X> = Model X
CIVIL DEFENSE or TRIANGLE: #c
/c = Incident Command Post
@@ -269,6 +274,7 @@ FE = (F overlay) Fog was \{
GATEWAYS: #&
/& = HF Gateway <= the original primary table definition
I& = Igate Generic (please use more specific overlay)
+L& - Lora Igate
R& = Receive only IGate (do not send msgs back to RF)
P& = PSKmail node
T& = TX igate with path set to 1 hop only)
@@ -339,17 +345,17 @@ I; = Islands on the air
S; = Summits on the air
W; = WOTA
-POWER or ENERGY: #%
+POWER and ENERGY: #%
/% = DX cluster <= the original primary table definition
C% = Coal
E% = Emergency (new Aug 2014)
-G% = Geothermal
+G% = Gas Turbine
H% = Hydroelectric
N% = Nuclear
P% = Portable (new Aug 2014)
R% = Renewable (hydrogen etc fuels)
S% = Solar
-T% = Turbine
+T% = Thermal (geo)
W% = Wind
RAIL Symbols: #=
diff --git a/data/tocalls.txt b/data/tocalls.txt
index 47ba2e3b..692d6b40 100644
--- a/data/tocalls.txt
+++ b/data/tocalls.txt
@@ -1,44 +1,36 @@
-APRS TO-CALL VERSION NUMBERS 13 Oct 2020
--------------------------------------------------------------------
- WB4APR
+APRS TO-CALL VERSION NUMBERS 14 Dec 2021
+---------------------------------------------------------------------
+ WB4APR
+14 Dec 21 Added APATAR ATA-R APRS Digipeater by TA7W/OH2UDS and TA6AEU
+26 Sep 21 Added APRRDZ EPS32 https://github.com/dl9rdz/rdz_ttgo_sonde
+18 Sep 21 Added APCSS for AMSAT Cubesat Simulator https://cubesatsim.org
+16 Sep 21 Added APY05D for Yaesu FT5D series
+04 Sep 21 APLOxx LoRa KISS TNC/Tracker https://github.com/SQ9MDD/TTGO-T-Beam-LoRa-APRS
+24 Aug 21 Added APLSxx SARIMESH http://www.sarimesh.net
+22 Aug 21 Added APE2Ax for VA3NNW's Email-2-APRS ap
+30 Jun 21 Added APCNxx for carNET by DG5OAW
+14 Jun 21 Added APN2xx for NOSaprs JNOS 2.0 - VE4KLM
+24 Apr 21 Added APMPAD for DF1JSL's WXBot clone and extension
+20 Apr 21 Added APLCxx for APRScube by DL3DCW
+19 Apr 21 Added APVMxx for DRCC-DVM Voice (Digital Radio China Club)
+13 Apr 21 Added APIxxx for all Dstar ICOMS (APRS via DPRS)
+23 MAr 20 Added APW9xx For 9A9Y Weather Tracker
+16 Feb 21 Added API970 for I com 9700
-13 Oct 20 Added APIZCI hymTR IZCI Tracker by TA7W/OH2UDS and TA6AEU
-13 Aug 20 Added APLGxx for LoRa Gateway/Digipeater
- APLTxx for LoRa Tracker - OE5BPA
-02 Aug 20 Added APNVxx for SQ8L's VP digi and Nodes
-26 May 20 Added APY300 for Yaesu
- 5 May 20 added APESPG ESP SmartBeacon APRS-IS Client
- APESPW ESP Weather Station APRS-IS Client
-17 Apr 20 Added APGDTx for VK4FAST's Graphic Data Terminal
-19 Mar 20 Added APOSW and APOSB for OpenSPOT2 and 3
-20 Jan 20 Added APBT62 for BTech DMR 6x2
-08 Jan 20 Added APCLUB for Brazil APRS network
-06 Jan 20 Added APMQxx for Ham Radio of Things WB2OSZ
-18 Dec 19 Added APTPNx: TARPN Packet Node Tracker by KN4ORB
-02 Dec 19 added APJ8xx For Jordan / KN4CRD JS8Call application
- 8 Sep 19 Added APBSDx for OpenBSD or HamBSD https://hambsd.org/
-19 Aug 19 Added APNKMX for KAM-XL
- and Added APAT51 for Anytone AT-D578UV APRS radio
-16 Jul 19 expanded APMGxx to cover PiCrumbs and MiniGate
-24 Jun 19 Added APTCMA for CAPI tracker - PU1CMA Brazil
- 4 Jun 19 added APATxx for Anytone
- 8 May 19 added APQTHx for W8WJB's QTH.app
-12 Mar 19 added APLIGx for LightAPRS
- 3 Dec 18 added APRARX forVK5QI's radiosonde tracking
- 8 Nov 18 added APELKx for WB8ELK balloons
-24 Oct 18 added APGBLN for NW5W's GoBalloon
-18 Apr 18 added APBKxx for PY5BK Bravo Tracker in Brazil
- 7 Mar 18 added APERSx Runner tracking by Jason,KG7YKZ
- 8 Jan 18 added APTCHE PU3IKE in Brazil TcheTracker/Tcheduino
+2020 Added APHBLx,APIZCI,APLGxx,APLTxx,APNVxx,APY300,APESPG,APESPW
+ APGDTx,APOSWx,APOSBx,APBT62,APCLUB,APMQxx
+2019 Added APTPNx,APJ8xx,APBSDx,APNKMX,APAT51,APMGxx,APTCMA,
+ APATxx,APQTHx,APLIGx
+2018 added APRARX,APELKx,APGBLN,APBKxx,APERSx,APTCHE
2017 Added APHWxx,APDVxx,APPICO,APBMxx,APP6xx,APTAxx,APOCSG,APCSMS,
APPMxx,APOFF,APDTMF,APRSON,APDIGI,APSAT,APTBxx,APIExx,
APSFxx
2016 added APYSxx,APINxx,APNICx,APTKPT,APK004,APFPRS,APCDS0,APDNOx
2015 Added APSTPO,APAND1,APDRxx,APZ247,APHTxx,APMTxx,APZMAJ
- APB2MF,APR2MF,APAVT5
+ APB2MF,APR2MF,APAVT5
@@ -52,6 +44,10 @@ bytes of the field are available to indicate the software version
number or application. The following applications have requested
a TOCALL number series:
+Authors with similar alphabetic requirements are encouraged to share
+their address space with other software. Work out agreements amongst
+yourselves and keep me informed.
+
@@ -69,7 +65,8 @@ a TOCALL number series:
APAHxx AHub
APAND1 APRSdroid (pre-release) http://aprsdroid.org/
APAMxx Altus Metrum GPS trackers
- APATxx for Anytone. 81 for 878 HT
+ APATAR ATA-R APRS Digipeater by TA7W/OH2UDS and TA6AEU
+ APAT8x for Anytone. 81 for 878 HT
APAT51 for Anytone AT-D578UV APRS mobile radio
APAVT5 SainSonic AP510 which is a 1watt tracker
APAWxx AGWPE
@@ -89,7 +86,9 @@ a TOCALL number series:
APCLEZ Telit EZ10 GSM application ZS6CEY
APCLUB Brazil APRS network
APCLWX EYWeather GPRS/GSM WX station by ZS6EY
+ APCNxx for carNET by DG5OAW
APCSMS for Cosmos (used for sending commands @USNA)
+ APCSS for AMSAT cubesats https://cubesatsim.org
APCWP8 John GM7HHB, WinphoneAPRS
APCYxx Cybiko applications
APD APD4xx UP4DAR platform
@@ -112,6 +111,7 @@ a TOCALL number series:
APDVxx OE6PLD's SSTV with APRS status exchange
APDWxx DireWolf, WB2OSZ
APE APExxx Telemetry devices
+ APE2Ax VA3NNW's Email-2-APRS ap
APECAN Pecan Pico APRS Balloon Tracker
APELKx WB8ELK balloons
APERXQ Experimental tracker by PE1RXQ
@@ -128,9 +128,21 @@ a TOCALL number series:
APGDTx for VK4FAST's Graphic Data Terminal
APH APHKxx for LA1BR tracker/digipeater
APHAXn SM2APRS by PY2UEP
+ APHBLx for DMR Gateway by Eric - KF7EEL
APHTxx HMTracker by IU0AAC
APHWxx for use in "HamWAN
- API APICQx for ICQ
+ API API282 for ICOM IC-2820
+ API31 for ICOM ID-31
+ API410 for ICOM ID-4100
+ API51 for ICOM ID-51
+ API510 for ICOM ID-5100
+ API710 for ICOM IC-7100
+ API80 for ICOM IC-80
+ API880 for ICOM ID-880
+ API910 for ICOM IC-9100
+ API92 for ICOM IC-92
+ API970 for ICOM 9700
+ APICQx for ICQ
APICxx HA9MCQ's Pic IGate
APIExx W7KMV's PiAPRS system
APINxx PinPoint by AB0WV
@@ -147,17 +159,22 @@ a TOCALL number series:
APK1xx Kenwood D700's
APK102 Kenwood D710
APKRAM KRAMstuff.com - Mark. G7LEU
- APL APLGxx LoRa Gateway/Digipeater OE5BPA
+ APL APLCxx APRScube by DL3DCW
+ APLGxx LoRa Gateway/Digipeater OE5BPA
APLIGx LightAPRS - TA2MUN and TA9OHC
+ APLOxx LoRa KISS TNC/Tracker
APLQRU Charlie - QRU Server
APLMxx WA0TQG transceiver controller
+ APLSxx SARIMESH ( http://www.sarimesh.net )
APLTxx LoRa Tracker - OE5BPA
APM APMxxx MacAPRS,
APMGxx PiCrumbs and MiniGate - Alex, AB0TJ
APMIxx SQ3PLX http://microsat.com.pl/
+ APMPAD DF1JSL's WXBot clone and extension
APMQxx Ham Radio of Things WB2OSZ
APMTxx LZ1PPL for tracker
APN APNxxx Network nodes, digis, etc
+ APN2xx NOSaprs for JNOS 2.0 - VE4KLM
APN3xx Kantronics KPC-3 rom versions
APN9xx Kantronics KPC-9612 Roms
APNAxx WB6ZSU's APRServe
@@ -234,10 +251,12 @@ a TOCALL number series:
APU3xx UIview terminal program
APUDRx NW Digital Radio's UDR (APRS/Dstar)
APV APVxxx Voice over Internet applications
+ APVMxx DRCC-DVM Digital Voice (Digital Radio China Club)
APVRxx for IRLP
APVLxx for I-LINK
APVExx for ECHO link
APW APWxxx WinAPRS, etc
+ APW9xx 9A9Y Weather Tracker
APWAxx APRSISCE Android version
APWSxx DF4IAN's WS2300 WX station
APWMxx APRSISCE KJ4ERJ
@@ -249,11 +268,13 @@ a TOCALL number series:
APY01D Yaesu FT1D series
APY02D Yaesu FT2D series
APY03D Yaesu FT3D series
+ APY05D Yaesu FT5D series
APY100 Yaesu FTM-100D series
APY300 Yaesu FTM-300D series
APY350 Yaesu FTM-350 series
APY400 Yaesu FTM-400D series
APZ APZxxx Experimental
+ APZ200 old versions of JNOS
APZ247 for UPRS NR0Q
APZ0xx Xastir (old versions. See APX)
APZMAJ Martyn M1MAJ DeLorme inReach Tracker
@@ -265,15 +286,11 @@ a TOCALL number series:
-Authors with similar alphabetic requirements are encouraged to share
-their address space with other software. Work out agreements amongst
-yourselves and keep me informed.
-
-REGISTERED ALTNETS:
--------------------
+REGISTERED TOCALL ALTNETS:
+--------------------------
ALTNETS are uses of the AX-25 tocall to distinguish specialized
traffic that may be flowing on the APRS-IS, but that are not intended
@@ -301,6 +318,6 @@ and need some special recognition. Here are some ideas:
SATERN - Salvation Army Altnet
- AFMARS - Airforce Mars
+ AFMARS - Airforce Mars
AMARS - Army Mars
\ No newline at end of file
diff --git a/external/geotranz/mgrs.h b/external/geotranz/mgrs.h
index 79a1c28e..bd0453a1 100644
--- a/external/geotranz/mgrs.h
+++ b/external/geotranz/mgrs.h
@@ -236,7 +236,7 @@ extern "C" {
* The function Convert_MGRS_To_UPS converts an MGRS coordinate string
* to UPS (hemisphere, easting, and northing) coordinates, according
* to the current ellipsoid parameters. If any errors occur, the error
- * code(s) are returned by the function, otherwide UPS_NO_ERROR is returned.
+ * code(s) are returned by the function, otherwise UPS_NO_ERROR is returned.
*
* MGRS : MGRS coordinate string (input)
* Hemisphere : Hemisphere either 'N' or 'S' (output)
diff --git a/external/hidapi/CMakeLists.txt b/external/hidapi/CMakeLists.txt
new file mode 100644
index 00000000..c9dd66f1
--- /dev/null
+++ b/external/hidapi/CMakeLists.txt
@@ -0,0 +1,21 @@
+set(HIDAPI_LIBRARIES "" CACHE INTERNAL "")
+
+if(WIN32 OR CYGWIN) # windows
+
+ set(HIDAPI_LIBRARIES hidapi CACHE INTERNAL "hidapi")
+
+ list(APPEND hidapi_SOURCES
+ # Functions for accessing HID devices on Windows.
+ # These were copied from https://github.com/libusb/hidapi
+ ${CUSTOM_HIDAPI_DIR}/hid.c
+ )
+
+ add_library(hidapi STATIC
+ ${hidapi_SOURCES}
+ )
+
+ set_target_properties(hidapi
+ PROPERTIES COMPILE_FLAGS "-Dbool=int -Dtrue=1 -Dfalse=0 -DUSE_HIDAPI_STATIC"
+ )
+
+endif()
diff --git a/external/hidapi/LICENSE-bsd.txt b/external/hidapi/LICENSE-bsd.txt
new file mode 100644
index 00000000..538cdf95
--- /dev/null
+++ b/external/hidapi/LICENSE-bsd.txt
@@ -0,0 +1,26 @@
+Copyright (c) 2010, Alan Ott, Signal 11 Software
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Signal 11 Software nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/external/hidapi/LICENSE-gpl3.txt b/external/hidapi/LICENSE-gpl3.txt
new file mode 100644
index 00000000..94a9ed02
--- /dev/null
+++ b/external/hidapi/LICENSE-gpl3.txt
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/external/hidapi/LICENSE-orig.txt b/external/hidapi/LICENSE-orig.txt
new file mode 100644
index 00000000..e3f33808
--- /dev/null
+++ b/external/hidapi/LICENSE-orig.txt
@@ -0,0 +1,9 @@
+ HIDAPI - Multi-Platform library for
+ communication with HID devices.
+
+ Copyright 2009, Alan Ott, Signal 11 Software.
+ All Rights Reserved.
+
+ This software may be used by anyone for any reason so
+ long as the copyright notice in the source files
+ remains intact.
diff --git a/external/hidapi/LICENSE.txt b/external/hidapi/LICENSE.txt
new file mode 100644
index 00000000..e1676d4c
--- /dev/null
+++ b/external/hidapi/LICENSE.txt
@@ -0,0 +1,13 @@
+HIDAPI can be used under one of three licenses.
+
+1. The GNU General Public License, version 3.0, in LICENSE-gpl3.txt
+2. A BSD-Style License, in LICENSE-bsd.txt.
+3. The more liberal original HIDAPI license. LICENSE-orig.txt
+
+The license chosen is at the discretion of the user of HIDAPI. For example:
+1. An author of GPL software would likely use HIDAPI under the terms of the
+GPL.
+
+2. An author of commercial closed-source software would likely use HIDAPI
+under the terms of the BSD-style license or the original HIDAPI license.
+
diff --git a/external/hidapi/README.md b/external/hidapi/README.md
new file mode 100644
index 00000000..6ced0f91
--- /dev/null
+++ b/external/hidapi/README.md
@@ -0,0 +1 @@
+This is from https://github.com/libusb/hidapi
diff --git a/external/hidapi/hid.c b/external/hidapi/hid.c
new file mode 100644
index 00000000..e483cd4f
--- /dev/null
+++ b/external/hidapi/hid.c
@@ -0,0 +1,1091 @@
+/*******************************************************
+ HIDAPI - Multi-Platform library for
+ communication with HID devices.
+
+ Alan Ott
+ Signal 11 Software
+
+ 8/22/2009
+
+ Copyright 2009, All Rights Reserved.
+
+ At the discretion of the user of this library,
+ this software may be licensed under the terms of the
+ GNU General Public License v3, a BSD-Style license, or the
+ original HIDAPI license as outlined in the LICENSE.txt,
+ LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
+ files located at the root of the source distribution.
+ These files may also be found in the public source
+ code repository located at:
+ https://github.com/libusb/hidapi .
+********************************************************/
+
+#include
+
+#ifndef _NTDEF_
+typedef LONG NTSTATUS;
+#endif
+
+#ifdef __MINGW32__
+#include
+#include
+#endif
+
+#ifdef __CYGWIN__
+#include
+#define _wcsdup wcsdup
+#endif
+
+/* The maximum number of characters that can be passed into the
+ HidD_Get*String() functions without it failing.*/
+#define MAX_STRING_WCHARS 0xFFF
+
+/*#define HIDAPI_USE_DDK*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ #include
+ #include
+ #ifdef HIDAPI_USE_DDK
+ #include
+ #endif
+
+ /* Copied from inc/ddk/hidclass.h, part of the Windows DDK. */
+ #define HID_OUT_CTL_CODE(id) \
+ CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
+ #define IOCTL_HID_GET_FEATURE HID_OUT_CTL_CODE(100)
+ #define IOCTL_HID_GET_INPUT_REPORT HID_OUT_CTL_CODE(104)
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#include
+#include
+
+
+#include "hidapi.h"
+
+#undef MIN
+#define MIN(x,y) ((x) < (y)? (x): (y))
+
+#ifdef _MSC_VER
+ /* Thanks Microsoft, but I know how to use strncpy(). */
+ #pragma warning(disable:4996)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static struct hid_api_version api_version = {
+ .major = HID_API_VERSION_MAJOR,
+ .minor = HID_API_VERSION_MINOR,
+ .patch = HID_API_VERSION_PATCH
+};
+
+#ifndef HIDAPI_USE_DDK
+ /* Since we're not building with the DDK, and the HID header
+ files aren't part of the SDK, we have to define all this
+ stuff here. In lookup_functions(), the function pointers
+ defined below are set. */
+ typedef struct _HIDD_ATTRIBUTES{
+ ULONG Size;
+ USHORT VendorID;
+ USHORT ProductID;
+ USHORT VersionNumber;
+ } HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES;
+
+ typedef USHORT USAGE;
+ typedef struct _HIDP_CAPS {
+ USAGE Usage;
+ USAGE UsagePage;
+ USHORT InputReportByteLength;
+ USHORT OutputReportByteLength;
+ USHORT FeatureReportByteLength;
+ USHORT Reserved[17];
+ USHORT fields_not_used_by_hidapi[10];
+ } HIDP_CAPS, *PHIDP_CAPS;
+ typedef void* PHIDP_PREPARSED_DATA;
+ #define HIDP_STATUS_SUCCESS 0x110000
+
+ typedef BOOLEAN (__stdcall *HidD_GetAttributes_)(HANDLE device, PHIDD_ATTRIBUTES attrib);
+ typedef BOOLEAN (__stdcall *HidD_GetSerialNumberString_)(HANDLE device, PVOID buffer, ULONG buffer_len);
+ typedef BOOLEAN (__stdcall *HidD_GetManufacturerString_)(HANDLE handle, PVOID buffer, ULONG buffer_len);
+ typedef BOOLEAN (__stdcall *HidD_GetProductString_)(HANDLE handle, PVOID buffer, ULONG buffer_len);
+ typedef BOOLEAN (__stdcall *HidD_SetFeature_)(HANDLE handle, PVOID data, ULONG length);
+ typedef BOOLEAN (__stdcall *HidD_GetFeature_)(HANDLE handle, PVOID data, ULONG length);
+ typedef BOOLEAN (__stdcall *HidD_GetInputReport_)(HANDLE handle, PVOID data, ULONG length);
+ typedef BOOLEAN (__stdcall *HidD_GetIndexedString_)(HANDLE handle, ULONG string_index, PVOID buffer, ULONG buffer_len);
+ typedef BOOLEAN (__stdcall *HidD_GetPreparsedData_)(HANDLE handle, PHIDP_PREPARSED_DATA *preparsed_data);
+ typedef BOOLEAN (__stdcall *HidD_FreePreparsedData_)(PHIDP_PREPARSED_DATA preparsed_data);
+ typedef NTSTATUS (__stdcall *HidP_GetCaps_)(PHIDP_PREPARSED_DATA preparsed_data, HIDP_CAPS *caps);
+ typedef BOOLEAN (__stdcall *HidD_SetNumInputBuffers_)(HANDLE handle, ULONG number_buffers);
+
+ static HidD_GetAttributes_ HidD_GetAttributes;
+ static HidD_GetSerialNumberString_ HidD_GetSerialNumberString;
+ static HidD_GetManufacturerString_ HidD_GetManufacturerString;
+ static HidD_GetProductString_ HidD_GetProductString;
+ static HidD_SetFeature_ HidD_SetFeature;
+ static HidD_GetFeature_ HidD_GetFeature;
+ static HidD_GetInputReport_ HidD_GetInputReport;
+ static HidD_GetIndexedString_ HidD_GetIndexedString;
+ static HidD_GetPreparsedData_ HidD_GetPreparsedData;
+ static HidD_FreePreparsedData_ HidD_FreePreparsedData;
+ static HidP_GetCaps_ HidP_GetCaps;
+ static HidD_SetNumInputBuffers_ HidD_SetNumInputBuffers;
+
+ static HMODULE lib_handle = NULL;
+ static BOOLEAN initialized = FALSE;
+#endif /* HIDAPI_USE_DDK */
+
+struct hid_device_ {
+ HANDLE device_handle;
+ BOOL blocking;
+ USHORT output_report_length;
+ size_t input_report_length;
+ USHORT feature_report_length;
+ unsigned char *feature_buf;
+ void *last_error_str;
+ DWORD last_error_num;
+ BOOL read_pending;
+ char *read_buf;
+ OVERLAPPED ol;
+ OVERLAPPED write_ol;
+};
+
+static hid_device *new_hid_device()
+{
+ hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device));
+ dev->device_handle = INVALID_HANDLE_VALUE;
+ dev->blocking = TRUE;
+ dev->output_report_length = 0;
+ dev->input_report_length = 0;
+ dev->feature_report_length = 0;
+ dev->feature_buf = NULL;
+ dev->last_error_str = NULL;
+ dev->last_error_num = 0;
+ dev->read_pending = FALSE;
+ dev->read_buf = NULL;
+ memset(&dev->ol, 0, sizeof(dev->ol));
+ dev->ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*initial state f=nonsignaled*/, NULL);
+ memset(&dev->write_ol, 0, sizeof(dev->write_ol));
+ dev->write_ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*inital state f=nonsignaled*/, NULL);
+
+ return dev;
+}
+
+static void free_hid_device(hid_device *dev)
+{
+ CloseHandle(dev->ol.hEvent);
+ CloseHandle(dev->write_ol.hEvent);
+ CloseHandle(dev->device_handle);
+ LocalFree(dev->last_error_str);
+ free(dev->feature_buf);
+ free(dev->read_buf);
+ free(dev);
+}
+
+static void register_error(hid_device *dev, const char *op)
+{
+ WCHAR *ptr, *msg;
+ (void)op; // unreferenced param
+ FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL,
+ GetLastError(),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPWSTR)&msg, 0/*sz*/,
+ NULL);
+
+ /* Get rid of the CR and LF that FormatMessage() sticks at the
+ end of the message. Thanks Microsoft! */
+ ptr = msg;
+ while (*ptr) {
+ if (*ptr == '\r') {
+ *ptr = 0x0000;
+ break;
+ }
+ ptr++;
+ }
+
+ /* Store the message off in the Device entry so that
+ the hid_error() function can pick it up. */
+ LocalFree(dev->last_error_str);
+ dev->last_error_str = msg;
+}
+
+#ifndef HIDAPI_USE_DDK
+static int lookup_functions()
+{
+ lib_handle = LoadLibraryA("hid.dll");
+ if (lib_handle) {
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+//# pragma GCC diagnostic ignored "-Wcast-function-type"
+#endif
+#define RESOLVE(x) x = (x##_)GetProcAddress(lib_handle, #x); if (!x) return -1;
+ RESOLVE(HidD_GetAttributes);
+ RESOLVE(HidD_GetSerialNumberString);
+ RESOLVE(HidD_GetManufacturerString);
+ RESOLVE(HidD_GetProductString);
+ RESOLVE(HidD_SetFeature);
+ RESOLVE(HidD_GetFeature);
+ RESOLVE(HidD_GetInputReport);
+ RESOLVE(HidD_GetIndexedString);
+ RESOLVE(HidD_GetPreparsedData);
+ RESOLVE(HidD_FreePreparsedData);
+ RESOLVE(HidP_GetCaps);
+ RESOLVE(HidD_SetNumInputBuffers);
+#undef RESOLVE
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+ }
+ else
+ return -1;
+
+ return 0;
+}
+#endif
+
+static HANDLE open_device(const char *path, BOOL open_rw)
+{
+ HANDLE handle;
+ DWORD desired_access = (open_rw)? (GENERIC_WRITE | GENERIC_READ): 0;
+ DWORD share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE;
+
+ handle = CreateFileA(path,
+ desired_access,
+ share_mode,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_OVERLAPPED,/*FILE_ATTRIBUTE_NORMAL,*/
+ 0);
+
+ return handle;
+}
+
+HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version()
+{
+ return &api_version;
+}
+
+HID_API_EXPORT const char* HID_API_CALL hid_version_str()
+{
+ return HID_API_VERSION_STR;
+}
+
+int HID_API_EXPORT hid_init(void)
+{
+#ifndef HIDAPI_USE_DDK
+ if (!initialized) {
+ if (lookup_functions() < 0) {
+ hid_exit();
+ return -1;
+ }
+ initialized = TRUE;
+ }
+#endif
+ return 0;
+}
+
+int HID_API_EXPORT hid_exit(void)
+{
+#ifndef HIDAPI_USE_DDK
+ if (lib_handle)
+ FreeLibrary(lib_handle);
+ lib_handle = NULL;
+ initialized = FALSE;
+#endif
+ return 0;
+}
+
+struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id)
+{
+ BOOL res;
+ struct hid_device_info *root = NULL; /* return object */
+ struct hid_device_info *cur_dev = NULL;
+
+ /* Windows objects for interacting with the driver. */
+ GUID InterfaceClassGuid = {0x4d1e55b2, 0xf16f, 0x11cf, {0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30} };
+ SP_DEVINFO_DATA devinfo_data;
+ SP_DEVICE_INTERFACE_DATA device_interface_data;
+ SP_DEVICE_INTERFACE_DETAIL_DATA_A *device_interface_detail_data = NULL;
+ HDEVINFO device_info_set = INVALID_HANDLE_VALUE;
+ int device_index = 0;
+ int i;
+
+ if (hid_init() < 0)
+ return NULL;
+
+ /* Initialize the Windows objects. */
+ memset(&devinfo_data, 0x0, sizeof(devinfo_data));
+ devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA);
+ device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
+
+ /* Get information for all the devices belonging to the HID class. */
+ device_info_set = SetupDiGetClassDevsA(&InterfaceClassGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
+
+ /* Iterate over each device in the HID class, looking for the right one. */
+
+ for (;;) {
+ HANDLE write_handle = INVALID_HANDLE_VALUE;
+ DWORD required_size = 0;
+ HIDD_ATTRIBUTES attrib;
+
+ res = SetupDiEnumDeviceInterfaces(device_info_set,
+ NULL,
+ &InterfaceClassGuid,
+ device_index,
+ &device_interface_data);
+
+ if (!res) {
+ /* A return of FALSE from this function means that
+ there are no more devices. */
+ break;
+ }
+
+ /* Call with 0-sized detail size, and let the function
+ tell us how long the detail struct needs to be. The
+ size is put in &required_size. */
+ res = SetupDiGetDeviceInterfaceDetailA(device_info_set,
+ &device_interface_data,
+ NULL,
+ 0,
+ &required_size,
+ NULL);
+
+ /* Allocate a long enough structure for device_interface_detail_data. */
+ device_interface_detail_data = (SP_DEVICE_INTERFACE_DETAIL_DATA_A*) malloc(required_size);
+ device_interface_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A);
+
+ /* Get the detailed data for this device. The detail data gives us
+ the device path for this device, which is then passed into
+ CreateFile() to get a handle to the device. */
+ res = SetupDiGetDeviceInterfaceDetailA(device_info_set,
+ &device_interface_data,
+ device_interface_detail_data,
+ required_size,
+ NULL,
+ NULL);
+
+ if (!res) {
+ /* register_error(dev, "Unable to call SetupDiGetDeviceInterfaceDetail");
+ Continue to the next device. */
+ goto cont;
+ }
+
+ /* Make sure this device is of Setup Class "HIDClass" and has a
+ driver bound to it. */
+ for (i = 0; ; i++) {
+ char driver_name[256];
+
+ /* Populate devinfo_data. This function will return failure
+ when there are no more interfaces left. */
+ res = SetupDiEnumDeviceInfo(device_info_set, i, &devinfo_data);
+ if (!res)
+ goto cont;
+
+ res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data,
+ SPDRP_CLASS, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL);
+ if (!res)
+ goto cont;
+
+ if ((strcmp(driver_name, "HIDClass") == 0) ||
+ (strcmp(driver_name, "Mouse") == 0) ||
+ (strcmp(driver_name, "Keyboard") == 0)) {
+ /* See if there's a driver bound. */
+ res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data,
+ SPDRP_DRIVER, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL);
+ if (res)
+ break;
+ }
+ }
+
+ //wprintf(L"HandleName: %s\n", device_interface_detail_data->DevicePath);
+
+ /* Open a handle to the device */
+ write_handle = open_device(device_interface_detail_data->DevicePath, FALSE);
+
+ /* Check validity of write_handle. */
+ if (write_handle == INVALID_HANDLE_VALUE) {
+ /* Unable to open the device. */
+ //register_error(dev, "CreateFile");
+ goto cont_close;
+ }
+
+
+ /* Get the Vendor ID and Product ID for this device. */
+ attrib.Size = sizeof(HIDD_ATTRIBUTES);
+ HidD_GetAttributes(write_handle, &attrib);
+ //wprintf(L"Product/Vendor: %x %x\n", attrib.ProductID, attrib.VendorID);
+
+ /* Check the VID/PID to see if we should add this
+ device to the enumeration list. */
+ if ((vendor_id == 0x0 || attrib.VendorID == vendor_id) &&
+ (product_id == 0x0 || attrib.ProductID == product_id)) {
+
+ #define WSTR_LEN 512
+ const char *str;
+ struct hid_device_info *tmp;
+ PHIDP_PREPARSED_DATA pp_data = NULL;
+ HIDP_CAPS caps;
+ NTSTATUS nt_res;
+ wchar_t wstr[WSTR_LEN]; /* TODO: Determine Size */
+ size_t len;
+
+ /* VID/PID match. Create the record. */
+ tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info));
+ if (cur_dev) {
+ cur_dev->next = tmp;
+ }
+ else {
+ root = tmp;
+ }
+ cur_dev = tmp;
+
+ /* Get the Usage Page and Usage for this device. */
+ res = HidD_GetPreparsedData(write_handle, &pp_data);
+ if (res) {
+ nt_res = HidP_GetCaps(pp_data, &caps);
+ if (nt_res == HIDP_STATUS_SUCCESS) {
+ cur_dev->usage_page = caps.UsagePage;
+ cur_dev->usage = caps.Usage;
+ }
+
+ HidD_FreePreparsedData(pp_data);
+ }
+
+ /* Fill out the record */
+ cur_dev->next = NULL;
+ str = device_interface_detail_data->DevicePath;
+ if (str) {
+ len = strlen(str);
+ cur_dev->path = (char*) calloc(len+1, sizeof(char));
+ strncpy(cur_dev->path, str, len+1);
+ cur_dev->path[len] = '\0';
+ }
+ else
+ cur_dev->path = NULL;
+
+ /* Serial Number */
+ wstr[0]= 0x0000;
+ res = HidD_GetSerialNumberString(write_handle, wstr, sizeof(wstr));
+ wstr[WSTR_LEN-1] = 0x0000;
+ if (res) {
+ cur_dev->serial_number = _wcsdup(wstr);
+ }
+
+ /* Manufacturer String */
+ wstr[0]= 0x0000;
+ res = HidD_GetManufacturerString(write_handle, wstr, sizeof(wstr));
+ wstr[WSTR_LEN-1] = 0x0000;
+ if (res) {
+ cur_dev->manufacturer_string = _wcsdup(wstr);
+ }
+
+ /* Product String */
+ wstr[0]= 0x0000;
+ res = HidD_GetProductString(write_handle, wstr, sizeof(wstr));
+ wstr[WSTR_LEN-1] = 0x0000;
+ if (res) {
+ cur_dev->product_string = _wcsdup(wstr);
+ }
+
+ /* VID/PID */
+ cur_dev->vendor_id = attrib.VendorID;
+ cur_dev->product_id = attrib.ProductID;
+
+ /* Release Number */
+ cur_dev->release_number = attrib.VersionNumber;
+
+ /* Interface Number. It can sometimes be parsed out of the path
+ on Windows if a device has multiple interfaces. See
+ http://msdn.microsoft.com/en-us/windows/hardware/gg487473 or
+ search for "Hardware IDs for HID Devices" at MSDN. If it's not
+ in the path, it's set to -1. */
+ cur_dev->interface_number = -1;
+ if (cur_dev->path) {
+ char *interface_component = strstr(cur_dev->path, "&mi_");
+ if (interface_component) {
+ char *hex_str = interface_component + 4;
+ char *endptr = NULL;
+ cur_dev->interface_number = strtol(hex_str, &endptr, 16);
+ if (endptr == hex_str) {
+ /* The parsing failed. Set interface_number to -1. */
+ cur_dev->interface_number = -1;
+ }
+ }
+ }
+ }
+
+cont_close:
+ CloseHandle(write_handle);
+cont:
+ /* We no longer need the detail data. It can be freed */
+ free(device_interface_detail_data);
+
+ device_index++;
+
+ }
+
+ /* Close the device information handle. */
+ SetupDiDestroyDeviceInfoList(device_info_set);
+
+ return root;
+
+}
+
+void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs)
+{
+ /* TODO: Merge this with the Linux version. This function is platform-independent. */
+ struct hid_device_info *d = devs;
+ while (d) {
+ struct hid_device_info *next = d->next;
+ free(d->path);
+ free(d->serial_number);
+ free(d->manufacturer_string);
+ free(d->product_string);
+ free(d);
+ d = next;
+ }
+}
+
+
+HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)
+{
+ /* TODO: Merge this functions with the Linux version. This function should be platform independent. */
+ struct hid_device_info *devs, *cur_dev;
+ const char *path_to_open = NULL;
+ hid_device *handle = NULL;
+
+ devs = hid_enumerate(vendor_id, product_id);
+ cur_dev = devs;
+ while (cur_dev) {
+ if (cur_dev->vendor_id == vendor_id &&
+ cur_dev->product_id == product_id) {
+ if (serial_number) {
+ if (cur_dev->serial_number && wcscmp(serial_number, cur_dev->serial_number) == 0) {
+ path_to_open = cur_dev->path;
+ break;
+ }
+ }
+ else {
+ path_to_open = cur_dev->path;
+ break;
+ }
+ }
+ cur_dev = cur_dev->next;
+ }
+
+ if (path_to_open) {
+ /* Open the device */
+ handle = hid_open_path(path_to_open);
+ }
+
+ hid_free_enumeration(devs);
+
+ return handle;
+}
+
+HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path)
+{
+ hid_device *dev;
+ HIDP_CAPS caps;
+ PHIDP_PREPARSED_DATA pp_data = NULL;
+ BOOLEAN res;
+ NTSTATUS nt_res;
+
+ if (hid_init() < 0) {
+ return NULL;
+ }
+
+ dev = new_hid_device();
+
+ /* Open a handle to the device */
+ dev->device_handle = open_device(path, TRUE);
+
+ /* Check validity of write_handle. */
+ if (dev->device_handle == INVALID_HANDLE_VALUE) {
+ /* System devices, such as keyboards and mice, cannot be opened in
+ read-write mode, because the system takes exclusive control over
+ them. This is to prevent keyloggers. However, feature reports
+ can still be sent and received. Retry opening the device, but
+ without read/write access. */
+ dev->device_handle = open_device(path, FALSE);
+
+ /* Check the validity of the limited device_handle. */
+ if (dev->device_handle == INVALID_HANDLE_VALUE) {
+ /* Unable to open the device, even without read-write mode. */
+ register_error(dev, "CreateFile");
+ goto err;
+ }
+ }
+
+ /* Set the Input Report buffer size to 64 reports. */
+ res = HidD_SetNumInputBuffers(dev->device_handle, 64);
+ if (!res) {
+ register_error(dev, "HidD_SetNumInputBuffers");
+ goto err;
+ }
+
+ /* Get the Input Report length for the device. */
+ res = HidD_GetPreparsedData(dev->device_handle, &pp_data);
+ if (!res) {
+ register_error(dev, "HidD_GetPreparsedData");
+ goto err;
+ }
+ nt_res = HidP_GetCaps(pp_data, &caps);
+ if (nt_res != HIDP_STATUS_SUCCESS) {
+ register_error(dev, "HidP_GetCaps");
+ goto err_pp_data;
+ }
+ dev->output_report_length = caps.OutputReportByteLength;
+ dev->input_report_length = caps.InputReportByteLength;
+ dev->feature_report_length = caps.FeatureReportByteLength;
+ HidD_FreePreparsedData(pp_data);
+
+ dev->read_buf = (char*) malloc(dev->input_report_length);
+
+ return dev;
+
+err_pp_data:
+ HidD_FreePreparsedData(pp_data);
+err:
+ free_hid_device(dev);
+ return NULL;
+}
+
+int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length)
+{
+ DWORD bytes_written = 0;
+ int function_result = -1;
+ BOOL res;
+ BOOL overlapped = FALSE;
+
+ unsigned char *buf;
+
+ /* Make sure the right number of bytes are passed to WriteFile. Windows
+ expects the number of bytes which are in the _longest_ report (plus
+ one for the report number) bytes even if the data is a report
+ which is shorter than that. Windows gives us this value in
+ caps.OutputReportByteLength. If a user passes in fewer bytes than this,
+ create a temporary buffer which is the proper size. */
+ if (length >= dev->output_report_length) {
+ /* The user passed the right number of bytes. Use the buffer as-is. */
+ buf = (unsigned char *) data;
+ } else {
+ /* Create a temporary buffer and copy the user's data
+ into it, padding the rest with zeros. */
+ buf = (unsigned char *) malloc(dev->output_report_length);
+ memcpy(buf, data, length);
+ memset(buf + length, 0, dev->output_report_length - length);
+ length = dev->output_report_length;
+ }
+
+ res = WriteFile(dev->device_handle, buf, (DWORD) length, NULL, &dev->write_ol);
+
+ if (!res) {
+ if (GetLastError() != ERROR_IO_PENDING) {
+ /* WriteFile() failed. Return error. */
+ register_error(dev, "WriteFile");
+ goto end_of_function;
+ }
+ overlapped = TRUE;
+ }
+
+ if (overlapped) {
+ /* Wait for the transaction to complete. This makes
+ hid_write() synchronous. */
+ res = WaitForSingleObject(dev->write_ol.hEvent, 1000);
+ if (res != WAIT_OBJECT_0) {
+ /* There was a Timeout. */
+ register_error(dev, "WriteFile/WaitForSingleObject Timeout");
+ goto end_of_function;
+ }
+
+ /* Get the result. */
+ res = GetOverlappedResult(dev->device_handle, &dev->write_ol, &bytes_written, FALSE/*wait*/);
+ if (res) {
+ function_result = bytes_written;
+ }
+ else {
+ /* The Write operation failed. */
+ register_error(dev, "WriteFile");
+ goto end_of_function;
+ }
+ }
+
+end_of_function:
+ if (buf != data)
+ free(buf);
+
+ return function_result;
+}
+
+
+int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
+{
+ DWORD bytes_read = 0;
+ size_t copy_len = 0;
+ BOOL res = FALSE;
+ BOOL overlapped = FALSE;
+
+ /* Copy the handle for convenience. */
+ HANDLE ev = dev->ol.hEvent;
+
+ if (!dev->read_pending) {
+ /* Start an Overlapped I/O read. */
+ dev->read_pending = TRUE;
+ memset(dev->read_buf, 0, dev->input_report_length);
+ ResetEvent(ev);
+ res = ReadFile(dev->device_handle, dev->read_buf, (DWORD) dev->input_report_length, &bytes_read, &dev->ol);
+
+ if (!res) {
+ if (GetLastError() != ERROR_IO_PENDING) {
+ /* ReadFile() has failed.
+ Clean up and return error. */
+ CancelIo(dev->device_handle);
+ dev->read_pending = FALSE;
+ goto end_of_function;
+ }
+ overlapped = TRUE;
+ }
+ }
+ else {
+ overlapped = TRUE;
+ }
+
+ if (overlapped) {
+ if (milliseconds >= 0) {
+ /* See if there is any data yet. */
+ res = WaitForSingleObject(ev, milliseconds);
+ if (res != WAIT_OBJECT_0) {
+ /* There was no data this time. Return zero bytes available,
+ but leave the Overlapped I/O running. */
+ return 0;
+ }
+ }
+
+ /* Either WaitForSingleObject() told us that ReadFile has completed, or
+ we are in non-blocking mode. Get the number of bytes read. The actual
+ data has been copied to the data[] array which was passed to ReadFile(). */
+ res = GetOverlappedResult(dev->device_handle, &dev->ol, &bytes_read, TRUE/*wait*/);
+ }
+ /* Set pending back to false, even if GetOverlappedResult() returned error. */
+ dev->read_pending = FALSE;
+
+ if (res && bytes_read > 0) {
+ if (dev->read_buf[0] == 0x0) {
+ /* If report numbers aren't being used, but Windows sticks a report
+ number (0x0) on the beginning of the report anyway. To make this
+ work like the other platforms, and to make it work more like the
+ HID spec, we'll skip over this byte. */
+ bytes_read--;
+ copy_len = length > bytes_read ? bytes_read : length;
+ memcpy(data, dev->read_buf+1, copy_len);
+ }
+ else {
+ /* Copy the whole buffer, report number and all. */
+ copy_len = length > bytes_read ? bytes_read : length;
+ memcpy(data, dev->read_buf, copy_len);
+ }
+ }
+
+end_of_function:
+ if (!res) {
+ register_error(dev, "GetOverlappedResult");
+ return -1;
+ }
+
+ return (int) copy_len;
+}
+
+int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length)
+{
+ return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0);
+}
+
+int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock)
+{
+ dev->blocking = !nonblock;
+ return 0; /* Success */
+}
+
+int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
+{
+ BOOL res = FALSE;
+ unsigned char *buf;
+ size_t length_to_send;
+
+ /* Windows expects at least caps.FeatureReportByteLength bytes passed
+ to HidD_SetFeature(), even if the report is shorter. Any less sent and
+ the function fails with error ERROR_INVALID_PARAMETER set. Any more
+ and HidD_SetFeature() silently truncates the data sent in the report
+ to caps.FeatureReportByteLength. */
+ if (length >= dev->feature_report_length) {
+ buf = (unsigned char *) data;
+ length_to_send = length;
+ } else {
+ if (dev->feature_buf == NULL)
+ dev->feature_buf = (unsigned char *) malloc(dev->feature_report_length);
+ buf = dev->feature_buf;
+ memcpy(buf, data, length);
+ memset(buf + length, 0, dev->feature_report_length - length);
+ length_to_send = dev->feature_report_length;
+ }
+
+ res = HidD_SetFeature(dev->device_handle, (PVOID)buf, (DWORD) length_to_send);
+
+ if (!res) {
+ register_error(dev, "HidD_SetFeature");
+ return -1;
+ }
+
+ return (int) length;
+}
+
+
+int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
+{
+ BOOL res;
+#if 0
+ res = HidD_GetFeature(dev->device_handle, data, length);
+ if (!res) {
+ register_error(dev, "HidD_GetFeature");
+ return -1;
+ }
+ return 0; /* HidD_GetFeature() doesn't give us an actual length, unfortunately */
+#else
+ DWORD bytes_returned;
+
+ OVERLAPPED ol;
+ memset(&ol, 0, sizeof(ol));
+
+ res = DeviceIoControl(dev->device_handle,
+ IOCTL_HID_GET_FEATURE,
+ data, (DWORD) length,
+ data, (DWORD) length,
+ &bytes_returned, &ol);
+
+ if (!res) {
+ if (GetLastError() != ERROR_IO_PENDING) {
+ /* DeviceIoControl() failed. Return error. */
+ register_error(dev, "Send Feature Report DeviceIoControl");
+ return -1;
+ }
+ }
+
+ /* Wait here until the write is done. This makes
+ hid_get_feature_report() synchronous. */
+ res = GetOverlappedResult(dev->device_handle, &ol, &bytes_returned, TRUE/*wait*/);
+ if (!res) {
+ /* The operation failed. */
+ register_error(dev, "Send Feature Report GetOverLappedResult");
+ return -1;
+ }
+
+ /* bytes_returned does not include the first byte which contains the
+ report ID. The data buffer actually contains one more byte than
+ bytes_returned. */
+ bytes_returned++;
+
+ return bytes_returned;
+#endif
+}
+
+
+int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length)
+{
+ BOOL res;
+#if 0
+ res = HidD_GetInputReport(dev->device_handle, data, length);
+ if (!res) {
+ register_error(dev, "HidD_GetInputReport");
+ return -1;
+ }
+ return length;
+#else
+ DWORD bytes_returned;
+
+ OVERLAPPED ol;
+ memset(&ol, 0, sizeof(ol));
+
+ res = DeviceIoControl(dev->device_handle,
+ IOCTL_HID_GET_INPUT_REPORT,
+ data, (DWORD) length,
+ data, (DWORD) length,
+ &bytes_returned, &ol);
+
+ if (!res) {
+ if (GetLastError() != ERROR_IO_PENDING) {
+ /* DeviceIoControl() failed. Return error. */
+ register_error(dev, "Send Input Report DeviceIoControl");
+ return -1;
+ }
+ }
+
+ /* Wait here until the write is done. This makes
+ hid_get_feature_report() synchronous. */
+ res = GetOverlappedResult(dev->device_handle, &ol, &bytes_returned, TRUE/*wait*/);
+ if (!res) {
+ /* The operation failed. */
+ register_error(dev, "Send Input Report GetOverLappedResult");
+ return -1;
+ }
+
+ /* bytes_returned does not include the first byte which contains the
+ report ID. The data buffer actually contains one more byte than
+ bytes_returned. */
+ bytes_returned++;
+
+ return bytes_returned;
+#endif
+}
+
+void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev)
+{
+ if (!dev)
+ return;
+ CancelIo(dev->device_handle);
+ free_hid_device(dev);
+}
+
+int HID_API_EXPORT_CALL HID_API_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
+{
+ BOOL res;
+
+ res = HidD_GetManufacturerString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS));
+ if (!res) {
+ register_error(dev, "HidD_GetManufacturerString");
+ return -1;
+ }
+
+ return 0;
+}
+
+int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
+{
+ BOOL res;
+
+ res = HidD_GetProductString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS));
+ if (!res) {
+ register_error(dev, "HidD_GetProductString");
+ return -1;
+ }
+
+ return 0;
+}
+
+int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
+{
+ BOOL res;
+
+ res = HidD_GetSerialNumberString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS));
+ if (!res) {
+ register_error(dev, "HidD_GetSerialNumberString");
+ return -1;
+ }
+
+ return 0;
+}
+
+int HID_API_EXPORT_CALL HID_API_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen)
+{
+ BOOL res;
+
+ res = HidD_GetIndexedString(dev->device_handle, string_index, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS));
+ if (!res) {
+ register_error(dev, "HidD_GetIndexedString");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev)
+{
+ if (dev) {
+ if (dev->last_error_str == NULL)
+ return L"Success";
+ return (wchar_t*)dev->last_error_str;
+ }
+
+ // Global error messages are not (yet) implemented on Windows.
+ return L"hid_error for global errors is not implemented yet";
+}
+
+
+/*#define PICPGM*/
+/*#define S11*/
+#define P32
+#ifdef S11
+ unsigned short VendorID = 0xa0a0;
+ unsigned short ProductID = 0x0001;
+#endif
+
+#ifdef P32
+ unsigned short VendorID = 0x04d8;
+ unsigned short ProductID = 0x3f;
+#endif
+
+
+#ifdef PICPGM
+ unsigned short VendorID = 0x04d8;
+ unsigned short ProductID = 0x0033;
+#endif
+
+
+#if 0
+int __cdecl main(int argc, char* argv[])
+{
+ int res;
+ unsigned char buf[65];
+
+ UNREFERENCED_PARAMETER(argc);
+ UNREFERENCED_PARAMETER(argv);
+
+ /* Set up the command buffer. */
+ memset(buf,0x00,sizeof(buf));
+ buf[0] = 0;
+ buf[1] = 0x81;
+
+
+ /* Open the device. */
+ int handle = open(VendorID, ProductID, L"12345");
+ if (handle < 0)
+ printf("unable to open device\n");
+
+
+ /* Toggle LED (cmd 0x80) */
+ buf[1] = 0x80;
+ res = write(handle, buf, 65);
+ if (res < 0)
+ printf("Unable to write()\n");
+
+ /* Request state (cmd 0x81) */
+ buf[1] = 0x81;
+ write(handle, buf, 65);
+ if (res < 0)
+ printf("Unable to write() (2)\n");
+
+ /* Read requested state */
+ read(handle, buf, 65);
+ if (res < 0)
+ printf("Unable to read()\n");
+
+ /* Print out the returned buffer. */
+ for (int i = 0; i < 4; i++)
+ printf("buf[%d]: %d\n", i, buf[i]);
+
+ return 0;
+}
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
diff --git a/external/hidapi/hidapi.h b/external/hidapi/hidapi.h
new file mode 100644
index 00000000..efba3188
--- /dev/null
+++ b/external/hidapi/hidapi.h
@@ -0,0 +1,498 @@
+/*******************************************************
+ HIDAPI - Multi-Platform library for
+ communication with HID devices.
+
+ Alan Ott
+ Signal 11 Software
+
+ 8/22/2009
+
+ Copyright 2009, All Rights Reserved.
+
+ At the discretion of the user of this library,
+ this software may be licensed under the terms of the
+ GNU General Public License v3, a BSD-Style license, or the
+ original HIDAPI license as outlined in the LICENSE.txt,
+ LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
+ files located at the root of the source distribution.
+ These files may also be found in the public source
+ code repository located at:
+ https://github.com/libusb/hidapi .
+********************************************************/
+
+/** @file
+ * @defgroup API hidapi API
+ */
+
+#ifndef HIDAPI_H__
+#define HIDAPI_H__
+
+#include
+
+#ifdef _WIN32
+ #define HID_API_EXPORT __declspec(dllexport)
+ #define HID_API_CALL
+#else
+ #define HID_API_EXPORT /**< API export macro */
+ #define HID_API_CALL /**< API call macro */
+#endif
+
+#define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/
+
+/** @brief Static/compile-time major version of the library.
+
+ @ingroup API
+*/
+#define HID_API_VERSION_MAJOR 0
+/** @brief Static/compile-time minor version of the library.
+
+ @ingroup API
+*/
+#define HID_API_VERSION_MINOR 10
+/** @brief Static/compile-time patch version of the library.
+
+ @ingroup API
+*/
+#define HID_API_VERSION_PATCH 1
+
+/* Helper macros */
+#define HID_API_AS_STR_IMPL(x) #x
+#define HID_API_AS_STR(x) HID_API_AS_STR_IMPL(x)
+#define HID_API_TO_VERSION_STR(v1, v2, v3) HID_API_AS_STR(v1.v2.v3)
+
+/** @brief Static/compile-time string version of the library.
+
+ @ingroup API
+*/
+#define HID_API_VERSION_STR HID_API_TO_VERSION_STR(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ struct hid_api_version {
+ int major;
+ int minor;
+ int patch;
+ };
+
+ struct hid_device_;
+ typedef struct hid_device_ hid_device; /**< opaque hidapi structure */
+
+ /** hidapi info structure */
+ struct hid_device_info {
+ /** Platform-specific device path */
+ char *path;
+ /** Device Vendor ID */
+ unsigned short vendor_id;
+ /** Device Product ID */
+ unsigned short product_id;
+ /** Serial Number */
+ wchar_t *serial_number;
+ /** Device Release Number in binary-coded decimal,
+ also known as Device Version Number */
+ unsigned short release_number;
+ /** Manufacturer String */
+ wchar_t *manufacturer_string;
+ /** Product string */
+ wchar_t *product_string;
+ /** Usage Page for this Device/Interface
+ (Windows/Mac/hidraw only) */
+ unsigned short usage_page;
+ /** Usage for this Device/Interface
+ (Windows/Mac/hidraw only) */
+ unsigned short usage;
+ /** The USB interface which this logical device
+ represents.
+
+ * Valid on both Linux implementations in all cases.
+ * Valid on the Windows implementation only if the device
+ contains more than one interface.
+ * Valid on the Mac implementation if and only if the device
+ is a USB HID device. */
+ int interface_number;
+
+ /** Pointer to the next device */
+ struct hid_device_info *next;
+ };
+
+
+ /** @brief Initialize the HIDAPI library.
+
+ This function initializes the HIDAPI library. Calling it is not
+ strictly necessary, as it will be called automatically by
+ hid_enumerate() and any of the hid_open_*() functions if it is
+ needed. This function should be called at the beginning of
+ execution however, if there is a chance of HIDAPI handles
+ being opened by different threads simultaneously.
+
+ @ingroup API
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_init(void);
+
+ /** @brief Finalize the HIDAPI library.
+
+ This function frees all of the static data associated with
+ HIDAPI. It should be called at the end of execution to avoid
+ memory leaks.
+
+ @ingroup API
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_exit(void);
+
+ /** @brief Enumerate the HID Devices.
+
+ This function returns a linked list of all the HID devices
+ attached to the system which match vendor_id and product_id.
+ If @p vendor_id is set to 0 then any vendor matches.
+ If @p product_id is set to 0 then any product matches.
+ If @p vendor_id and @p product_id are both set to 0, then
+ all HID devices will be returned.
+
+ @ingroup API
+ @param vendor_id The Vendor ID (VID) of the types of device
+ to open.
+ @param product_id The Product ID (PID) of the types of
+ device to open.
+
+ @returns
+ This function returns a pointer to a linked list of type
+ struct #hid_device_info, containing information about the HID devices
+ attached to the system, or NULL in the case of failure. Free
+ this linked list by calling hid_free_enumeration().
+ */
+ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id);
+
+ /** @brief Free an enumeration Linked List
+
+ This function frees a linked list created by hid_enumerate().
+
+ @ingroup API
+ @param devs Pointer to a list of struct_device returned from
+ hid_enumerate().
+ */
+ void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs);
+
+ /** @brief Open a HID device using a Vendor ID (VID), Product ID
+ (PID) and optionally a serial number.
+
+ If @p serial_number is NULL, the first device with the
+ specified VID and PID is opened.
+
+ This function sets the return value of hid_error().
+
+ @ingroup API
+ @param vendor_id The Vendor ID (VID) of the device to open.
+ @param product_id The Product ID (PID) of the device to open.
+ @param serial_number The Serial Number of the device to open
+ (Optionally NULL).
+
+ @returns
+ This function returns a pointer to a #hid_device object on
+ success or NULL on failure.
+ */
+ HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number);
+
+ /** @brief Open a HID device by its path name.
+
+ The path name be determined by calling hid_enumerate(), or a
+ platform-specific path name can be used (eg: /dev/hidraw0 on
+ Linux).
+
+ This function sets the return value of hid_error().
+
+ @ingroup API
+ @param path The path name of the device to open
+
+ @returns
+ This function returns a pointer to a #hid_device object on
+ success or NULL on failure.
+ */
+ HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path);
+
+ /** @brief Write an Output report to a HID device.
+
+ The first byte of @p data[] must contain the Report ID. For
+ devices which only support a single report, this must be set
+ to 0x0. The remaining bytes contain the report data. Since
+ the Report ID is mandatory, calls to hid_write() will always
+ contain one more byte than the report contains. For example,
+ if a hid report is 16 bytes long, 17 bytes must be passed to
+ hid_write(), the Report ID (or 0x0, for devices with a
+ single report), followed by the report data (16 bytes). In
+ this example, the length passed in would be 17.
+
+ hid_write() will send the data on the first OUT endpoint, if
+ one exists. If it does not, it will send the data through
+ the Control Endpoint (Endpoint 0).
+
+ This function sets the return value of hid_error().
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ @param data The data to send, including the report number as
+ the first byte.
+ @param length The length in bytes of the data to send.
+
+ @returns
+ This function returns the actual number of bytes written and
+ -1 on error.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length);
+
+ /** @brief Read an Input report from a HID device with timeout.
+
+ Input reports are returned
+ to the host through the INTERRUPT IN endpoint. The first byte will
+ contain the Report number if the device uses numbered reports.
+
+ This function sets the return value of hid_error().
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ @param data A buffer to put the read data into.
+ @param length The number of bytes to read. For devices with
+ multiple reports, make sure to read an extra byte for
+ the report number.
+ @param milliseconds timeout in milliseconds or -1 for blocking wait.
+
+ @returns
+ This function returns the actual number of bytes read and
+ -1 on error. If no packet was available to be read within
+ the timeout period, this function returns 0.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds);
+
+ /** @brief Read an Input report from a HID device.
+
+ Input reports are returned
+ to the host through the INTERRUPT IN endpoint. The first byte will
+ contain the Report number if the device uses numbered reports.
+
+ This function sets the return value of hid_error().
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ @param data A buffer to put the read data into.
+ @param length The number of bytes to read. For devices with
+ multiple reports, make sure to read an extra byte for
+ the report number.
+
+ @returns
+ This function returns the actual number of bytes read and
+ -1 on error. If no packet was available to be read and
+ the handle is in non-blocking mode, this function returns 0.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length);
+
+ /** @brief Set the device handle to be non-blocking.
+
+ In non-blocking mode calls to hid_read() will return
+ immediately with a value of 0 if there is no data to be
+ read. In blocking mode, hid_read() will wait (block) until
+ there is data to read before returning.
+
+ Nonblocking can be turned on and off at any time.
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ @param nonblock enable or not the nonblocking reads
+ - 1 to enable nonblocking
+ - 0 to disable nonblocking.
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock);
+
+ /** @brief Send a Feature report to the device.
+
+ Feature reports are sent over the Control endpoint as a
+ Set_Report transfer. The first byte of @p data[] must
+ contain the Report ID. For devices which only support a
+ single report, this must be set to 0x0. The remaining bytes
+ contain the report data. Since the Report ID is mandatory,
+ calls to hid_send_feature_report() will always contain one
+ more byte than the report contains. For example, if a hid
+ report is 16 bytes long, 17 bytes must be passed to
+ hid_send_feature_report(): the Report ID (or 0x0, for
+ devices which do not use numbered reports), followed by the
+ report data (16 bytes). In this example, the length passed
+ in would be 17.
+
+ This function sets the return value of hid_error().
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ @param data The data to send, including the report number as
+ the first byte.
+ @param length The length in bytes of the data to send, including
+ the report number.
+
+ @returns
+ This function returns the actual number of bytes written and
+ -1 on error.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length);
+
+ /** @brief Get a feature report from a HID device.
+
+ Set the first byte of @p data[] to the Report ID of the
+ report to be read. Make sure to allow space for this
+ extra byte in @p data[]. Upon return, the first byte will
+ still contain the Report ID, and the report data will
+ start in data[1].
+
+ This function sets the return value of hid_error().
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ @param data A buffer to put the read data into, including
+ the Report ID. Set the first byte of @p data[] to the
+ Report ID of the report to be read, or set it to zero
+ if your device does not use numbered reports.
+ @param length The number of bytes to read, including an
+ extra byte for the report ID. The buffer can be longer
+ than the actual report.
+
+ @returns
+ This function returns the number of bytes read plus
+ one for the report ID (which is still in the first
+ byte), or -1 on error.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length);
+
+ /** @brief Get a input report from a HID device.
+
+ Set the first byte of @p data[] to the Report ID of the
+ report to be read. Make sure to allow space for this
+ extra byte in @p data[]. Upon return, the first byte will
+ still contain the Report ID, and the report data will
+ start in data[1].
+
+ @ingroup API
+ @param device A device handle returned from hid_open().
+ @param data A buffer to put the read data into, including
+ the Report ID. Set the first byte of @p data[] to the
+ Report ID of the report to be read, or set it to zero
+ if your device does not use numbered reports.
+ @param length The number of bytes to read, including an
+ extra byte for the report ID. The buffer can be longer
+ than the actual report.
+
+ @returns
+ This function returns the number of bytes read plus
+ one for the report ID (which is still in the first
+ byte), or -1 on error.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length);
+
+ /** @brief Close a HID device.
+
+ This function sets the return value of hid_error().
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ */
+ void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev);
+
+ /** @brief Get The Manufacturer String from a HID device.
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ @param string A wide string buffer to put the data into.
+ @param maxlen The length of the buffer in multiples of wchar_t.
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen);
+
+ /** @brief Get The Product String from a HID device.
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ @param string A wide string buffer to put the data into.
+ @param maxlen The length of the buffer in multiples of wchar_t.
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen);
+
+ /** @brief Get The Serial Number String from a HID device.
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ @param string A wide string buffer to put the data into.
+ @param maxlen The length of the buffer in multiples of wchar_t.
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen);
+
+ /** @brief Get a string from a HID device, based on its string index.
+
+ @ingroup API
+ @param dev A device handle returned from hid_open().
+ @param string_index The index of the string to get.
+ @param string A wide string buffer to put the data into.
+ @param maxlen The length of the buffer in multiples of wchar_t.
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen);
+
+ /** @brief Get a string describing the last error which occurred.
+
+ Whether a function sets the last error is noted in its
+ documentation. These functions will reset the last error
+ to NULL before their execution.
+
+ Strings returned from hid_error() must not be freed by the user!
+
+ This function is thread-safe, and error messages are thread-local.
+
+ @ingroup API
+ @param dev A device handle returned from hid_open(),
+ or NULL to get the last non-device-specific error
+ (e.g. for errors in hid_open() itself).
+
+ @returns
+ This function returns a string containing the last error
+ which occurred or NULL if none has occurred.
+ */
+ HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev);
+
+ /** @brief Get a runtime version of the library.
+
+ @ingroup API
+
+ @returns
+ Pointer to statically allocated struct, that contains version.
+ */
+ HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void);
+
+
+ /** @brief Get a runtime version string of the library.
+
+ @ingroup API
+
+ @returns
+ Pointer to statically allocated string, that contains version string.
+ */
+ HID_API_EXPORT const char* HID_API_CALL hid_version_str(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/external/misc/CMakeLists.txt b/external/misc/CMakeLists.txt
index 07b10b2e..16125d0a 100644
--- a/external/misc/CMakeLists.txt
+++ b/external/misc/CMakeLists.txt
@@ -6,12 +6,31 @@ include_directories(
)
if(LINUX)
- list(APPEND misc_SOURCES
- # Provide our own copy of strlcpy and strlcat
- # because they are not included with Linux.
- ${CUSTOM_MISC_DIR}/strlcpy.c
- ${CUSTOM_MISC_DIR}/strlcat.c
- )
+# Previously -
+# list(APPEND misc_SOURCES
+# # Provide our own copy of strlcpy and strlcat
+# # because they are not included with Linux.
+# ${CUSTOM_MISC_DIR}/strlcpy.c
+# ${CUSTOM_MISC_DIR}/strlcat.c
+# )
+# It seems that Alpine Linux and Void Linux have strlcpy and
+# strlcat so we need to handle the situation more delicately.
+# When doing it this way, there is probably no reason to
+# distinguish between Linux and BSD-like systems here.
+# If we kept going, the same thing could be done for each
+# of the functions and no OS check would be needed.
+
+ if (NOT HAVE_STRLCPY)
+ list(APPEND misc_SOURCES
+ ${CUSTOM_MISC_DIR}/strlcpy.c
+ )
+ endif()
+
+ if (NOT HAVE_STRLCAT)
+ list(APPEND misc_SOURCES
+ ${CUSTOM_MISC_DIR}/strlcat.c
+ )
+ endif()
add_library(misc STATIC
${misc_SOURCES}
diff --git a/external/regex/CMakeLists.txt b/external/regex/CMakeLists.txt
index 67207639..76bbf9ba 100644
--- a/external/regex/CMakeLists.txt
+++ b/external/regex/CMakeLists.txt
@@ -18,7 +18,7 @@ if(WIN32 OR CYGWIN) # windows
)
set_target_properties(regex
- PROPERTIES COMPILE_FLAGS "-Dbool=int -Dtrue=1 -Dfalse=0 -DUSE_REGEX_STATIC"
+ PROPERTIES COMPILE_FLAGS "-Dbool=int -Dtrue=1 -Dfalse=0 -DREGEX_STATIC"
)
endif()
diff --git a/external/regex/regex.h b/external/regex/regex.h
index c2a9a4c3..52b5fede 100644
--- a/external/regex/regex.h
+++ b/external/regex/regex.h
@@ -35,17 +35,23 @@
#if (defined __WIN32__) || (defined _WIN32)
# ifdef BUILD_REGEX_DLL
# define REGEX_DLL_IMPEXP __DLL_EXPORT__
+# define REGEX_VARIABLE_IMPEXP __DLL_EXPORT__
# elif defined(REGEX_STATIC)
# define REGEX_DLL_IMPEXP
+# define REGEX_VARIABLE_IMPEXP
# elif defined (USE_REGEX_DLL)
# define REGEX_DLL_IMPEXP __DLL_IMPORT__
+# define REGEX_VARIABLE_IMPEXP __DLL_IMPORT__
# elif defined (USE_REGEX_STATIC)
# define REGEX_DLL_IMPEXP
+# define REGEX_VARIABLE_IMPEXP extern
# else /* assume USE_REGEX_DLL */
# define REGEX_DLL_IMPEXP __DLL_IMPORT__
+# define REGEX_VARIABLE_IMPEXP __DLL_IMPORT__
# endif
#else /* __WIN32__ */
# define REGEX_DLL_IMPEXP
+# define REGEX_VARIABLE_IMPEXP
#endif
/* Allow the use in C++ code. */
@@ -202,7 +208,7 @@ typedef unsigned long int reg_syntax_t;
some interfaces). When a regexp is compiled, the syntax used is
stored in the pattern buffer, so changing this does not affect
already-compiled regexps. */
-REGEX_DLL_IMPEXP reg_syntax_t re_syntax_options;
+REGEX_VARIABLE_IMPEXP reg_syntax_t re_syntax_options;
/* Define combinations of the above bits for the standard possibilities.
(The [[[ comments delimit what gets put into the Texinfo file, so
diff --git a/man/direwolf.1 b/man/direwolf.1
index 76bc195d..93f786dc 100644
--- a/man/direwolf.1
+++ b/man/direwolf.1
@@ -87,7 +87,15 @@ Divide audio sample by n for first channel.
.TP
.BI "-X " "n"
-1 to enable FX.25 transmit.
+1 to enable FX.25 transmit. 16, 32, 64 for specific number of check bytes.
+
+.TP
+.BI "-I " "n"
+Enable IL2P transmit. n=1 is recommended. 0 uses weaker FEC.
+
+.TP
+.BI "-i " "n"
+Enable IL2P transmit, inverted polarity. n=1 is recommended. 0 uses weaker FEC.
.TP
.BI "-d " "x"
@@ -122,6 +130,8 @@ m = Monitor heard station list.
f = Packet filtering.
.P
x = FX.25 increase verbose level.
+.P
+d = APRStt (DTMF to APRS object conversion).
.RE
.RE
.PD
@@ -135,6 +145,8 @@ Quiet (suppress output). Specify one or more of the following in place of x.
h = Heard line with the audio level.
.P
d = Decoding of APRS packets.
+.P
+x = Silence FX.25 information.
.RE
.RE
.PD
@@ -149,11 +161,26 @@ Text colors. 0=disabled. 1=default. 2,3,4,... alternatives. Use 9 to test com
Enable pseudo terminal for KISS protocol.
.TP
-.B "-x "
+.BI "-x "
Send Xmit level calibration tones.
+.PD 0
+.RS
+.RS
+a = Alternating mark/space tones.
+.P
+m = steady Mark tone (e.g. 1200 Hz)
+.P
+s = steady Space tone (e.g. 2200 Hz)
+.P
+p = selence (set Ptt only).
+.P
+Optionally add a number to specify radio channel.
+.RE
+.RE
+.PD
.TP
-.B "-U "
+.B "-u "
Print UTF-8 test string and exit.
.TP
diff --git a/man/gen_packets.1 b/man/gen_packets.1
index 773a88eb..a4042885 100644
--- a/man/gen_packets.1
+++ b/man/gen_packets.1
@@ -62,6 +62,18 @@ Force G3RUH modem regardless of data rate.
.BI "-J "
2400 bps QPSK compatible with MFJ-2400.
+.TP
+.BI "-X " "n"
+1 to enable FX.25 transmit. 16, 32, 64 for specific number of check bytes.
+
+.TP
+.BI "-I " "n"
+Enable IL2P transmit. n=1 is recommended. 0 uses weaker FEC.
+
+.TP
+.BI "-i " "n"
+Enable IL2P transmit, inverted polarity. n=1 is recommended. 0 uses weaker FEC.
+
.TP
.BI "-m " "n"
diff --git a/man/kissutil.1 b/man/kissutil.1
index 09eb12c6..a7968f97 100644
--- a/man/kissutil.1
+++ b/man/kissutil.1
@@ -45,7 +45,7 @@ Example: %H:%M:%S for current time in hours, minutes, seconds.
.TP
.BI "-f " "xmit-directory"
-Files in this directory are transmited and deleted.
+Files in this directory are transmitted and deleted.
Another application places a file here when it wants something to be transmitted.
.TP
diff --git a/man/tt2text.1 b/man/tt2text.1
index b3c3266c..c6214c27 100644
--- a/man/tt2text.1
+++ b/man/tt2text.1
@@ -11,7 +11,7 @@ tt2text \- Convert Touch Tone sequence to text
.SH DESCRIPTION
-\fBtt2text\fR converts a Touch Tone squence to text. There are two types
+\fBtt2text\fR converts a Touch Tone sequence to text. There are two types
of encoding:
.RS
.HP
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 46d3ac7a..a2c3963d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,3 +1,4 @@
+
# global includes
# not ideal but not so slow
# otherwise use target_include_directories
@@ -7,7 +8,9 @@ include_directories(
${ALSA_INCLUDE_DIRS}
${UDEV_INCLUDE_DIRS}
${PORTAUDIO_INCLUDE_DIRS}
+ ${SNDIO_INCLUDE_DIRS}
${CUSTOM_GEOTRANZ_DIR}
+ ${CUSTOM_HIDAPI_DIR}
)
if(WIN32 OR CYGWIN)
@@ -57,6 +60,13 @@ list(APPEND direwolf_SOURCES
hdlc_rec2.c
hdlc_send.c
igate.c
+ il2p_codec.c
+ il2p_scramble.c
+ il2p_rec.c
+ il2p_payload.c
+ il2p_init.c
+ il2p_header.c
+ il2p_send.c
kiss_frame.c
kiss.c
kissserial.c
@@ -96,9 +106,16 @@ if(LINUX)
cm108.c
)
endif()
+ if(AVAHI_CLIENT_FOUND)
+ list(APPEND direwolf_SOURCES
+ dns_sd_common.c
+ dns_sd_avahi.c
+ )
+ endif()
elseif(WIN32 OR CYGWIN) # windows
list(APPEND direwolf_SOURCES
audio_win.c
+ cm108.c
# icon
# require plain gcc binary or link
@@ -107,10 +124,20 @@ if(LINUX)
list(REMOVE_ITEM direwolf_SOURCES
dwgpsd.c
)
- else() # macOS freebsd openbsd
+ elseif(HAVE_SNDIO)
+ list(APPEND direwolf_SOURCES
+ audio.c
+ )
+ else() # macOS freebsd
list(APPEND direwolf_SOURCES
audio_portaudio.c
)
+ if(USE_MACOS_DNSSD)
+ list(APPEND direwolf_SOURCES
+ dns_sd_common.c
+ dns_sd_macos.c
+ )
+ endif()
endif()
add_executable(direwolf
@@ -121,19 +148,22 @@ target_link_libraries(direwolf
${GEOTRANZ_LIBRARIES}
${MISC_LIBRARIES}
${REGEX_LIBRARIES}
+ ${HIDAPI_LIBRARIES}
Threads::Threads
${GPSD_LIBRARIES}
${HAMLIB_LIBRARIES}
${ALSA_LIBRARIES}
${UDEV_LIBRARIES}
${PORTAUDIO_LIBRARIES}
+ ${SNDIO_LIBRARIES}
+ ${AVAHI_LIBRARIES}
)
if(WIN32 OR CYGWIN)
set_target_properties(direwolf
PROPERTIES COMPILE_FLAGS "-DUSE_REGEX_STATIC"
)
- target_link_libraries(direwolf winmm ws2_32)
+ target_link_libraries(direwolf winmm ws2_32 setupapi)
endif()
# decode_aprs
@@ -266,12 +296,20 @@ target_link_libraries(log2gpx
list(APPEND gen_packets_SOURCES
gen_packets.c
ax25_pad.c
+ ax25_pad2.c
fx25_encode.c
+ fx25_extract.c
fx25_init.c
fx25_send.c
hdlc_send.c
fcs_calc.c
gen_tone.c
+ il2p_codec.c
+ il2p_scramble.c
+ il2p_payload.c
+ il2p_init.c
+ il2p_header.c
+ il2p_send.c
morse.c
dtmf.c
textcolor.c
@@ -298,14 +336,22 @@ list(APPEND atest_SOURCES
demod_9600.c
dsp.c
fx25_extract.c
+ fx25_encode.c
fx25_init.c
fx25_rec.c
hdlc_rec.c
hdlc_rec2.c
+ il2p_codec.c
+ il2p_scramble.c
+ il2p_rec.c
+ il2p_payload.c
+ il2p_init.c
+ il2p_header.c
multi_modem.c
rrbb.c
fcs_calc.c
ax25_pad.c
+ ax25_pad2.c
decode_aprs.c
dwgpsnmea.c
dwgps.c
@@ -398,9 +444,35 @@ if(WIN32 OR CYGWIN)
endif()
+# TNC interoperability testing for AX.25 connected mode.
+# tnctest
+list(APPEND tnctest_SOURCES
+ tnctest.c
+ textcolor.c
+ dtime_now.c
+ serial_port.c
+ )
+
+add_executable(tnctest
+ ${tnctest_SOURCES}
+ )
+
+target_link_libraries(tnctest
+ ${MISC_LIBRARIES}
+ Threads::Threads
+ )
+
+if(WIN32 OR CYGWIN)
+ target_link_libraries(tnctest ws2_32)
+endif()
+
+
# List USB audio adapters than can use GPIO for PTT.
+# Originally for Linux only (using udev).
+# Version 1.7 adds it for Windows. Needs hidapi library.
+
# cm108
-if(UDEV_FOUND)
+if(UDEV_FOUND OR WIN32 OR CYGWIN)
list(APPEND cm108_SOURCES
cm108.c
textcolor.c
@@ -416,8 +488,21 @@ if(UDEV_FOUND)
target_link_libraries(cm108
${MISC_LIBRARIES}
- ${UDEV_LIBRARIES}
)
+
+ if (LINUX)
+ target_link_libraries(cm108
+ ${UDEV_LIBRARIES}
+ )
+ endif()
+
+ if (WIN32 OR CYGWIN)
+ target_link_libraries(cm108
+ ${HIDAPI_LIBRARIES}
+ ws2_32
+ setupapi
+ )
+ endif()
endif()
@@ -481,7 +566,8 @@ install(TARGETS gen_packets DESTINATION ${INSTALL_BIN_DIR})
install(TARGETS atest DESTINATION ${INSTALL_BIN_DIR})
install(TARGETS ttcalc DESTINATION ${INSTALL_BIN_DIR})
install(TARGETS kissutil DESTINATION ${INSTALL_BIN_DIR})
+install(TARGETS tnctest DESTINATION ${INSTALL_BIN_DIR})
install(TARGETS appserver DESTINATION ${INSTALL_BIN_DIR})
-if(UDEV_FOUND)
+if(UDEV_FOUND OR WIN32 OR CYGWIN)
install(TARGETS cm108 DESTINATION ${INSTALL_BIN_DIR})
endif()
diff --git a/src/agwlib.c b/src/agwlib.c
index 09881097..33d490c8 100644
--- a/src/agwlib.c
+++ b/src/agwlib.c
@@ -321,7 +321,7 @@ static void * tnc_listen_thread (void *arg)
s_tnc_sock = dwsock_connect (s_tnc_host, s_tnc_port, "TNC", 0, 0, tncaddr);
if (s_tnc_sock != -1) {
- dw_printf ("Succesfully reattached to network TNC.\n");
+ dw_printf ("Successfully reattached to network TNC.\n");
// Might need to run TNC initialization again.
// For example, a server would register its callsigns.
@@ -600,7 +600,7 @@ int agwlib_G_ask_port_information (void)
* Returns: Number of bytes sent for success, -1 for error.
*
* Description: This only starts the sequence and does not wait.
- * Success or failue will be indicated sometime later by ?
+ * Success or failure will be indicated sometime later by ?
*
*--------------------------------------------------------------------*/
@@ -635,7 +635,7 @@ int agwlib_C_connect (int chan, char *call_from, char *call_to)
* Returns: Number of bytes sent for success, -1 for error.
*
* Description: This only starts the sequence and does not wait.
- * Success or failue will be indicated sometime later by ?
+ * Success or failure will be indicated sometime later by ?
*
*--------------------------------------------------------------------*/
@@ -722,13 +722,13 @@ int agwlib_D_send_connected_data (int chan, int pid, char *call_from, char *call
* hand we don't want to get TOO far ahead when transferring a large file.
*
* Before disconnecting from another station, it would be good to know
- * that it actually recevied the last message we sent. For this reason,
+ * that it actually received the last message we sent. For this reason,
* I think it would be good for this to include frames that were
- * transmitted but not yet acknowleged. (Even if it was transmitted once,
+ * transmitted but not yet acknowledged. (Even if it was transmitted once,
* it could still be transmitted again, if lost, so you could say it is
* still waiting for transmission.)
*
- * See server.c for a more precise definition of exacly how this is defined.
+ * See server.c for a more precise definition of exactly how this is defined.
*
*--------------------------------------------------------------------*/
diff --git a/src/ais.c b/src/ais.c
index cadf6482..938fa012 100644
--- a/src/ais.c
+++ b/src/ais.c
@@ -338,7 +338,7 @@ void ais_to_nmea (unsigned char *ais, int ais_len, char *nmea, int nmea_size)
*
* Name: ais_parse
*
- * Purpose: Parse AIS sentence and extract interesing parts.
+ * Purpose: Parse AIS sentence and extract interesting parts.
*
* Inputs: sentence NMEA sentence.
*
@@ -594,7 +594,7 @@ int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mss
*
* Returns: -1 Invalid message type.
* 0 Good length.
- * 1 Unexpected lenth.
+ * 1 Unexpected length.
*
*--------------------------------------------------------------------*/
diff --git a/src/appserver.c b/src/appserver.c
index 2badaec8..b0ef7d87 100644
--- a/src/appserver.c
+++ b/src/appserver.c
@@ -320,7 +320,7 @@ static void poll_timing_test (void)
*
* data - Should look something like this for incoming:
* *** CONNECTED to Station xxx\r
- * and ths for my request being accepted:
+ * and this for my request being accepted:
* *** CONNECTED With Station xxx\r
*
* session_id - Session id to be used in data transfer and
@@ -491,15 +491,19 @@ void agw_cb_D_connected_data (int chan, char *call_from, char *call_to, int data
// who - list people currently logged in.
int n;
- char greeting[80];
+ char greeting[128];
snprintf (greeting, sizeof(greeting), "Session Channel User Since\r");
agwlib_D_send_connected_data (chan, 0xF0, mycall, call_from, strlen(greeting), greeting);
for (n = 0; n < MAX_SESSIONS; n++) {
if (session[n].client_addr[0]) {
+// I think compiler is confused. It says up to 520 characters can be written.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-truncation"
snprintf (greeting, sizeof(greeting), " %2d %d %-9s [time later]\r",
n, session[n].channel, session[n].client_addr);
+#pragma GCC diagnostic pop
agwlib_D_send_connected_data (chan, 0xF0, mycall, call_from, strlen(greeting), greeting);
}
}
diff --git a/src/aprs_tt.c b/src/aprs_tt.c
index 0543f8a7..7b125759 100644
--- a/src/aprs_tt.c
+++ b/src/aprs_tt.c
@@ -115,6 +115,8 @@ static int find_ttloc_match (char *e, char *xstr, char *ystr, char *zstr, char *
static void check_result (void);
#endif
+static int tt_debug = 0;
+
/*------------------------------------------------------------------
*
@@ -122,7 +124,8 @@ static void check_result (void);
*
* Purpose: Initialize the APRStt gateway at system startup time.
*
- * Inputs: Configuration options gathered by config.c.
+ * Inputs: P - Pointer to configuration options gathered by config.c.
+ * debug - Debug printing control.
*
* Global out: Make our own local copy of the structure here.
*
@@ -164,9 +167,10 @@ static struct ttloc_s test_config[] = {
#endif
-void aprs_tt_init (struct tt_config_s *p)
+void aprs_tt_init (struct tt_config_s *p, int debug)
{
int c;
+ tt_debug = debug;
#if TT_MAIN
/* For unit testing. */
@@ -208,7 +212,7 @@ void aprs_tt_init (struct tt_config_s *p)
* The complete message is then processed.
* The touch tone decoder sends $ if no activity
* for some amount of time, perhaps 5 seconds.
- * A partially accumulated messge is discarded if
+ * A partially accumulated message is discarded if
* there is a long gap.
*
* '.' means no activity during processing period.
@@ -483,7 +487,19 @@ static int parse_fields (char *msg)
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("parse_fields (%s).\n", msg);
- strlcpy (stemp, msg, sizeof(stemp));
+// Make a copy of msg because strtok corrupts the original.
+// While we are at it, remove any blanks.
+// This should not happen with DTMF reception but could happen
+// in manually crafted strings for testing.
+
+ int n = 0;
+ for (char *m = msg; *m != '\0' && n < sizeof(stemp)-1; m++) {
+ if (*m != ' ') {
+ stemp[n++] = *m;
+ }
+ }
+ stemp[n] = '\0';
+
e = strtok_r (stemp, "*#", &save);
while (e != NULL) {
@@ -551,7 +567,7 @@ static int parse_fields (char *msg)
default:
text_color_set(DW_COLOR_ERROR);
- dw_printf ("Field does not start with A, B, C, or digit: \"%s\"\n", msg);
+ dw_printf ("Field does not start with A, B, C, or digit: \"%s\"\n", e);
return (TT_ERROR_D_MSG);
}
@@ -574,7 +590,7 @@ static int parse_fields (char *msg)
* Purpose: Expand compact form "macro" to full format then process.
*
* Inputs: e - An "entry" extracted from a complete
- * APRStt messsage.
+ * APRStt message.
* In this case, it should contain only digits.
*
* Returns: 0 for success or one of the TT_ERROR_... codes.
@@ -689,18 +705,16 @@ static int expand_macro (char *e)
* Purpose: Extract traditional format callsign or object name from touch tone sequence.
*
* Inputs: e - An "entry" extracted from a complete
- * APRStt messsage.
- * In this case, it should start with "A".
+ * APRStt message.
+ * In this case, it should start with "A" then a digit.
*
* Outputs: m_callsign
*
* m_symtab_or_overlay - Set to 0-9 or A-Z if specified.
*
- * m_symbol_code - Always set to 'A'.
- * NO! This should be applied only if we
- * have the default value at this point.
- * The symbol might have been explicitly
- * set already and we don't want to overwrite that.
+ * m_symbol_code - Always set to 'A' (Box, DTMF or RFID)
+ * If you want a different symbol, use the new
+ * object name format and separate symbol specification.
*
* Returns: 0 for success or one of the TT_ERROR_... codes.
*
@@ -752,6 +766,11 @@ static int parse_callsign (char *e)
int len;
char tttemp[40], stemp[30];
+ if (tt_debug) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("APRStt parse callsign (starts with A then digit): \"%s\"\n", e);
+ }
+
assert (*e == 'A');
len = strlen(e);
@@ -762,6 +781,10 @@ static int parse_callsign (char *e)
if (len == 4 && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3])) {
strlcpy (m_callsign, e+1, sizeof(m_callsign));
+ if (tt_debug) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("Special case, 3 digit tactical call: \"%s\"\n", m_callsign);
+ }
return (0);
}
@@ -779,7 +802,7 @@ static int parse_callsign (char *e)
return (cs_err);
}
- strncpy (m_callsign, e+1, 3);
+ memcpy (m_callsign, e+1, 3);
m_callsign[3] = '\0';
if (len == 7) {
@@ -789,10 +812,20 @@ static int parse_callsign (char *e)
tt_two_key_to_text (tttemp, 0, stemp);
m_symbol_code = APRSTT_DEFAULT_SYMBOL;
m_symtab_or_overlay = stemp[0];
+ if (tt_debug) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("Three digit abbreviation1: callsign \"%s\", symbol code '%c (Box DTMF)', overlay '%c', checksum %c\n",
+ m_callsign, m_symbol_code, m_symtab_or_overlay, e[len-1]);
+ }
}
else {
m_symbol_code = APRSTT_DEFAULT_SYMBOL;
m_symtab_or_overlay = e[len-2];
+ if (tt_debug) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("Three digit abbreviation2: callsign \"%s\", symbol code '%c' (Box DTMF), overlay '%c', checksum %c\n",
+ m_callsign, m_symbol_code, m_symtab_or_overlay, e[len-1]);
+ }
}
return (0);
}
@@ -810,7 +843,7 @@ static int parse_callsign (char *e)
}
if (isupper(e[len-2])) {
- strncpy (tttemp, e+1, len-4);
+ memcpy (tttemp, e+1, len-4);
tttemp[len-4] = '\0';
tt_two_key_to_text (tttemp, 0, m_callsign);
@@ -820,14 +853,24 @@ static int parse_callsign (char *e)
tt_two_key_to_text (tttemp, 0, stemp);
m_symbol_code = APRSTT_DEFAULT_SYMBOL;
m_symtab_or_overlay = stemp[0];
+ if (tt_debug) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("Callsign in two key format1: callsign \"%s\", symbol code '%c' (Box DTMF), overlay '%c', checksum %c\n",
+ m_callsign, m_symbol_code, m_symtab_or_overlay, e[len-1]);
+ }
}
else {
- strncpy (tttemp, e+1, len-3);
+ memcpy (tttemp, e+1, len-3);
tttemp[len-3] = '\0';
tt_two_key_to_text (tttemp, 0, m_callsign);
m_symbol_code = APRSTT_DEFAULT_SYMBOL;
m_symtab_or_overlay = e[len-2];
+ if (tt_debug) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("Callsign in two key format2: callsign \"%s\", symbol code '%c' (Box DTMF), overlay '%c', checksum %c\n",
+ m_callsign, m_symbol_code, m_symtab_or_overlay, e[len-1]);
+ }
}
return (0);
}
@@ -845,7 +888,7 @@ static int parse_callsign (char *e)
* Purpose: Extract object name from touch tone sequence.
*
* Inputs: e - An "entry" extracted from a complete
- * APRStt messsage.
+ * APRStt message.
* In this case, it should start with "AA".
*
* Outputs: m_callsign
@@ -864,9 +907,11 @@ static int parse_callsign (char *e)
static int parse_object_name (char *e)
{
int len;
- //int c_length;
- //char tttemp[40];
- //char stemp[30];
+
+ if (tt_debug) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("APRStt parse object name (starts with AA): \"%s\"\n", e);
+ }
assert (e[0] == 'A');
assert (e[1] == 'A');
@@ -882,6 +927,10 @@ static int parse_object_name (char *e)
if (tt_two_key_to_text (e+2, 0, m_callsign) == 0) {
m_callsign[9] = '\0'; /* truncate to 9 */
m_ssid = 0; /* No ssid for object name */
+ if (tt_debug) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("Object name in two key format: \"%s\"\n", m_callsign);
+ }
return (0);
}
}
@@ -901,7 +950,7 @@ static int parse_object_name (char *e)
* Purpose: Extract symbol from touch tone sequence.
*
* Inputs: e - An "entry" extracted from a complete
- * APRStt messsage.
+ * APRStt message.
* In this case, it should start with "AB".
*
* Outputs: m_symtab_or_overlay
@@ -935,6 +984,11 @@ static int parse_symbol (char *e)
int nn;
char stemp[10];
+ if (tt_debug) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("APRStt parse symbol (starts with AB): \"%s\"\n", e);
+ }
+
assert (e[0] == 'A');
assert (e[1] == 'B');
@@ -959,12 +1013,22 @@ static int parse_symbol (char *e)
case '1':
m_symtab_or_overlay = '/';
m_symbol_code = 32 + nn;
+ if (tt_debug) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("symbol code '%c', primary symbol table '%c'\n",
+ m_symbol_code, m_symtab_or_overlay);
+ }
return (0);
break;
case '2':
m_symtab_or_overlay = '\\';
m_symbol_code = 32 + nn;
+ if (tt_debug) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("symbol code '%c', alternate symbol table '%c'\n",
+ m_symbol_code, m_symtab_or_overlay);
+ }
return (0);
break;
@@ -973,6 +1037,11 @@ static int parse_symbol (char *e)
if (tt_two_key_to_text (e+5, 0, stemp) == 0) {
m_symbol_code = 32 + nn;
m_symtab_or_overlay = stemp[0];
+ if (tt_debug) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("symbol code '%c', alternate symbol table with overlay '%c'\n",
+ m_symbol_code, m_symtab_or_overlay);
+ }
return (0);
}
}
@@ -995,7 +1064,7 @@ static int parse_symbol (char *e)
* Purpose: Extract QIKcom-2 / APRStt 3 ten digit call or five digit suffix.
*
* Inputs: e - An "entry" extracted from a complete
- * APRStt messsage.
+ * APRStt message.
* In this case, it should start with "AC".
*
* Outputs: m_callsign
@@ -1018,6 +1087,11 @@ static int parse_aprstt3_call (char *e)
assert (e[0] == 'A');
assert (e[1] == 'C');
+ if (tt_debug) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("APRStt parse QIKcom-2 / APRStt 3 ten digit call or five digit suffix (starts with AC): \"%s\"\n", e);
+ }
+
if (strlen(e) == 2+10) {
char call[12];
@@ -1073,7 +1147,7 @@ static int parse_aprstt3_call (char *e)
* Purpose: Extract location from touch tone sequence.
*
* Inputs: e - An "entry" extracted from a complete
- * APRStt messsage.
+ * APRStt message.
* In this case, it should start with "B".
*
* Outputs: m_latitude
@@ -1125,6 +1199,11 @@ static int parse_location (char *e)
char mh[20];
char stemp[32];
+ if (tt_debug) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("APRStt parse location (starts with B): \"%s\"\n", e);
+ // TODO: more detail later...
+ }
assert (*e == 'B');
@@ -1204,13 +1283,26 @@ static int parse_location (char *e)
lat0 = tt_config.ttloc_ptr[ipat].grid.lat0;
lat9 = tt_config.ttloc_ptr[ipat].grid.lat9;
+ double yrange = lat9 - lat0;
y = atof(ystr);
- m_latitude = lat0 + y * (lat9-lat0) / (pow(10., strlen(ystr)) - 1.);
-
+ double user_y_max = round(pow(10., strlen(ystr)) - 1.); // e.g. 999 for 3 digits
+ m_latitude = lat0 + yrange * y / user_y_max;
+#if 0
+ dw_printf ("TTLOC_GRID LAT min=%f, max=%f, range=%f\n", lat0, lat9, yrange);
+ dw_printf ("TTLOC_GRID LAT user_y=%f, user_y_max=%f\n", y, user_y_max);
+ dw_printf ("TTLOC_GRID LAT min + yrange * user_y / user_y_range = %f\n", m_latitude);
+#endif
lon0 = tt_config.ttloc_ptr[ipat].grid.lon0;
lon9 = tt_config.ttloc_ptr[ipat].grid.lon9;
+ double xrange = lon9 - lon0;
x = atof(xstr);
- m_longitude = lon0 + x * (lon9-lon0) / (pow(10., strlen(xstr)) - 1.);
+ double user_x_max = round(pow(10., strlen(xstr)) - 1.);
+ m_longitude = lon0 + xrange * x / user_x_max;
+#if 0
+ dw_printf ("TTLOC_GRID LON min=%f, max=%f, range=%f\n", lon0, lon9, xrange);
+ dw_printf ("TTLOC_GRID LON user_x=%f, user_x_max=%f\n", x, user_x_max);
+ dw_printf ("TTLOC_GRID LON min + xrange * user_x / user_x_range = %f\n", m_longitude);
+#endif
m_dao[2] = e[0];
m_dao[3] = e[1];
@@ -1415,7 +1507,7 @@ static int parse_location (char *e)
* defined in the configuration file.
*
* Inputs: e - An "entry" extracted from a complete
- * APRStt messsage.
+ * APRStt message.
* In this case, it should start with "B".
*
* valstrsize - size of the outputs so we can check for buffer overflow.
@@ -1566,7 +1658,7 @@ static int find_ttloc_match (char *e, char *xstr, char *ystr, char *zstr, char *
* Purpose: Extract comment / status or other special information from touch tone message.
*
* Inputs: e - An "entry" extracted from a complete
- * APRStt messsage.
+ * APRStt message.
* In this case, it should start with "C".
*
* Outputs: m_comment
@@ -1893,6 +1985,7 @@ static const struct {
/* Latitude comes out ok, 37.9137 -> 55.82 min. */
/* Longitude -81.1254 -> 8.20 min */
{ "B21234*A67979#", "679", "12", "7A", "", "", "12.3400", "56.1200", "!TB2!" },
+
{ "B533686*A67979#", "679", "12", "7A", "", "", "37.9222", "81.1143", "!TB5!" },
// TODO: should test other coordinate systems.
@@ -1985,7 +2078,7 @@ static void check_result (void)
int main (int argc, char *argv[])
{
- aprs_tt_init (NULL);
+ aprs_tt_init (NULL, 0);
error_count = 0;
diff --git a/src/aprs_tt.h b/src/aprs_tt.h
index 8cc845d8..4d33f487 100644
--- a/src/aprs_tt.h
+++ b/src/aprs_tt.h
@@ -123,7 +123,7 @@ struct tt_config_s {
int obj_recv_chan; /* Channel to listen for tones. */
int obj_xmit_chan; /* Channel to transmit object report. */
- /* -1 for none. This could happpen if we */
+ /* -1 for none. This could happen if we */
/* are only sending to application */
/* and/or IGate. */
@@ -165,7 +165,7 @@ struct tt_config_s {
-void aprs_tt_init (struct tt_config_s *p_config);
+void aprs_tt_init (struct tt_config_s *p_config, int debug);
void aprs_tt_button (int chan, char button);
diff --git a/src/atest.c b/src/atest.c
index 5c197759..5f2dd054 100644
--- a/src/atest.c
+++ b/src/atest.c
@@ -2,7 +2,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
-// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2019 John Langner, WB2OSZ
+// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2019, 2021 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -25,10 +25,10 @@
*
* Purpose: Test fixture for the AFSK demodulator.
*
- * Inputs: Takes audio from a .WAV file insted 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
- * controlled and reproducable conditions for tweaking.
+ * controlled and reproducible conditions for tweaking.
*
* For example
*
@@ -82,6 +82,7 @@
#include "ptt.h"
#include "dtime_now.h"
#include "fx25.h"
+#include "il2p.h"
#include "hdlc_rec.h"
@@ -189,6 +190,7 @@ static int h_opt = 0; // Hexadecimal display of received packet.
static char P_opt[16] = ""; // Demodulator profiles.
static int d_x_opt = 1; // FX.25 debug.
static int d_o_opt = 0; // "-d o" option for DCD output control. */
+static int d_2_opt = 0; // "-d 2" option for IL2P details. */
static int dcd_count = 0;
static int dcd_missing_errors = 0;
@@ -236,7 +238,7 @@ int main (int argc, char *argv[])
my_audio_config.achan[channel].space_freq = DEFAULT_SPACE_FREQ;
my_audio_config.achan[channel].baud = DEFAULT_BAUD;
- strlcpy (my_audio_config.achan[channel].profiles, "E", sizeof(my_audio_config.achan[channel].profiles));
+ strlcpy (my_audio_config.achan[channel].profiles, "A", sizeof(my_audio_config.achan[channel].profiles));
my_audio_config.achan[channel].num_freq = 1;
my_audio_config.achan[channel].offset = 0;
@@ -389,6 +391,7 @@ int main (int argc, char *argv[])
switch (*p) {
case 'x': d_x_opt++; break; // FX.25
case 'o': d_o_opt++; break; // DCD output control
+ case '2': d_2_opt++; break; // IL2P debug out
default: break;
}
}
@@ -430,19 +433,21 @@ int main (int argc, char *argv[])
/* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */
/* that need to be kept in sync. Maybe it could be a common function someday. */
- if (my_audio_config.achan[0].baud == 100) {
+ if (my_audio_config.achan[0].baud == 100) { // What was this for?
my_audio_config.achan[0].modem_type = MODEM_AFSK;
my_audio_config.achan[0].mark_freq = 1615;
my_audio_config.achan[0].space_freq = 1785;
- strlcpy (my_audio_config.achan[0].profiles, "D", sizeof(my_audio_config.achan[0].profiles));
+ //strlcpy (my_audio_config.achan[0].profiles, "A", sizeof(my_audio_config.achan[0].profiles));
}
- else if (my_audio_config.achan[0].baud < 600) {
+ 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].mark_freq = 1600;
my_audio_config.achan[0].space_freq = 1800;
- strlcpy (my_audio_config.achan[0].profiles, "D", sizeof(my_audio_config.achan[0].profiles));
+ // Previously we had a "D" which was fine tuned for 300 bps.
+ // In v1.7, it's not clear if we should use "B" or just stick with "A".
+ //strlcpy (my_audio_config.achan[0].profiles, "B", sizeof(my_audio_config.achan[0].profiles));
}
- else if (my_audio_config.achan[0].baud < 1800) {
+ else if (my_audio_config.achan[0].baud < 1800) { // common 1200
my_audio_config.achan[0].modem_type = MODEM_AFSK;
my_audio_config.achan[0].mark_freq = DEFAULT_MARK_FREQ;
my_audio_config.achan[0].space_freq = DEFAULT_SPACE_FREQ;
@@ -460,7 +465,7 @@ int main (int argc, char *argv[])
my_audio_config.achan[0].space_freq = 0;
strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles));
}
- else if (my_audio_config.achan[0].baud == 12345) {
+ else if (my_audio_config.achan[0].baud == 12345) { // Hack for different use of 9600
my_audio_config.achan[0].modem_type = MODEM_AIS;
my_audio_config.achan[0].baud = 9600;
my_audio_config.achan[0].mark_freq = 0;
@@ -473,7 +478,7 @@ int main (int argc, char *argv[])
// Will make more precise in afsk demod init.
my_audio_config.achan[0].mark_freq = 2083; // Actually 2083.3 - logic 1.
my_audio_config.achan[0].space_freq = 1563; // Actually 1562.5 - logic 0.
- strlcpy (my_audio_config.achan[0].profiles, "D", sizeof(my_audio_config.achan[0].profiles));
+ strlcpy (my_audio_config.achan[0].profiles, "A", sizeof(my_audio_config.achan[0].profiles));
}
else {
my_audio_config.achan[0].modem_type = MODEM_SCRAMBLE;
@@ -537,6 +542,7 @@ int main (int argc, char *argv[])
}
fx25_init (d_x_opt);
+ il2p_init (d_2_opt);
start_time = dtime_now();
@@ -614,9 +620,9 @@ int main (int argc, char *argv[])
my_audio_config.adev[0].bits_per_sample = format.wbitspersample;
my_audio_config.adev[0].num_channels = format.nchannels;
- my_audio_config.achan[0].medium = MEDIUM_RADIO;
+ my_audio_config.chan_medium[0] = MEDIUM_RADIO;
if (format.nchannels == 2) {
- my_audio_config.achan[1].medium = MEDIUM_RADIO;
+ my_audio_config.chan_medium[1] = MEDIUM_RADIO;
}
text_color_set(DW_COLOR_INFO);
@@ -703,7 +709,7 @@ int main (int argc, char *argv[])
dw_printf ("%d packets decoded in %.3f seconds. %.1f x realtime\n", packets_decoded_total, elapsed, total_filetime/elapsed);
if (d_o_opt) {
dw_printf ("DCD count = %d\n", dcd_count);
- dw_printf ("DCD missing erors = %d\n", dcd_missing_errors);
+ dw_printf ("DCD missing errors = %d\n", dcd_missing_errors);
}
if (error_if_less_than != -1 && packets_decoded_total < error_if_less_than) {
@@ -880,7 +886,7 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev
decode_aprs_t A;
- decode_aprs (&A, pp, 0);
+ decode_aprs (&A, pp, 0, 0);
// Temp experiment to see how different systems set the RR bits in the source and destination.
// log_rr_bits (&A, pp);
diff --git a/src/audio.c b/src/audio.c
index 613be06d..b8cf6b11 100644
--- a/src/audio.c
+++ b/src/audio.c
@@ -75,18 +75,17 @@
#include
#include
#include
+#include
#if USE_ALSA
#include
-#else
-#include
-#ifdef __OpenBSD__
-#include
+#elif USE_SNDIO
+#include
+#include
#else
#include
#endif
-#endif
#include "audio.h"
@@ -111,6 +110,9 @@ static struct adev_s {
int bytes_per_frame; /* number of bytes for a sample from all channels. */
/* e.g. 4 for stereo 16 bit. */
+#elif USE_SNDIO
+ struct sio_hdl *sndio_in_handle;
+ struct sio_hdl *sndio_out_handle;
#else
int oss_audio_device_fd; /* Single device, both directions. */
@@ -141,6 +143,9 @@ static struct adev_s {
#if USE_ALSA
static int set_alsa_params (int a, snd_pcm_t *handle, struct audio_s *pa, char *name, char *dir);
//static void alsa_select_device (char *pick_dev, int direction, char *result);
+#elif USE_SNDIO
+static int set_sndio_params (int a, struct sio_hdl *handle, struct audio_s *pa, char *devname, char *inout);
+static int poll_sndio (struct sio_hdl *hdl, int events);
#else
static int set_oss_params (int a, int fd, struct audio_s *pa);
#endif
@@ -201,7 +206,7 @@ static int calcbufsize(int rate, int chans, int bits)
* more restrictive in its capabilities.
* It might say, the best I can do is mono, 8 bit, 8000/sec.
*
- * The sofware modem must use this ACTUAL information
+ * The software modem must use this ACTUAL information
* that the device is supplying, that could be different
* than what the user specified.
*
@@ -212,7 +217,9 @@ static int calcbufsize(int rate, int chans, int bits)
int audio_open (struct audio_s *pa)
{
+#if !USE_SNDIO
int err;
+#endif
int chan;
int a;
char audio_in_name[30];
@@ -224,7 +231,11 @@ int audio_open (struct audio_s *pa)
memset (adev, 0, sizeof(adev));
for (a=0; aadev[a].adevice_in, O_RDWR);
@@ -439,6 +468,27 @@ int audio_open (struct audio_s *pa)
return (-1);
}
+#elif USE_SNDIO
+ adev[a].sndio_out_handle = sio_open (audio_out_name, SIO_PLAY, 0);
+ if (adev[a].sndio_out_handle == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Could not open audio device %s for output\n",
+ audio_out_name);
+ return (-1);
+ }
+
+ adev[a].outbuf_size_in_bytes = set_sndio_params (a, adev[a].sndio_out_handle, pa, audio_out_name, "output");
+
+ if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) {
+ return (-1);
+ }
+
+ if (!sio_start (adev[a].sndio_out_handle)) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Could not start audio device %s for output\n",
+ audio_out_name);
+ return (-1);
+ }
#endif
/*
@@ -675,6 +725,112 @@ static int set_alsa_params (int a, snd_pcm_t *handle, struct audio_s *pa, char *
} /* end alsa_set_params */
+#elif USE_SNDIO
+
+/*
+ * Set parameters for sound card. (sndio)
+ *
+ * See /usr/include/sndio.h for details.
+ */
+
+static int set_sndio_params (int a, struct sio_hdl *handle, struct audio_s *pa, char *devname, char *inout)
+{
+
+ struct sio_par q, r;
+
+ /* Signed 16 bit little endian or unsigned 8 bit. */
+ sio_initpar (&q);
+ q.bits = pa->adev[a].bits_per_sample;
+ q.bps = (q.bits + 7) / 8;
+ q.sig = (q.bits == 8) ? 0 : 1;
+ q.le = 1; /* always little endian */
+ q.msb = 0; /* LSB aligned */
+ q.rchan = q.pchan = pa->adev[a].num_channels;
+ q.rate = pa->adev[a].samples_per_sec;
+ q.xrun = SIO_IGNORE;
+ q.appbufsz = calcbufsize(pa->adev[a].samples_per_sec, pa->adev[a].num_channels, pa->adev[a].bits_per_sample);
+
+
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("suggest buffer size %d bytes for %s %s.\n",
+ q.appbufsz, devname, inout);
+#endif
+
+ /* challenge new setting */
+ if (!sio_setpar (handle, &q)) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Could not set hardware parameter for %s %s.\n",
+ devname, inout);
+ return (-1);
+ }
+
+ /* get response */
+ if (!sio_getpar (handle, &r)) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Could not obtain current hardware setting for %s %s.\n",
+ devname, inout);
+ return (-1);
+ }
+
+#if DEBUG
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("audio buffer size %d bytes for %s %s.\n",
+ r.appbufsz, devname, inout);
+#endif
+ if (q.rate != r.rate) {
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("Asked for %d samples/sec but got %d for %s %s.",
+ pa->adev[a].samples_per_sec, r.rate, devname, inout);
+ pa->adev[a].samples_per_sec = r.rate;
+ }
+
+ /* not supported */
+ if (q.bits != r.bits || q.bps != r.bps || q.sig != r.sig ||
+ (q.bits > 8 && q.le != r.le) ||
+ (*inout == 'o' && q.pchan != r.pchan) ||
+ (*inout == 'i' && q.rchan != r.rchan)) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Unsupported format for %s %s.\n", devname, inout);
+ return (-1);
+ }
+
+ return r.appbufsz;
+
+} /* end set_sndio_params */
+
+static int poll_sndio (struct sio_hdl *hdl, int events)
+{
+ struct pollfd *pfds;
+ int nfds, revents;
+
+ nfds = sio_nfds (hdl);
+ pfds = alloca (nfds * sizeof(struct pollfd));
+
+ do {
+ nfds = sio_pollfd (hdl, pfds, events);
+ if (nfds < 1) {
+ /* no need to wait */
+ return (0);
+ }
+ if (poll (pfds, nfds, -1) < 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("poll %d\n", errno);
+ return (-1);
+ }
+ revents = sio_revents (hdl, pfds);
+ } while (!(revents & (events | POLLHUP)));
+
+ /* unrecoverable error occurred */
+ if (revents & POLLHUP) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("waited for %s, POLLHUP received\n", (events & POLLIN) ? "POLLIN" : "POLLOUT");
+ return (-1);
+ }
+
+ return (0);
+}
+
#else
@@ -769,7 +925,7 @@ static int set_oss_params (int a, int fd, struct audio_s *pa)
* This was long ago under different conditions.
* Should study this again some day.
*
- * Your milage may vary.
+ * Your mileage may vary.
*/
err = ioctl (fd, SNDCTL_DSP_GETBLKSIZE, &ossbuf_size_in_bytes);
if (err == -1) {
@@ -842,7 +998,9 @@ __attribute__((hot))
int audio_get (int a)
{
int n;
+#if USE_ALSA
int retries = 0;
+#endif
#if STATISTICS
/* Gather numbers for read from audio device. */
@@ -936,6 +1094,8 @@ int audio_get (int a)
dw_printf ("This is most likely caused by the CPU being too slow to keep up with the audio stream.\n");
dw_printf ("Use the \"top\" command, in another command window, to look at CPU usage.\n");
dw_printf ("This might be a temporary condition so we will attempt to recover a few times before giving up.\n");
+ dw_printf ("If using a very slow CPU, try reducing the CPU load by using -P- command\n");
+ dw_printf ("line option for 9600 bps or -D3 for slower AFSK .\n");
}
audio_stats (a,
@@ -970,7 +1130,28 @@ int audio_get (int a)
}
-#else /* end ALSA, begin OSS */
+#elif USE_SNDIO
+
+ while (adev[a].inbuf_next >= adev[a].inbuf_len) {
+
+ assert (adev[a].sndio_in_handle != NULL);
+ if (poll_sndio (adev[a].sndio_in_handle, POLLIN) < 0) {
+ adev[a].inbuf_len = 0;
+ adev[a].inbuf_next = 0;
+ return (-1);
+ }
+
+ n = sio_read (adev[a].sndio_in_handle, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes);
+ adev[a].inbuf_len = n;
+ adev[a].inbuf_next = 0;
+
+ audio_stats (a,
+ save_audio_config_p->adev[a].num_channels,
+ n / (save_audio_config_p->adev[a].num_channels * save_audio_config_p->adev[a].bits_per_sample / 8),
+ save_audio_config_p->statistics_interval);
+ }
+
+#else /* begin OSS */
/* Fixed in 1.2. This was formerly outside of the switch */
/* so the OSS version did not process stdin or UDP. */
@@ -1250,6 +1431,37 @@ int audio_flush (int a)
adev[a].outbuf_len = 0;
return (-1);
+#elif USE_SNDIO
+
+ int k;
+ unsigned char *ptr;
+ int len;
+
+ ptr = adev[a].outbuf_ptr;
+ len = adev[a].outbuf_len;
+
+ while (len > 0) {
+ assert (adev[a].sndio_out_handle != NULL);
+ if (poll_sndio (adev[a].sndio_out_handle, POLLOUT) < 0) {
+ text_color_set(DW_COLOR_ERROR);
+ perror("Can't write to audio device");
+ adev[a].outbuf_len = 0;
+ return (-1);
+ }
+
+ k = sio_write (adev[a].sndio_out_handle, ptr, len);
+#if DEBUGx
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("audio_flush(): write %d returns %d\n", len, k);
+ fflush (stdout);
+#endif
+ ptr += k;
+ len -= k;
+ }
+
+ adev[a].outbuf_len = 0;
+ return (0);
+
#else /* OSS */
int k;
@@ -1351,6 +1563,10 @@ void audio_wait (int a)
* Either way, the caller will now compensate for it.
*/
+#elif USE_SNDIO
+
+ poll_sndio (adev[a].sndio_out_handle, POLLOUT);
+
#else
assert (adev[a].oss_audio_device_fd > 0);
@@ -1396,7 +1612,22 @@ int audio_close (void)
snd_pcm_close (adev[a].audio_in_handle);
snd_pcm_close (adev[a].audio_out_handle);
-
+
+ adev[a].audio_in_handle = adev[a].audio_out_handle = NULL;
+
+#elif USE_SNDIO
+
+ if (adev[a].sndio_in_handle != NULL && adev[a].sndio_out_handle != NULL) {
+
+ audio_wait (a);
+
+ sio_stop (adev[a].sndio_in_handle);
+ sio_stop (adev[a].sndio_out_handle);
+ sio_close (adev[a].sndio_in_handle);
+ sio_close (adev[a].sndio_out_handle);
+
+ adev[a].sndio_in_handle = adev[a].sndio_out_handle = NULL;
+
#else
if (adev[a].oss_audio_device_fd > 0) {
diff --git a/src/audio.h b/src/audio.h
index 61dec9d9..78327a73 100644
--- a/src/audio.h
+++ b/src/audio.h
@@ -107,10 +107,11 @@ struct audio_s {
float recv_ber; /* Receive Bit Error Rate (BER). */
/* Probability of inverting a bit coming out of the modem. */
- int fx25_xmit_enable; /* Enable transmission of FX.25. */
+ //int fx25_xmit_enable; /* Enable transmission of FX.25. */
/* See fx25_init.c for explanation of values. */
/* Initially this applies to all channels. */
/* This should probably be per channel. One step at a time. */
+ /* v1.7 - replaced by layer2_xmit==LAYER2_FX25 */
int fx25_auto_enable; /* Turn on FX.25 for current connected mode session */
/* under poor conditions. */
@@ -118,32 +119,39 @@ struct audio_s {
/* I put it here, rather than with the rest of the link layer */
/* parameters because it is really a part of the HDLC layer */
/* and is part of the KISS TNC functionality rather than our data link layer. */
+ /* Future: not used yet. */
+
char timestamp_format[40]; /* -T option */
/* Precede received & transmitted frames with timestamp. */
/* Command line option uses "strftime" format string. */
- /* Properties for each channel, common to receive and transmit. */
- /* Can be different for each radio channel. */
/* originally a "channel" was always connected to an internal modem. */
/* In version 1.6, this is generalized so that a channel (as seen by client application) */
/* can be connected to something else. Initially, this will allow application */
/* access to the IGate. Later we might have network TNCs or other internal functions. */
+ // Properties for all channels.
- struct achan_param_s {
-
- // Originally there was a boolean, called "valid", to indicate that the
- // channel is valid. This has been replaced with the new "medium" which
- // will allow channels to correspond to things other than internal modems.
-
- enum medium_e medium; // MEDIUM_NONE for invalid.
+ enum medium_e chan_medium[MAX_TOTAL_CHANS];
+ // MEDIUM_NONE for invalid.
// MEDIUM_RADIO for internal modem. (only possibility earlier)
// MEDIUM_IGATE allows application access to IGate.
+ // MEDIUM_NETTNC for external TNC via TCP.
+
+ int igate_vchannel; /* Virtual channel mapped to APRS-IS. */
+ /* -1 for none. */
+ /* Redundant but it makes things quicker and simpler */
+ /* than always searching thru above. */
+
+ /* Properties for each radio channel, common to receive and transmit. */
+ /* Can be different for each radio channel. */
+ struct achan_param_s {
+ // What else should be moved out of structure and enlarged when NETTNC is implemented. ???
char mycall[AX25_MAX_ADDR_LEN]; /* Call associated with this radio channel. */
/* Could all be the same or different. */
@@ -156,9 +164,26 @@ struct audio_s {
/* Might try MFJ-2400 / CCITT v.26 / Bell 201 someday. */
/* No modem. Might want this for DTMF only channel. */
+ enum layer2_t { LAYER2_AX25 = 0, LAYER2_FX25, LAYER2_IL2P } layer2_xmit;
+
+ // IL2P - New for version 1.7.
+ // New layer 2 with FEC. Much less overhead than FX.25 but no longer backward compatible.
+ // Only applies to transmit.
+ // Listening for FEC sync word should add negligible overhead so
+ // we leave reception enabled all the time as we do with FX.25.
+ // TODO: FX.25 should probably be put here rather than global for all channels.
+
+ int fx25_strength; // Strength of FX.25 FEC.
+ // 16, 23, 64 for specific number of parity symbols.
+ // 1 for automatic selection based on frame size.
+
+ int il2p_max_fec; // 1 for max FEC length, 0 for automatic based on size.
+
+ int il2p_invert_polarity; // 1 means invert on transmit. Receive handles either automatically.
+
enum v26_e { V26_UNSPECIFIED=0, V26_A, V26_B } v26_alternative;
- // Original implementaion used alternative A for 2400 bbps PSK.
+ // Original implementation used alternative A for 2400 bbps PSK.
// Years later, we discover that MFJ-2400 used alternative B.
// It's likely the others did too. it also works a little better.
// Default to MFJ compatible and print warning if user did not
@@ -240,15 +265,17 @@ struct audio_s {
ptt_method_t ptt_method; /* none, serial port, GPIO, LPT, HAMLIB, CM108. */
- char ptt_device[100]; /* Serial device name for PTT. e.g. COM1 or /dev/ttyS0 */
+ char ptt_device[128]; /* Serial device name for PTT. e.g. COM1 or /dev/ttyS0 */
/* Also used for HAMLIB. Could be host:port when model is 1. */
/* For years, 20 characters was plenty then we start getting extreme names like this: */
/* /dev/serial/by-id/usb-FTDI_Navigator__CAT___2nd_PTT__00000000-if00-port0 */
/* /dev/serial/by-id/usb-Prolific_Technology_Inc._USB-Serial_Controller_D-if00-port0 */
/* Issue 104, changed to 100 bytes in version 1.5. */
- /* This same field is also used for CM108 GPIO PTT which will */
- /* have a name like /dev/hidraw1. */
+ /* This same field is also used for CM108/CM119 GPIO PTT which will */
+ /* have a name like /dev/hidraw1 for Linux or */
+ /* \\?\hid#vid_0d8c&pid_0008&mi_03#8&39d3555&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030} */
+ /* for Windows. Largest observed was 95 but add some extra to be safe. */
ptt_line_t ptt_line; /* Control line when using serial port. PTT_LINE_RTS, PTT_LINE_DTR. */
ptt_line_t ptt_line2; /* Optional second one: PTT_LINE_NONE when not used. */
@@ -256,12 +283,12 @@ struct audio_s {
int out_gpio_num; /* GPIO number. Originally this was only for PTT. */
/* It is now more general. */
/* octrl array is indexed by PTT, DCD, or CONnected indicator. */
- /* For CM108, this should be in range of 1-8. */
+ /* For CM108/CM119, this should be in range of 1-8. */
#define MAX_GPIO_NAME_LEN 20 // 12 would cover any case I've seen so this should be safe
char out_gpio_name[MAX_GPIO_NAME_LEN];
- /* orginally, gpio number NN was assumed to simply */
+ /* originally, gpio number NN was assumed to simply */
/* have the name gpioNN but this turned out not to be */
/* the case for CubieBoard where it was longer. */
/* This is filled in by ptt_init so we don't have to */
@@ -298,7 +325,7 @@ struct audio_s {
int in_gpio_num; /* GPIO number */
char in_gpio_name[MAX_GPIO_NAME_LEN];
- /* orginally, gpio number NN was assumed to simply */
+ /* originally, gpio number NN was assumed to simply */
/* have the name gpioNN but this turned out not to be */
/* the case for CubieBoard where it was longer. */
/* This is filled in by ptt_init so we don't have to */
@@ -312,7 +339,7 @@ struct audio_s {
int dwait; /* First wait extra time for receiver squelch. */
/* Default 0 units of 10 mS each . */
- int slottime; /* Slot time in 10 mS units for persistance algorithm. */
+ int slottime; /* Slot time in 10 mS units for persistence algorithm. */
/* Typical value is 10 meaning 100 milliseconds. */
int persist; /* Sets probability for transmitting after each */
@@ -350,8 +377,8 @@ struct audio_s {
#define DEFAULT_ADEVICE "" /* Mac OSX: Empty string = default audio device. */
#elif USE_ALSA
#define DEFAULT_ADEVICE "default" /* Use default device for ALSA. */
-#elif __OpenBSD__
-#define DEFAULT_ADEVICE "default" /* Use default device for OpenBSD-portaudio. */
+#elif USE_SNDIO
+#define DEFAULT_ADEVICE "default" /* Use default device for sndio. */
#else
#define DEFAULT_ADEVICE "/dev/dsp" /* First audio device for OSS. (FreeBSD) */
#endif
diff --git a/src/audio_portaudio.c b/src/audio_portaudio.c
index 6d53f6af..77c4ee35 100644
--- a/src/audio_portaudio.c
+++ b/src/audio_portaudio.c
@@ -156,7 +156,7 @@ static int calcbufsize(int rate, int chans, int bits)
* the same device name for more then one connected device
* (ie two SignaLinks). Appending a Portaudio device index to the
* the device name ensure we can find the correct one. And if it's not
- * available return the first occurence that matches the device name.
+ * available return the first occurrence that matches the device name.
*----------------------------------------------------------------*/
static int searchPADevice(struct adev_s *dev, char *_devName, int reqDeviceNo, int io_flag)
{
@@ -513,7 +513,7 @@ static int paOutput16CB( const void *inputBuffer, void *outputBuffer,
* more restrictive in its capabilities.
* It might say, the best I can do is mono, 8 bit, 8000/sec.
*
- * The sofware modem must use this ACTUAL information
+ * The software modem must use this ACTUAL information
* that the device is supplying, that could be different
* than what the user specified.
*
diff --git a/src/audio_win.c b/src/audio_win.c
index 2183d107..1ba64bba 100644
--- a/src/audio_win.c
+++ b/src/audio_win.c
@@ -209,7 +209,7 @@ static struct adev_s {
* more restrictive in its capabilities.
* It might say, the best I can do is mono, 8 bit, 8000/sec.
*
- * The sofware modem must use this ACTUAL information
+ * The software modem must use this ACTUAL information
* that the device is supplying, that could be different
* than what the user specified.
*
@@ -561,6 +561,8 @@ int audio_open (struct audio_s *pa)
*/
case AUDIO_IN_TYPE_SOUNDCARD:
+ // Use InitializeCriticalSectionAndSpinCount to avoid exceptions in low memory situations?
+
InitializeCriticalSection (&(A->in_cs));
err = waveInOpen (&(A->audio_in_handle), in_dev_no[a], &wf, (DWORD_PTR)in_callback, a, CALLBACK_FUNCTION);
@@ -921,7 +923,7 @@ int audio_get (int a)
* c - One byte in range of 0 - 255.
*
*
- * Global In: out_current - index of output buffer currenly being filled.
+ * Global In: out_current - index of output buffer currently being filled.
*
* Returns: Normally non-negative.
* -1 for any type of error.
diff --git a/src/ax25_link.c b/src/ax25_link.c
index 041066e1..09e71359 100644
--- a/src/ax25_link.c
+++ b/src/ax25_link.c
@@ -26,6 +26,10 @@
* Establish connections and transfer data in the proper
* order with retries.
*
+ * Using the term "data link" is rather unfortunate because it causes
+ * confusion to someone familiar with the OSI networking model.
+ * This corresponds to the layer 4 transport, not layer 2 data link.
+ *
* Description:
*
* Typical sequence for establishing a connection
@@ -255,7 +259,7 @@ typedef struct ax25_dlsm_s {
// addrs[OWNCALL] is owncall for this end of link.
// Note that we are acting on behalf of
// a client application so the APRS mycall
- // might not be relevent.
+ // might not be relevant.
#define PEERCALL AX25_DESTINATION
// addrs[PEERCALL] is call for other end.
@@ -319,8 +323,8 @@ typedef struct ax25_dlsm_s {
int reject_exception; // A REJ frame has been sent to the remote station. (boolean)
- // This is used only when receving an I frame, in states 3 & 4, SREJ not enabled.
- // When an I frame has an unepected N(S),
+ // This is used only when receiving an I frame, in states 3 & 4, SREJ not enabled.
+ // When an I frame has an unexpected N(S),
// - if not already set, set it and send REJ.
// When an I frame with expected N(S) is received, clear it.
// This would prevent us from sending additional REJ while
@@ -824,6 +828,11 @@ static ax25_dlsm_t *get_link_handle (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LE
// Create new data link state machine.
p = calloc (sizeof(ax25_dlsm_t), 1);
+ if (p == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
+ }
p->magic1 = MAGIC1;
p->start_time = dtime_now();
p->stream_id = next_stream_id++;
@@ -1157,7 +1166,7 @@ void dl_disconnect_request (dlq_item_t *E)
*
* Erratum: Not sure how to interpret that. See example below for how it was implemented.
*
- * Version 1.6: Bug 252. Segmentation was occuring for a V2.0 link. From the spec:
+ * Version 1.6: Bug 252. Segmentation was occurring for a V2.0 link. From the spec:
* "The receipt of an XID response from the other station establishes that both
* stations are using AX.25 version 2.2 or higher and enables the use of the
* segmenter/reassembler and selective reject."
@@ -1493,6 +1502,11 @@ void dl_register_callsign (dlq_item_t *E)
}
r = calloc(sizeof(reg_callsign_t),1);
+ if (r == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
+ }
strlcpy (r->callsign, E->addrs[0], sizeof(r->callsign));
r->chan = E->chan;
r->client = E->client;
@@ -1564,7 +1578,7 @@ void dl_unregister_callsign (dlq_item_t *E)
*
* Description: This is the sum of:
* - Incoming connected data, from application still in the queue.
- * - I frames which have been transmitted but not yet acknowleged.
+ * - I frames which have been transmitted but not yet acknowledged.
*
*------------------------------------------------------------------------------*/
@@ -1633,7 +1647,7 @@ void dl_outstanding_frames_request (dlq_item_t *E)
* Description: By client application we mean something that attached with the
* AGW network protocol.
*
- * Clean out anything related to the specfied client application.
+ * Clean out anything related to the specified client application.
* This would include state machines and registered callsigns.
*
*------------------------------------------------------------------------------*/
@@ -2201,7 +2215,7 @@ void lm_data_indication (dlq_item_t *E)
break;
// Erratum: The AX.25 spec is not clear about whether SREJ should be command, response, or both.
-// The underlying X.25 spec clearly says it is reponse only. Let's go with that.
+// The underlying X.25 spec clearly says it is response only. Let's go with that.
case frame_type_S_SREJ:
case frame_type_U_DM:
@@ -2224,7 +2238,7 @@ void lm_data_indication (dlq_item_t *E)
case frame_type_U_UI:
// Don't test at this point in case an APRS frame gets thru.
// APRS doesn't specify what to put in the Source and Dest C bits.
- // In practice we see all 4 possble combinations.
+ // In practice we see all 4 possible combinations.
// I have an opinion about what would be "correct" (discussed elsewhere)
// but in practice no one seems to care.
break;
@@ -2701,7 +2715,7 @@ static void i_frame_continued (ax25_dlsm_t *S, int p, int ns, int pid, char *inf
if (S->rxdata_by_ns[ns] != NULL) {
// There is a possibility that we might have another received frame stashed
- // away from 8 or 128 (modulo) frames back. Remove it so it doesn't accidently
+ // away from 8 or 128 (modulo) frames back. Remove it so it doesn't accidentally
// show up at some future inopportune time.
cdata_delete (S->rxdata_by_ns[ns]);
@@ -2786,7 +2800,7 @@ static void i_frame_continued (ax25_dlsm_t *S, int p, int ns, int pid, char *inf
// we discard 3,4,5,6, and tell the other end to resend everything starting with 2.
// At one time, I had some doubts about when to use command or response for REJ.
-// I now believe that reponse, as implied by setting F in the flow chart, is correct.
+// I now believe that response, as implied by setting F in the flow chart, is correct.
int f = p;
int nr = S->vr; // Next expected sequence number.
@@ -2796,7 +2810,7 @@ static void i_frame_continued (ax25_dlsm_t *S, int p, int ns, int pid, char *inf
S->reject_exception = 1;
if (s_debug_retry) {
- text_color_set(DW_COLOR_ERROR); // make it more noticable.
+ text_color_set(DW_COLOR_ERROR); // make it more noticeable.
dw_printf ("sending REJ, at %s %d, SREJ not enabled case, V(R)=%d", __func__, __LINE__, S->vr);
}
@@ -2860,7 +2874,7 @@ static void i_frame_continued (ax25_dlsm_t *S, int p, int ns, int pid, char *inf
// In version 1.4:
// We end up sending more SREJ than necessary and and get back redundant information. Example:
// When we see 113 missing, we ask for a resend.
-// When we see 115 & 116 missing, a cummulative SREJ asks for everything.
+// When we see 115 & 116 missing, a cumulative SREJ asks for everything.
// The other end dutifully sends 113 twice.
//
// [0.4] DW1>DW0:(SREJ res, n(r)=113, f=0)
@@ -2890,7 +2904,7 @@ static void i_frame_continued (ax25_dlsm_t *S, int p, int ns, int pid, char *inf
// int allow_f1 = 0; // F=1 from X.25 2.4.6.4 b) 3)
int allow_f1 = 1; // F=1 from X.25 2.4.6.4 b) 3)
-// send only for this gap, not cummulative from V(R).
+// send only for this gap, not cumulative from V(R).
int last = AX25MODULO(ns - 1, S->modulo, __FILE__, __func__, __LINE__);
int first = last;
@@ -2993,7 +3007,7 @@ dw_printf ("%s:%d, %d srej exceptions, V(R)=%d, N(S)=%d\n", __func__, __LINE__,
if (first == AX25MODULO(S->vr - 1, S->modulo, __FILE__, __func__, __LINE__)) {
// Oops! Went too far. This I frame was already processed.
text_color_set(DW_COLOR_ERROR);
- dw_printf ("INTERNAL ERROR calulating what to put in SREJ, %s line %d\n", __func__, __LINE__);
+ dw_printf ("INTERNAL ERROR calculating what to put in SREJ, %s line %d\n", __func__, __LINE__);
dw_printf ("V(R)=%d, N(S)=%d, SREJ exception=%d, first=%d, ask_resend_count=%d\n", S->vr, ns, selective_reject_exception(S), first, ask_resend_count);
int k;
for (k=0; k<128; k++) {
@@ -3146,7 +3160,7 @@ static void send_srej_frames (ax25_dlsm_t *S, int *resend, int count, int allow_
if (s_debug_retry) {
text_color_set(DW_COLOR_INFO);
dw_printf ("%s line %d\n", __func__, __LINE__);
- //dw_printf ("state=%d, count=%d, k=%d, V(R)=%d, SREJ exeception=%d\n", S->state, count, S->k_maxframe, S->vr, selective_reject_exception(S));
+ //dw_printf ("state=%d, count=%d, k=%d, V(R)=%d, SREJ exception=%d\n", S->state, count, S->k_maxframe, S->vr, selective_reject_exception(S));
dw_printf ("state=%d, count=%d, k=%d, V(R)=%d\n", S->state, count, S->k_maxframe, S->vr);
dw_printf ("resend[]=");
@@ -3447,7 +3461,7 @@ static void rr_rnr_frame (ax25_dlsm_t *S, int ready, cmdres_t cr, int pf, int nr
// we received RR frames with N(R) values indicating that the other side received everything
// that we sent. Eventually rc could reach the limit and we would get an error.
// If we are in state 4, and other guy ack'ed last I frame we sent, transition to state 3.
-// The same thing was done for receving I frames after check_i_frame_ackd.
+// The same thing was done for receiving I frames after check_i_frame_ackd.
// Thought: Could we simply call check_i_frame_ackd, for consistency, rather than only setting V(A)?
@@ -3759,7 +3773,7 @@ static void rej_frame (ax25_dlsm_t *S, cmdres_t cr, int pf, int nr)
*
* The SREJ command/response initiates more-efficient error recovery by requesting the retransmission of a
* single I frame following the detection of a sequence error. This is an advancement over the earlier versions in
- * which the requested I frame was retransmitted togther with all additional I frames subsequently transmitted and
+ * which the requested I frame was retransmitted together with all additional I frames subsequently transmitted and
* successfully received.
*
* When a TNC sends one or more SREJ commands, each with the P bit set to "0" or "1", or one or more SREJ
@@ -4409,7 +4423,7 @@ static void disc_frame (ax25_dlsm_t *S, int p)
* earliest opportunity. If the TNC is not capable of accepting a SABME command, it responds with a DM frame.
*
* A TNC that uses a version of AX.25 prior to v2.2 responds with a FRMR.
- * ( I think the KPC-3+ has a bug - it replys with DM - WB2OSZ )
+ * ( I think the KPC-3+ has a bug - it replies with DM - WB2OSZ )
*
* 4.3.3.5. Disconnected Mode (DM) Response
*
@@ -4636,7 +4650,7 @@ static void ua_frame (ax25_dlsm_t *S, int f)
if (f == 1) {
if (S->layer_3_initiated) {
text_color_set(DW_COLOR_INFO);
- // TODO: add via if apppropriate.
+ // TODO: add via if appropriate.
dw_printf ("Stream %d: Connected to %s. (%s)\n", S->stream_id, S->addrs[PEERCALL], S->state == state_5_awaiting_v22_connection ? "v2.2" : "v2.0");
// There is a subtle difference here between connect confirm and indication.
// connect *confirm* means "has been made"
@@ -5615,7 +5629,7 @@ static void clear_exception_conditions (ax25_dlsm_t *S)
*
* Other guy gets RR/RNR command P=1.
* Same action for either state 3 or 4.
- * Whether he has outstanding un-ack'ed sent I frames is irrelevent.
+ * Whether he has outstanding un-ack'ed sent I frames is irrelevant.
* He calls "enquiry response" which sends RR/RNR response F=1.
* (Read about detour 1 below and in enquiry_response.)
*
diff --git a/src/ax25_pad.c b/src/ax25_pad.c
index d8af765d..b5d47639 100644
--- a/src/ax25_pad.c
+++ b/src/ax25_pad.c
@@ -49,7 +49,7 @@
*
*
* APRS uses only UI frames.
- * Each starts with 2-10 addressses (14-70 octets):
+ * Each starts with 2-10 addresses (14-70 octets):
*
* * Destination Address (note: opposite order in printed format)
*
@@ -350,8 +350,9 @@ void ax25_delete (packet_t this_p)
* strict - True to enforce rules for packets sent over the air.
* False to be more lenient for packets from IGate server.
*
- * Messages from an IGate server can have longer
- * addresses after qAC. Up to 9 observed so far.
+ * Packets from an IGate server can have longer
+ * addresses after qAC. Up to 9 observed so far.
+ * The SSID can be 2 alphanumeric characters, not just 1 to 15.
*
* We can just truncate the name because we will only
* end up discarding it. TODO: check on this.
@@ -372,7 +373,7 @@ packet_t ax25_from_text (char *monitor, int strict)
/*
* Tearing it apart is destructive so make our own copy first.
*/
- char stuff[512];
+ char stuff[AX25_MAX_PACKET_LEN+1];
char *pinfo;
int ssid_temp, heard_temp;
@@ -511,6 +512,13 @@ packet_t ax25_from_text (char *monitor, int strict)
// printf ("DEBUG: get digi loop, num addr = %d, address = '%s'\n", k, pa);// FIXME
+ // Hack for q construct, from APRS-IS, so it does not cause panic later.
+
+ if ( ! strict && pa[0] == 'q' && pa[1] == 'A') {
+ pa[0] = 'Q';
+ pa[2] = toupper(pa[2]);
+ }
+
if ( ! ax25_parse_addr (k, pa, strict, atemp, &ssid_temp, &heard_temp)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Failed to create packet from text. Bad digipeater address\n");
@@ -733,6 +741,7 @@ packet_t ax25_dup (packet_t copy_from)
* alphanumeric characters for the SSID.
* We also get messages like this from a server.
* KB1POR>APU25N,TCPIP*,qAC,T2NUENGLD:...
+ * K1BOS-B>APOSB,TCPIP,WR2X-2*:...
*
* 2 (extra true) will complain if * is found at end.
*
@@ -940,7 +949,7 @@ int ax25_check_addresses (packet_t pp)
*
* Name: ax25_unwrap_third_party
*
- * Purpose: Unwrap a third party messge from the header.
+ * Purpose: Unwrap a third party message from the header.
*
* Inputs: copy_from - Existing packet object.
*
@@ -1717,6 +1726,19 @@ int ax25_get_info (packet_t this_p, unsigned char **paddr)
} /* end ax25_get_info */
+void ax25_set_info (packet_t this_p, unsigned char *new_info_ptr, int new_info_len)
+{
+ unsigned char *old_info_ptr;
+ int old_info_len = ax25_get_info (this_p, &old_info_ptr);
+ this_p->frame_len -= old_info_len;
+
+ if (new_info_len < 0) new_info_len = 0;
+ if (new_info_len > AX25_MAX_INFO_LEN) new_info_len = AX25_MAX_INFO_LEN;
+ memcpy (old_info_ptr, new_info_ptr, new_info_len);
+ this_p->frame_len += new_info_len;
+}
+
+
/*------------------------------------------------------------------------------
*
* Name: ax25_cut_at_crlf
@@ -1892,6 +1914,25 @@ void ax25_set_modulo (packet_t this_p, int modulo)
}
+/*------------------------------------------------------------------------------
+ *
+ * Name: ax25_get_modulo
+ *
+ * Purpose: Get modulo value for I and S frame sequence numbers.
+ *
+ * Returns: 8 or 128 if known.
+ * 0 if unknown.
+ *
+ *------------------------------------------------------------------------------*/
+
+int ax25_get_modulo (packet_t this_p)
+{
+ assert (this_p->magic1 == MAGIC);
+ assert (this_p->magic2 == MAGIC);
+
+ return (this_p->modulo);
+}
+
@@ -1944,6 +1985,7 @@ void ax25_format_addrs (packet_t this_p, char *result)
}
ax25_get_addr_with_ssid (this_p, AX25_SOURCE, stemp);
+ // FIXME: For ALL strcat: Pass in sizeof result and use strlcat.
strcat (result, stemp);
strcat (result, ">");
@@ -2598,6 +2640,15 @@ int ax25_get_frame_len (packet_t this_p)
} /* end ax25_get_frame_len */
+unsigned char *ax25_get_frame_data_ptr (packet_t this_p)
+{
+ assert (this_p->magic1 == MAGIC);
+ assert (this_p->magic2 == MAGIC);
+
+ return (this_p->frame_data);
+
+} /* end ax25_get_frame_data_ptr */
+
/*------------------------------------------------------------------------------
*
@@ -2710,6 +2761,7 @@ unsigned short ax25_m_m_crc (packet_t pp)
unsigned char fbuf[AX25_MAX_PACKET_LEN];
int flen;
+ // TODO: I think this can be more efficient by getting the packet content pointer instead of copying.
flen = ax25_pack (pp, fbuf);
crc = 0xffff;
@@ -2762,7 +2814,8 @@ unsigned short ax25_m_m_crc (packet_t pp)
*
*------------------------------------------------------------------*/
-#define MAXSAFE 500
+//#define MAXSAFE 500
+#define MAXSAFE AX25_MAX_INFO_LEN
void ax25_safe_print (char *pstr, int len, int ascii_only)
{
diff --git a/src/ax25_pad.h b/src/ax25_pad.h
index 22568f74..cdb84c65 100644
--- a/src/ax25_pad.h
+++ b/src/ax25_pad.h
@@ -11,7 +11,7 @@
#define AX25_MAX_REPEATERS 8
-#define AX25_MIN_ADDRS 2 /* Destinatin & Source. */
+#define AX25_MIN_ADDRS 2 /* Destination & Source. */
#define AX25_MAX_ADDRS 10 /* Destination, Source, 8 digipeaters. */
#define AX25_DESTINATION 0 /* Address positions in frame. */
@@ -98,7 +98,7 @@ struct packet_s {
*
* Bits: H R R SSID 0
*
- * H for digipeaters set to 0 intially.
+ * H for digipeaters set to 0 initially.
* Changed to 1 when position has been used.
*
* for source & destination it is called
@@ -397,6 +397,7 @@ extern int ax25_get_first_not_repeated(packet_t pp);
extern int ax25_get_rr (packet_t this_p, int n);
extern int ax25_get_info (packet_t pp, unsigned char **paddr);
+extern void ax25_set_info (packet_t pp, unsigned char *info_ptr, int info_len);
extern int ax25_cut_at_crlf (packet_t this_p);
extern void ax25_set_nextp (packet_t this_p, packet_t next_p);
@@ -409,6 +410,7 @@ extern void ax25_set_release_time (packet_t this_p, double release_time);
extern double ax25_get_release_time (packet_t this_p);
extern void ax25_set_modulo (packet_t this_p, int modulo);
+extern int ax25_get_modulo (packet_t this_p);
extern void ax25_format_addrs (packet_t pp, char *);
extern void ax25_format_via_path (packet_t this_p, char *result, size_t result_size);
@@ -428,6 +430,7 @@ extern int ax25_get_c2 (packet_t this_p);
extern int ax25_get_pid (packet_t this_p);
extern int ax25_get_frame_len (packet_t this_p);
+extern unsigned char *ax25_get_frame_data_ptr (packet_t this_p);
extern unsigned short ax25_dedupe_crc (packet_t pp);
diff --git a/src/ax25_pad2.c b/src/ax25_pad2.c
index efba8879..347df4b1 100644
--- a/src/ax25_pad2.c
+++ b/src/ax25_pad2.c
@@ -102,7 +102,9 @@
*
* RR note: It seems that some implementations put a hint
* in the "RR" reserved bits.
- * http://www.tapr.org/pipermail/ax25-layer2/2005-October/000297.html
+ * http://www.tapr.org/pipermail/ax25-layer2/2005-October/000297.html (now broken)
+ * https://elixir.bootlin.com/linux/latest/source/net/ax25/ax25_addr.c#L237
+ *
* The RR bits can also be used for "DAMA" which is
* some sort of channel access coordination scheme.
* http://internet.freepage.de/cgi-bin/feets/freepage_ext/41030x030A/rewrite/hennig/afu/afudoc/afudama.html
@@ -406,7 +408,7 @@ packet_t ax25_s_frame (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_ad
}
// Erratum: The AX.25 spec is not clear about whether SREJ should be command, response, or both.
- // The underlying X.25 spec clearly says it is reponse only. Let's go with that.
+ // The underlying X.25 spec clearly says it is response only. Let's go with that.
if (ftype == frame_type_S_SREJ && cr != cr_res) {
text_color_set(DW_COLOR_ERROR);
diff --git a/src/beacon.c b/src/beacon.c
index 3fea7142..4c78198f 100644
--- a/src/beacon.c
+++ b/src/beacon.c
@@ -163,8 +163,8 @@ void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct
if (chan < 0) chan = 0; /* For IGate, use channel 0 call. */
- if (g_modem_config_p->achan[chan].medium == MEDIUM_RADIO ||
- g_modem_config_p->achan[chan].medium == MEDIUM_NETTNC) {
+ if (g_modem_config_p->chan_medium[chan] == MEDIUM_RADIO ||
+ g_modem_config_p->chan_medium[chan] == MEDIUM_NETTNC) {
if (strlen(g_modem_config_p->achan[chan].mycall) > 0 &&
strcasecmp(g_modem_config_p->achan[chan].mycall, "N0CALL") != 0 &&
@@ -614,6 +614,21 @@ static void * beacon_thread (void *arg)
/* i.e. Don't take relative to now in case there was some delay. */
bp->next += bp->every;
+
+ // https://github.com/wb2osz/direwolf/pull/301
+ // https://github.com/wb2osz/direwolf/pull/301
+ // This happens with a portable system with no Internet connection.
+ // On reboot, the time is in the past.
+ // After time gets set from GPS, all beacons from that interval are sent.
+ // FIXME: This will surely break time slotted scheduling.
+
+ /* 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 */
+ if ( bp->next < now ) {
+ bp->next = now + bp->every;
+ text_color_set(DW_COLOR_INFO);
+ dw_printf("\nSystem clock appears to have jumped forward. Beacon schedule updated.\n\n");
+ }
}
} /* if time to send it */
@@ -804,7 +819,12 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo)
* src > dest [ , via ]
*/
- strlcpy (beacon_text, mycall, sizeof(beacon_text));
+ if (bp->source != NULL) {
+ strlcpy (beacon_text, bp->source, sizeof(beacon_text));
+ }
+ else {
+ strlcpy (beacon_text, mycall, sizeof(beacon_text));
+ }
strlcat (beacon_text, ">", sizeof(beacon_text));
if (bp->dest != NULL) {
diff --git a/src/cdigipeater.c b/src/cdigipeater.c
index edfa2ddc..06128b20 100644
--- a/src/cdigipeater.c
+++ b/src/cdigipeater.c
@@ -34,7 +34,7 @@
* are significantly different and I thought it would be
* too confusing to munge them together.
*
- * References: The Ax.25 protcol barely mentions digipeaters and
+ * References: The Ax.25 protocol barely mentions digipeaters and
* and doesn't describe how they should work.
*
*------------------------------------------------------------------*/
@@ -132,7 +132,7 @@ void cdigipeater (int from_chan, packet_t pp)
// Connected mode is allowed only for channels with internal modem.
// It probably wouldn't matter for digipeating but let's keep that rule simple and consistent.
- if ( from_chan < 0 || from_chan >= MAX_CHANS || save_audio_config_p->achan[from_chan].medium != MEDIUM_RADIO) {
+ if ( from_chan < 0 || from_chan >= MAX_CHANS || save_audio_config_p->chan_medium[from_chan] != MEDIUM_RADIO) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("cdigipeater: Did not expect to receive on invalid channel %d.\n", from_chan);
return;
@@ -255,7 +255,7 @@ static packet_t cdigipeat_match (int from_chan, packet_t pp, char *mycall_rec, c
* Originally this was the only one.
* Should we change it to AFILTER to make it clearer?
* CFILTER - Similar for connected moded digipeater.
- * IGFILTER - APRS-IS (IGate) server side - completely diffeent.
+ * IGFILTER - APRS-IS (IGate) server side - completely different.
* Confusing with similar name but much different idea.
* Maybe this should be renamed to SUBSCRIBE or something like that.
*
diff --git a/src/cm108.c b/src/cm108.c
index ad3b0b8f..8c8fc5ed 100644
--- a/src/cm108.c
+++ b/src/cm108.c
@@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
-// Copyright (C) 2017,2019 John Langner, WB2OSZ
+// Copyright (C) 2017,2019,2021 John Langner, WB2OSZ
//
// Parts of this were adapted from "hamlib" which contains the notice:
//
@@ -30,9 +30,10 @@
*
* Description:
*
- * There is an incresing demand for using the GPIO pins of USB audio devices for PTT.
+ * There is an increasing demand for using the GPIO pins of USB audio devices for PTT.
* We have a few commercial products:
*
+ * DINAH https://hamprojects.info/dinah/
* DMK URI http://www.dmkeng.com/URI_Order_Page.htm
* RB-USB RIM http://www.repeater-builder.com/products/usb-rim-lite.html
* RA-35 http://www.masterscommunications.com/products/radio-adapter/ra35.html
@@ -44,14 +45,15 @@
* http://www.repeater-builder.com/projects/fob/USB-Fob-Construction.pdf
* https://irongarment.wordpress.com/2011/03/29/cm108-compatible-chips-with-gpio/
*
- * Usually GPIO 3 is used because it is easier to tack solder a wire to a pin on the end.
+ * Homebrew plans all use GPIO 3 because it is easier to tack solder a wire to a pin on the end.
+ * All of the products, that I have seen, also use the same pin so this is the default.
*
* Soundmodem and hamlib paved the way but didn't get too far.
* Dire Wolf 1.3 added HAMLIB support (Linux only) which theoretically allows this in a
* painful roundabout way. This is documented in the User Guide, section called,
* "Hamlib PTT Example 2: Use GPIO of USB audio adapter. (e.g. DMK URI)"
*
- * It's rather involved and the explantion doesn't cover the case of multiple
+ * It's rather involved and the explanation doesn't cover the case of multiple
* USB-Audio adapters. It is not as straightforward as you might expect. Here we have
* an example of 3 C-Media USB adapters, a SignaLink USB, a keyboard, and a mouse.
*
@@ -87,23 +89,28 @@
* Dire Wolf version 1.5 makes this much more flexible and easier to use by supporting multiple
* sound devices and automatically determining the corresponding HID for the PTT signal.
*
+ * In version 1.7, we add a half-backed solution for Windows. It's fine for situations
+ * with a single USB Audio Adapter, but does not automatically handle the multiple device case.
+ * Manual configuration needs to be used in this case.
+ *
*---------------------------------------------------------------*/
+#include "direwolf.h"
+
#ifndef USE_CM108
#ifdef CM108_MAIN
-#include "direwolf.h"
+
#include "textcolor.h"
int main (void)
{
text_color_init (0); // Turn off text color.
#if defined(__OpenBSD__) || defined(__FreeBSD__)
- dw_printf ("CM108 PTT support is not available for BSD.\n");
+ dw_printf ("CM108 PTT support is not available for this operating system.\n");
#else
- dw_printf ("CM108 PTT support was disabled in Makefile.linux.\n");
- dw_printf ("It was excluded because /usr/include/libudev.h was missing.\n");
+ dw_printf ("CM108 PTT support was excluded because /usr/include/libudev.h was missing.\n");
dw_printf ("Install it with \"sudo apt-get install libudev-dev\" or\n");
dw_printf ("\"sudo yum install libudev-devel\" then rebuild.\n");
#endif
@@ -112,11 +119,8 @@ int main (void)
#endif
-#else // USE_CM108 is defined.
+#else // USE_CM108 is defined
-#include "direwolf.h"
-
-#include
#include
#include
#include
@@ -124,12 +128,18 @@ int main (void)
#include
#include
+#if __WIN32__
+#include
+#include "hidapi.h"
+#else
+#include
#include
#include
#include // ioctl, _IOR
#include
#include
#include // for HIDIOCGRAWINFO
+#endif
#include "textcolor.h"
#include "cm108.h"
@@ -216,6 +226,8 @@ static int cm108_write (char *name, int iomask, int iodata);
// Used to process regular expression matching results.
+#ifndef __WIN32__
+
static void substr_se (char *dest, const char *src, int start, int endp1)
{
int len = endp1 - start;
@@ -229,6 +241,7 @@ static void substr_se (char *dest, const char *src, int start, int endp1)
} /* end substr_se */
+#endif
/*
* Result of taking inventory of USB soundcards and USB HIDs.
@@ -237,15 +250,17 @@ static void substr_se (char *dest, const char *src, int start, int endp1)
struct thing_s {
int vid; // vendor id, displayed as four hexadecimal digits.
int pid; // product id, displayed as four hexadecimal digits.
- char card_number[8]; // Number. e.g. 2 for plughw:2,0
- char card_name[32]; // Name, assigned by system (e.g. Device_1) or by udev rule.
+ char card_number[8]; // "Card" Number. e.g. 2 for plughw:2,0
+ char card_name[32]; // Audio Card Name, assigned by system (e.g. Device_1) or by udev rule.
char product[32]; // product name (e.g. manufacturer, model)
char devnode_sound[22]; // e.g. /dev/snd/pcmC0D0p
char plughw[72]; // Above in more familiar format e.g. plughw:0,0
// Oversized to silence a compiler warning.
char plughw2[72]; // With name rather than number.
char devpath[128]; // Kernel dev path. Does not include /sys mount point.
- char devnode_hidraw[17]; // e.g. /dev/hidraw3
+ char devnode_hidraw[128]; // e.g. /dev/hidraw3 - for Linux - was length 17
+ // The Windows path for a HID looks like this, lengths up to 95 seen.
+ // \\?\hid#vid_0d8c&pid_000c&mi_03#8&164d11c9&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
char devnode_usb[25]; // e.g. /dev/bus/usb/001/012
// This is what we use to match up audio and HID.
};
@@ -259,6 +274,13 @@ int cm108_inventory (struct thing_s *things, int max_things);
*
* Purpose: Useful utility to list USB audio and HID devices.
*
+ * Optional command line arguments:
+ *
+ * HID path
+ * GPIO number (default 3)
+ *
+ * When specified the pin will be set high and low until interrupted.
+ *
*------------------------------------------------------------------*/
//#define EXTRA 1
@@ -267,18 +289,111 @@ int cm108_inventory (struct thing_s *things, int max_things);
#ifdef CM108_MAIN
-int main (void)
+static void usage(void)
+{
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("\n");
+ dw_printf ("Usage: cm108 [ device-path [ gpio-num ] ]\n");
+ dw_printf ("\n");
+ dw_printf ("With no command line arguments, this will produce a list of\n");
+#if __WIN32__
+ dw_printf ("Human Interface Devices (HID) and indicate which ones can be\n");
+ dw_printf ("used for GPIO PTT.\n");
+#else
+ dw_printf ("Audio devices and Human Interface Devices (HID) and indicate\n");
+ dw_printf ("which ones can be used for GPIO PTT.\n");
+#endif
+ dw_printf ("\n");
+ dw_printf ("Specify the HID device path to test the PTT function.\n");
+ dw_printf ("Its state should change once per second.\n");
+#if __WIN32__
+ dw_printf ("You might need to quote the path depending on the command processor.\n");
+#endif
+ dw_printf ("GPIO 3 is the default. A different number can be optionally specified.\n");
+ exit (EXIT_FAILURE);
+}
+
+int main (int argc, char **argv)
{
struct thing_s things[MAXX_THINGS];
int num_things;
int i;
text_color_init (0); // Turn off text color.
+ text_color_set(DW_COLOR_INFO);
+
+ if (argc >=2) {
+ char path[128];
+ strlcpy(path, argv[1], sizeof(path));
+ int gpio = 3;
+ if (argc >= 3) {
+ gpio = atoi(argv[2]);
+ }
+ if (gpio < 1 || gpio > 8) {
+ dw_printf ("GPIO number must be in range of 1 - 8.\n");
+ usage();
+ exit (EXIT_FAILURE);
+ }
+ int state = 0;
+ while (1) {
+ dw_printf ("%d", state);
+ fflush (stdout);
+ int err = cm108_set_gpio_pin (path, gpio, state);
+ if (err != 0) {
+ dw_printf ("\nWRITE ERROR for USB Audio Adapter GPIO!\n");
+ usage();
+ exit (EXIT_FAILURE);
+ }
+ SLEEP_SEC(1);
+ state = ! state;
+ }
+ }
+
// Take inventory of USB Audio adapters and other HID devices.
num_things = cm108_inventory (things, MAXX_THINGS);
+#if __WIN32__
+
+/////////////////////////////////////////////////////
+// Windows - Remove the sound related columns for now.
+/////////////////////////////////////////////////////
+
+ dw_printf (" VID PID %-*s %-*s"
+ "\n", (int)sizeof(things[0].product), "Product",
+ 17, "HID [ptt]"
+ );
+
+ dw_printf (" --- --- %-*s %-*s"
+
+ "\n", (int)sizeof(things[0].product), "-------",
+ 17, "---------"
+ );
+ for (i = 0; i < num_things; i++) {
+
+ dw_printf ("%2s %04x %04x %-*s %s"
+
+ "\n",
+ GOOD_DEVICE(things[i].vid,things[i].pid) ? "**" : " ",
+ things[i].vid, things[i].pid,
+ (int)sizeof(things[i].product), things[i].product,
+ things[i].devnode_hidraw
+ );
+ }
+ dw_printf ("\n");
+ dw_printf ("** = Can use Audio Adapter GPIO for PTT.\n");
+ dw_printf ("\n");
+
+ // T.B.D. - additional text ???
+
+#else
+
+/////////////////////////////////////////////
+// Linux
+/////////////////////////////////////////////
+
+
dw_printf (" VID PID %-*s %-*s %-*s %-*s %-*s"
#if EXTRA
" %-*s"
@@ -287,7 +402,7 @@ int main (void)
(int)sizeof(things[0].devnode_sound), "Sound",
(int)sizeof(things[0].plughw)/5, "ADEVICE",
(int)sizeof(things[0].plughw2)/4, "ADEVICE",
- (int)sizeof(things[0].devnode_hidraw), "HID [ptt]"
+ 17, "HID [ptt]"
#if EXTRA
, (int)sizeof(things[0].devnode_usb), "USB"
#endif
@@ -301,14 +416,14 @@ int main (void)
(int)sizeof(things[0].devnode_sound), "-----",
(int)sizeof(things[0].plughw)/5, "-------",
(int)sizeof(things[0].plughw2)/4, "-------",
- (int)sizeof(things[0].devnode_hidraw), "---------"
+ 17, "---------"
#if EXTRA
, (int)sizeof(things[0].devnode_usb), "---"
#endif
);
for (i = 0; i < num_things; i++) {
- dw_printf ("%2s %04x %04x %-*s %-*s %-*s %-*s %-*s"
+ dw_printf ("%2s %04x %04x %-*s %-*s %-*s %-*s %s"
#if EXTRA
" %-*s"
#endif
@@ -319,22 +434,22 @@ int main (void)
(int)sizeof(things[i].devnode_sound), things[i].devnode_sound,
(int)sizeof(things[0].plughw)/5, things[i].plughw,
(int)sizeof(things[0].plughw2)/4, things[i].plughw2,
- (int)sizeof(things[i].devnode_hidraw), things[i].devnode_hidraw
+ things[i].devnode_hidraw
#if EXTRA
, (int)sizeof(things[i].devnode_usb), things[i].devnode_usb
#endif
);
//dw_printf (" %-*s\n", (int)sizeof(things[i].devpath), things[i].devpath);
}
+ dw_printf ("\n");
+ dw_printf ("** = Can use Audio Adapter GPIO for PTT.\n");
+ dw_printf ("\n");
- static const char *suggested_names[] = {"Fred", "Wilma", "Pebbles", "Dino", "Barney", "Betty", "Bamm_Bamm" };
+ static const char *suggested_names[] = {"Fred", "Wilma", "Pebbles", "Dino", "Barney", "Betty", "Bamm_Bamm", "Chip", "Roxy" };
int iname = 0;
// From example in https://alsa.opensrc.org/Udev
- dw_printf ("\n");
- dw_printf ("** = Can use Audio Adapter GPIO for PTT.\n");
- dw_printf ("\n");
dw_printf ("Notice that each USB Audio adapter is assigned a number and a name. These are not predictable so you could\n");
dw_printf ("end up using the wrong adapter after adding or removing other USB devices or after rebooting. You can assign a\n");
dw_printf ("name to each USB adapter so you can refer to the same one each time. This can be based on any characteristics\n");
@@ -372,7 +487,7 @@ int main (void)
}
dw_printf ("LABEL=\"my_usb_audio_end\"\n");
dw_printf ("\n");
-
+#endif
return (0);
}
@@ -400,6 +515,47 @@ int main (void)
int cm108_inventory (struct thing_s *things, int max_things)
{
+ int num_things = 0;
+ memset (things, 0, sizeof(struct thing_s) * max_things);
+
+#if __WIN32__
+
+ struct hid_device_info *devs, *cur_dev;
+
+ if (hid_init()) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("cm108_inventory: hid_init() failed.\n");
+ return (-1);
+ }
+
+ devs = hid_enumerate(0x0, 0x0);
+ cur_dev = devs;
+ while (cur_dev) {
+#if 0
+ printf("Device Found\n type: %04hx %04hx\n path: %s\n serial_number: %ls", cur_dev->vendor_id, cur_dev->product_id, cur_dev->path, cur_dev->serial_number);
+ printf("\n");
+ printf(" Manufacturer: %ls\n", cur_dev->manufacturer_string);
+ printf(" Product: %ls\n", cur_dev->product_string);
+ printf(" Release: %hx\n", cur_dev->release_number);
+ printf(" Interface: %d\n", cur_dev->interface_number);
+ printf(" Usage (page): 0x%hx (0x%hx)\n", cur_dev->usage, cur_dev->usage_page);
+ printf("\n");
+#endif
+ if (num_things < max_things && cur_dev->vendor_id != 0x051d) { // FIXME - remove exception
+ things[num_things].vid = cur_dev->vendor_id;
+ things[num_things].pid = cur_dev->product_id;
+ wcstombs (things[num_things].product, cur_dev->product_string, sizeof(things[num_things].product));
+ things[num_things].product[sizeof(things[num_things].product) - 1] = '\0';
+ strlcpy (things[num_things].devnode_hidraw, cur_dev->path, sizeof(things[num_things].devnode_hidraw));
+
+ num_things++;
+ }
+ cur_dev = cur_dev->next;
+ }
+ hid_free_enumeration(devs);
+
+#else // Linux, with udev
+
struct udev *udev;
struct udev_enumerate *enumerate;
struct udev_list_entry *devices, *dev_list_entry;
@@ -410,8 +566,6 @@ int cm108_inventory (struct thing_s *things, int max_things)
char const *pattrs_number = NULL;
char card_devpath[128] = "";
- int num_things = 0;
- memset (things, 0, sizeof(struct thing_s) * max_things);
/*
* First get a list of the USB audio devices.
@@ -561,6 +715,8 @@ int cm108_inventory (struct thing_s *things, int max_things)
}
}
+#endif // end Linux
+
return (num_things);
} /* end cm108_inventory */
@@ -584,7 +740,7 @@ int cm108_inventory (struct thing_s *things, int max_things)
* ptt_device_size - Size of result area to avoid buffer overflow.
*
* Outputs: ptt_device - Device name, something like /dev/hidraw2.
- * Will be emptry string if no match found.
+ * Will be empty string if no match found.
*
* Returns: none
*
@@ -594,13 +750,48 @@ void cm108_find_ptt (char *output_audio_device, char *ptt_device, int ptt_devic
{
struct thing_s things[MAXX_THINGS];
int num_things;
- int i;
//dw_printf ("DEBUG: cm108_find_ptt('%s')\n", output_audio_device);
strlcpy (ptt_device, "", ptt_device_size);
+
+ // Possible improvement: Skip if inventory already taken.
num_things = cm108_inventory (things, MAXX_THINGS);
+#if __WIN32__
+ // FIXME - This is just a half baked implementation.
+ // I have not been able to figure out how to find the connection
+ // between the audio device and HID in the same package.
+ // This is fine for a single USB Audio Adapter, good enough for most people.
+ // Those with multiple devices will need to manually configure PTT device path.
+
+ // Count how many good devices we have.
+
+ int good_devices = 0;
+
+ for (int i = 0; i < num_things; i++) {
+ if (GOOD_DEVICE(things[i].vid,things[i].pid) ) {
+ good_devices++;
+ //dw_printf ("DEBUG: success! returning '%s'\n", things[i].devnode_hidraw);
+ strlcpy (ptt_device, things[i].devnode_hidraw, ptt_device_size);
+ }
+ }
+
+ if (good_devices == 0) return; // None found - caller will print a message.
+
+ if (good_devices == 1) return; // Success - Only one candidate device.
+
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("There are multiple USB Audio Devices with GPIO capability.\n");
+ dw_printf ("Explicitly specify one of them for more predictable results:\n");
+ for (int i = 0; i < num_things; i++) {
+ if (GOOD_DEVICE(things[i].vid,things[i].pid) ) {
+ dw_printf (" \"%s\"\n", things[i].devnode_hidraw);
+ }
+ }
+ dw_printf ("Run the \"cm108\" utility for more details.\n");
+ text_color_set(DW_COLOR_INFO);
+#else
regex_t sound_re;
char emsg[100];
int e = regcomp (&sound_re, ".+:(CARD=)?([A-Za-z0-9_]+)(,.*)?", REG_EXTENDED);
@@ -625,7 +816,7 @@ void cm108_find_ptt (char *output_audio_device, char *ptt_device, int ptt_devic
return;
}
- for (i = 0; i < num_things; i++) {
+ for (int i = 0; i < num_things; i++) {
//dw_printf ("DEBUG: i=%d, card_name='%s', card_number='%s'\n", i, things[i].card_name, things[i].card_number);
if (strcmp(num_or_name,things[i].card_name) == 0 || strcmp(num_or_name,things[i].card_number) == 0) {
//dw_printf ("DEBUG: success! returning '%s'\n", things[i].devnode_hidraw);
@@ -638,6 +829,7 @@ void cm108_find_ptt (char *output_audio_device, char *ptt_device, int ptt_devic
return;
}
}
+#endif
} /* end cm108_find_ptt */
@@ -649,7 +841,8 @@ void cm108_find_ptt (char *output_audio_device, char *ptt_device, int ptt_devic
*
* Purpose: Set one GPIO pin of the CM108 or similar.
*
- * Inputs: name - Name of device such as /dev/hidraw2.
+ * Inputs: name - Name of device such as /dev/hidraw2 or
+ * \\?\hid#vid_0d8c&pid_0008&mi_03#8&39d3555&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
*
* num - GPIO number, range 1 thru 8.
*
@@ -661,7 +854,7 @@ void cm108_find_ptt (char *output_audio_device, char *ptt_device, int ptt_devic
*
* Future: For our initial implementation we are making the simplifying
* restriction of using only one GPIO pin per device and limit
- * configuratin to PTT only.
+ * configuration to PTT only.
* Longer term, we might want to have DCD, and maybe other
* controls thru the same chip.
* In this case, we would need to retain bit masks for each
@@ -669,44 +862,6 @@ void cm108_find_ptt (char *output_audio_device, char *ptt_device, int ptt_devic
*
*------------------------------------------------------------------*/
-#if TESTCM
-
-// Switch pin between input, output-low, and output-high.
-
-// gcc -DTESTCM=1 -DUSE_CM108 cm108.c textcolor.c misc.a -ludev
-
-int main (int argc, char *argv[])
-{
-#define MODE_IN 0
-#define MODE_OUT 0x04 // GPIO 3 = bit 2
-#define OUT_LOW 0
-#define OUT_HIGH 0x04
-
- if (argc != 2) {
- text_color_set(DW_COLOR_ERROR);
- dw_printf ("Specify HID path on command line.\n");
- exit (1);
- }
-
- while (1) {
- text_color_set(DW_COLOR_INFO);
- dw_printf ("Input-L\n");
- cm108_write (argv[1], MODE_IN, OUT_LOW);
- sleep(5);
- dw_printf ("Input-H\n");
- cm108_write (argv[1], MODE_IN, OUT_HIGH);
- sleep(5);
- dw_printf ("Out-LOW\n");
- cm108_write (argv[1], MODE_OUT, OUT_LOW);
- sleep(5);
- dw_printf ("out-HIGH\n");
- cm108_write (argv[1], MODE_OUT, OUT_HIGH);
- sleep(5);
- }
-}
-
-#endif
-
int cm108_set_gpio_pin (char *name, int num, int state)
{
@@ -758,6 +913,36 @@ int cm108_set_gpio_pin (char *name, int num, int state)
static int cm108_write (char *name, int iomask, int iodata)
{
+
+#if __WIN32__
+
+ //text_color_set(DW_COLOR_DEBUG);
+ //dw_printf ("TEMP DEBUG cm108_write: %s %d %d\n", name, iomask, iodata);
+
+ hid_device *handle = hid_open_path(name);
+ if (handle == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Could not open %s for write\n", name);
+ return (-1);
+ }
+
+ unsigned char io[5];
+ io[0] = 0;
+ io[1] = 0;
+ io[2] = iodata;
+ io[3] = iomask;
+ io[4] = 0;
+
+ int res = hid_write(handle, io, sizeof(io));
+ if (res < 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Write failed to %s\n", name);
+ return (-1);
+ }
+
+ hid_close(handle);
+
+#else
int fd;
struct hidraw_devinfo info;
char io[5];
@@ -862,6 +1047,8 @@ static int cm108_write (char *name, int iomask, int iodata)
}
close (fd);
+
+#endif
return (0);
} /* end cm108_write */
diff --git a/src/config.c b/src/config.c
index 8588a8cc..194d96a7 100644
--- a/src/config.c
+++ b/src/config.c
@@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
-// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 John Langner, WB2OSZ
+// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2021 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -63,7 +63,7 @@
#include "tt_text.h"
#include "ax25_link.h"
-#ifdef USE_CM108 // Linux only
+#if USE_CM108 // Current Linux or Windows only
#include "cm108.h"
#endif
@@ -317,7 +317,7 @@ static double parse_ll (char *str, enum parse_ll_which_e which, int line)
* - Negative zone for south.
* - Separate North or South.
*
- * I'm using the first alternatve.
+ * I'm using the first alternative.
* GEOTRANS uses the third.
* We will also recognize the second one but I'm not sure if I want to document it.
*
@@ -585,7 +585,7 @@ static int check_via_path (char *via_path)
*
*--------------------------------------------------------------------*/
-#define MAXCMDLEN 256
+#define MAXCMDLEN 1200
static char *split (char *string, int rest_of_line)
@@ -736,6 +736,8 @@ void config_init (char *fname, struct audio_s *p_audio_config,
memset (p_audio_config, 0, sizeof(struct audio_s));
+ p_audio_config->igate_vchannel = -1; // none.
+
/* First audio device is always available with defaults. */
/* Others must be explicitly defined before use. */
@@ -755,7 +757,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
for (channel=0; channelachan[channel].medium = MEDIUM_NONE; /* One or both channels will be */
+ p_audio_config->chan_medium[channel] = MEDIUM_NONE; /* One or both channels will be */
/* set to radio when corresponding */
/* audio device is defined. */
p_audio_config->achan[channel].modem_type = MODEM_AFSK;
@@ -770,6 +772,10 @@ void config_init (char *fname, struct audio_s *p_audio_config,
p_audio_config->achan[channel].num_freq = 1;
p_audio_config->achan[channel].offset = 0;
+ p_audio_config->achan[channel].layer2_xmit = LAYER2_AX25;
+ p_audio_config->achan[channel].il2p_max_fec = 1;
+ p_audio_config->achan[channel].il2p_invert_polarity = 0;
+
p_audio_config->achan[channel].fix_bits = DEFAULT_FIX_BITS;
p_audio_config->achan[channel].sanity_test = SANITY_APRS;
p_audio_config->achan[channel].passall = 0;
@@ -804,7 +810,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
/* First channel should always be valid. */
/* If there is no ADEVICE, it uses default device in mono. */
- p_audio_config->achan[0].medium = MEDIUM_RADIO;
+ p_audio_config->chan_medium[0] = MEDIUM_RADIO;
memset (p_digi_config, 0, sizeof(struct digi_config_s)); // APRS digipeater
p_digi_config->dedupe_time = DEFAULT_DEDUPE;
@@ -852,10 +858,19 @@ void config_init (char *fname, struct audio_s *p_audio_config,
memset (p_misc_config, 0, sizeof(struct misc_config_s));
p_misc_config->agwpe_port = DEFAULT_AGWPE_PORT;
- p_misc_config->kiss_port = DEFAULT_KISS_PORT;
+
+ for (int i=0; ikiss_port[i] = 0; // entry not used.
+ p_misc_config->kiss_chan[i] = -1;
+ }
+ p_misc_config->kiss_port[0] = DEFAULT_KISS_PORT;
+ p_misc_config->kiss_chan[0] = -1; // all channels.
+
p_misc_config->enable_kiss_pt = 0; /* -p option */
p_misc_config->kiss_copy = 0;
+ p_misc_config->dns_sd_enabled = 1;
+
/* Defaults from http://info.aprs.net/index.php?title=SmartBeaconing */
p_misc_config->sb_configured = 0; /* TRUE if SmartBeaconing is configured. */
@@ -1018,7 +1033,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
p_audio_config->adev[adevice].defined = 1;
/* First channel of device is valid. */
- p_audio_config->achan[ADEVFIRSTCHAN(adevice)].medium = MEDIUM_RADIO;
+ 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));
@@ -1073,7 +1088,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
p_audio_config->adev[adevice].defined = 1;
/* First channel of device is valid. */
- p_audio_config->achan[ADEVFIRSTCHAN(adevice)].medium = MEDIUM_RADIO;
+ 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));
}
@@ -1100,7 +1115,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
p_audio_config->adev[adevice].defined = 1;
/* First channel of device is valid. */
- p_audio_config->achan[ADEVFIRSTCHAN(adevice)].medium = MEDIUM_RADIO;
+ p_audio_config->chan_medium[ADEVFIRSTCHAN(adevice)] = MEDIUM_RADIO;
strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out));
}
@@ -1147,9 +1162,9 @@ void config_init (char *fname, struct audio_s *p_audio_config,
/* Set valid channels depending on mono or stereo. */
- p_audio_config->achan[ADEVFIRSTCHAN(adevice)].medium = MEDIUM_RADIO;
+ p_audio_config->chan_medium[ADEVFIRSTCHAN(adevice)] = MEDIUM_RADIO;
if (n == 2) {
- p_audio_config->achan[ADEVFIRSTCHAN(adevice) + 1].medium = MEDIUM_RADIO;
+ p_audio_config->chan_medium[ADEVFIRSTCHAN(adevice) + 1] = MEDIUM_RADIO;
}
}
else {
@@ -1163,7 +1178,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
*/
/*
- * CHANNEL - Set channel for following commands.
+ * CHANNEL n - Set channel for channel-specific commands.
*/
else if (strcasecmp(t, "CHANNEL") == 0) {
@@ -1179,7 +1194,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
channel = n;
- if (p_audio_config->achan[n].medium != MEDIUM_RADIO) {
+ if (p_audio_config->chan_medium[n] != MEDIUM_RADIO) {
if ( ! p_audio_config->adev[ACHAN2ADEV(n)].defined) {
text_color_set(DW_COLOR_ERROR);
@@ -1199,6 +1214,44 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
}
+/*
+ * ICHANNEL n - Define IGate virtual channel.
+ *
+ * This allows a client application to talk to to APRS-IS
+ * by using a channel number outside the normal range for modems.
+ * In the future there might be other typs of virtual channels.
+ * This does not change the current channel number used by MODEM, PTT, etc.
+ */
+
+ else if (strcasecmp(t, "ICHANNEL") == 0) {
+ t = split(NULL,0);
+ if (t == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Missing virtual channel number for ICHANNEL command.\n", line);
+ continue;
+ }
+ int ichan = atoi(t);
+ if (ichan >= MAX_CHANS && ichan < MAX_TOTAL_CHANS) {
+
+ if (p_audio_config->chan_medium[ichan] == MEDIUM_NONE) {
+
+ p_audio_config->chan_medium[ichan] = MEDIUM_IGATE;
+
+ // This is redundant but saves the time of searching through all
+ // the channels for each packet.
+ p_audio_config->igate_vchannel = ichan;
+ }
+ else {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: ICHANNEL can't use %d because it is already in use.\n", line, ichan);
+ }
+ }
+ else {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: ICHANNEL number must in range of %d to %d.\n", line, MAX_CHANS, MAX_TOTAL_CHANS-1);
+ }
+ }
+
/*
* MYCALL station
*/
@@ -1337,7 +1390,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
// Will make more precise in afsk demod init.
p_audio_config->achan[channel].mark_freq = 2083; // Actually 2083.3 - logic 1.
p_audio_config->achan[channel].space_freq = 1563; // Actually 1562.5 - logic 0.
- // ? strlcpy (p_audio_config->achan[channel].profiles, "D", sizeof(p_audio_config->achan[channel].profiles));
+ // ? strlcpy (p_audio_config->achan[channel].profiles, "A", sizeof(p_audio_config->achan[channel].profiles));
}
else {
p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE;
@@ -1641,6 +1694,9 @@ void config_init (char *fname, struct audio_s *p_audio_config,
text_color_set(DW_COLOR_INFO);
dw_printf ("Line %d: Using a FIX_BITS value greater than %d is not recommended for normal operation.\n",
line, DEFAULT_FIX_BITS);
+ dw_printf ("FIX_BITS > 1 was an interesting experiment but turned out to be a bad idea.\n");
+ dw_printf ("Don't be surprised if it takes 100%% CPU, direwolf can't keep up with the audio stream,\n");
+ dw_printf ("and you see messages like \"Audio input device 0 error code -32: Broken pipe\"\n");
}
t = split(NULL,0);
@@ -1808,7 +1864,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
strlcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, t, sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device));
- // Optional serial port rate for CAT controll PTT.
+ // Optional serial port rate for CAT control PTT.
t = split(NULL,0);
if (t != NULL) {
@@ -1845,9 +1901,9 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
else if (strcasecmp(t, "CM108") == 0) {
-/* CM108 - GPIO of USB sound card. case, Linux only. */
+/* CM108 - GPIO of USB sound card. case, Linux and Windows only. */
-#ifdef USE_CM108
+#if USE_CM108
if (ot != OCTYPE_PTT) {
// Future project: Allow DCD and CON via the same device.
@@ -1884,6 +1940,16 @@ void config_init (char *fname, struct audio_s *p_audio_config,
p_audio_config->achan[channel].octrl[ot].out_gpio_num = atoi(t);
p_audio_config->achan[channel].octrl[ot].ptt_invert = 0;
}
+#if __WIN32__
+ else if (*t == '\\') {
+ strlcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, t, sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device));
+ }
+ else {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Config file line %d: Found \"%s\" when expecting GPIO number or device name like \\\\?\\hid#vid_0d8c&... .\n", line, t);
+ continue;
+ }
+#else
else if (*t == '/') {
strlcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, t, sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device));
}
@@ -1892,6 +1958,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
dw_printf ("Config file line %d: Found \"%s\" when expecting GPIO number or device name like /dev/hidraw1.\n", line, t);
continue;
}
+#endif
}
if (p_audio_config->achan[channel].octrl[ot].out_gpio_num < 1 || p_audio_config->achan[channel].octrl[ot].out_gpio_num > 8) {
text_color_set(DW_COLOR_ERROR);
@@ -1903,22 +1970,23 @@ void config_init (char *fname, struct audio_s *p_audio_config,
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: Could not determine USB Audio GPIO PTT device for audio output %s.\n", line,
p_audio_config->adev[ACHAN2ADEV(channel)].adevice_out);
+#if __WIN32__
+ dw_printf ("You must explicitly mention a HID path.\n");
+#else
dw_printf ("You must explicitly mention a device name such as /dev/hidraw1.\n");
- dw_printf ("See User Guide for details.\n");
+#endif
+ dw_printf ("Run \"cm108\" utility to get a list.\n");
+ dw_printf ("See Interface Guide for details.\n");
continue;
}
p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_CM108;
-#else
-#if __WIN32__
- text_color_set(DW_COLOR_ERROR);
- dw_printf ("Config file line %d: CM108 USB Audio GPIO PTT is not available for Windows.\n", line);
#else
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file line %d: %s with CM108 is only available when USB Audio GPIO support is enabled.\n", line, otname);
dw_printf ("You must rebuild direwolf with CM108 Audio Adapter GPIO PTT support.\n");
- dw_printf ("See User Guide for details.\n");
-#endif
+ dw_printf ("See Interface Guide for details.\n");
+
exit (EXIT_FAILURE);
#endif
}
@@ -2231,7 +2299,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
* 0 = off, 1 = auto mode, others are suggestions for testing
* or special cases. 16, 32, 64 is number of parity bytes to add.
* Also set by "-X n" command line option.
- * Current a global setting. Could be per channel someday.
+ * V1.7 changed from global to per-channel setting.
*/
else if (strcasecmp(t, "FX25TX") == 0) {
@@ -2244,13 +2312,15 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
n = atoi(t);
if (n >= 0 && n < 200) {
- p_audio_config->fx25_xmit_enable = n;
+ p_audio_config->achan[channel].fx25_strength = n;
+ p_audio_config->achan[channel].layer2_xmit = LAYER2_FX25;
}
else {
- p_audio_config->fx25_xmit_enable = 1;
+ p_audio_config->achan[channel].fx25_strength = 1;
+ p_audio_config->achan[channel].layer2_xmit = LAYER2_FX25;
text_color_set(DW_COLOR_ERROR);
dw_printf ("Line %d: Unreasonable value for FX.25 transmission mode. Using %d.\n",
- line, p_audio_config->fx25_xmit_enable);
+ line, p_audio_config->achan[channel].fx25_strength);
}
}
@@ -2283,12 +2353,57 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
}
+/*
+ * IL2PTX [ + - ] [ 0 1 ] - Enable IL2P transmission. Default off.
+ * "+" means normal polarity. Redundant since it is the default.
+ * (command line -I for first channel)
+ * "-" means inverted polarity. Do not use for 1200 bps.
+ * (command line -i for first channel)
+ * "0" means weak FEC. Not recommended.
+ * "1" means stronger FEC. "Max FEC." Default if not specified.
+ */
+
+ else if (strcasecmp(t, "IL2PTX") == 0) {
+
+ p_audio_config->achan[channel].layer2_xmit = LAYER2_IL2P;
+ p_audio_config->achan[channel].il2p_max_fec = 1;
+ p_audio_config->achan[channel].il2p_invert_polarity = 0;
+
+ while ((t = split(NULL,0)) != NULL) {
+ for (char *c = t; *t != '\0'; c++) {
+ switch (*c) {
+ case '+':
+ p_audio_config->achan[channel].il2p_invert_polarity = 0;
+ break;
+ case '-':
+ p_audio_config->achan[channel].il2p_invert_polarity = 1;
+ break;
+ case '0':
+ p_audio_config->achan[channel].il2p_max_fec = 0;
+ break;
+ case '1':
+ p_audio_config->achan[channel].il2p_max_fec = 1;
+ break;
+ default:
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Invalid parameter '%c' fol IL2PTX command.\n", line, *c);
+ continue;
+ break;
+ }
+ }
+ }
+ }
+
+
/*
* ==================== APRS Digipeater parameters ====================
*/
/*
- * DIGIPEAT from-chan to-chan alias-pattern wide-pattern [ OFF|DROP|MARK|TRACE ]
+ * DIGIPEAT from-chan to-chan alias-pattern wide-pattern [ OFF|DROP|MARK|TRACE | ATGP=alias ]
+ *
+ * ATGP is an ugly hack for the specific need of ATGP which needs more that 8 digipeaters.
+ * DO NOT put this in the User Guide. On a need to know basis.
*/
else if (strcasecmp(t, "DIGIPEAT") == 0 || strcasecmp(t, "DIGIPEATER") == 0) {
@@ -2319,8 +2434,8 @@ void config_init (char *fname, struct audio_s *p_audio_config,
// Channels specified must be radio channels or network TNCs.
- if (p_audio_config->achan[from_chan].medium != MEDIUM_RADIO &&
- p_audio_config->achan[from_chan].medium != MEDIUM_NETTNC) {
+ if (p_audio_config->chan_medium[from_chan] != MEDIUM_RADIO &&
+ p_audio_config->chan_medium[from_chan] != MEDIUM_NETTNC) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n",
line, from_chan);
@@ -2347,8 +2462,8 @@ void config_init (char *fname, struct audio_s *p_audio_config,
continue;
}
- if (p_audio_config->achan[to_chan].medium != MEDIUM_RADIO &&
- p_audio_config->achan[to_chan].medium != MEDIUM_NETTNC) {
+ if (p_audio_config->chan_medium[to_chan] != MEDIUM_RADIO &&
+ p_audio_config->chan_medium[to_chan] != MEDIUM_NETTNC) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: TO-channel %d is not valid.\n",
line, to_chan);
@@ -2414,6 +2529,10 @@ void config_init (char *fname, struct audio_s *p_audio_config,
p_digi_config->preempt[from_chan][to_chan] = PREEMPT_TRACE;
t = split(NULL,0);
}
+ else if (strncasecmp(t, "ATGP=", 5) == 0) {
+ strlcpy (p_digi_config->atgp[from_chan][to_chan], t+5, sizeof(p_digi_config->atgp[from_chan][to_chan]));;
+ t = split(NULL,0);
+ }
}
if (t != NULL) {
@@ -2476,7 +2595,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
// Only radio channels are valid for regenerate.
- if (p_audio_config->achan[from_chan].medium != MEDIUM_RADIO) {
+ if (p_audio_config->chan_medium[from_chan] != MEDIUM_RADIO) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n",
line, from_chan);
@@ -2502,7 +2621,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
MAX_CHANS-1, line);
continue;
}
- if (p_audio_config->achan[to_chan].medium != MEDIUM_RADIO) {
+ if (p_audio_config->chan_medium[to_chan] != MEDIUM_RADIO) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: TO-channel %d is not valid.\n",
line, to_chan);
@@ -2553,7 +2672,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
// There is discussion about this in the document called
// Why-is-9600-only-twice-as-fast-as-1200.pdf
- if (p_audio_config->achan[from_chan].medium != MEDIUM_RADIO) {
+ if (p_audio_config->chan_medium[from_chan] != MEDIUM_RADIO) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n",
line, from_chan);
@@ -2580,7 +2699,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
MAX_CHANS-1, line);
continue;
}
- if (p_audio_config->achan[to_chan].medium != MEDIUM_RADIO) {
+ if (p_audio_config->chan_medium[to_chan] != MEDIUM_RADIO) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: TO-channel %d is not valid.\n",
line, to_chan);
@@ -2642,11 +2761,11 @@ void config_init (char *fname, struct audio_s *p_audio_config,
* There is discussion about this in the document called
* Why-is-9600-only-twice-as-fast-as-1200.pdf
*
- * IGFILTER - APRS-IS (IGate) server side - completely diffeent.
+ * IGFILTER - APRS-IS (IGate) server side - completely different.
* I'm not happy with this name because IG sounds like IGate
* which is really the client side. More comments later.
* Maybe it should be called subscribe or something like that
- * because the subscriptions are cummulative.
+ * because the subscriptions are cumulative.
*/
else if (strcasecmp(t, "FILTER") == 0) {
@@ -2671,14 +2790,14 @@ void config_init (char *fname, struct audio_s *p_audio_config,
continue;
}
- if (p_audio_config->achan[from_chan].medium != MEDIUM_RADIO &&
- p_audio_config->achan[from_chan].medium != MEDIUM_NETTNC) {
+ if (p_audio_config->chan_medium[from_chan] != MEDIUM_RADIO &&
+ p_audio_config->chan_medium[from_chan] != MEDIUM_NETTNC) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n",
line, from_chan);
continue;
}
- if (p_audio_config->achan[from_chan].medium == MEDIUM_IGATE) {
+ if (p_audio_config->chan_medium[from_chan] == MEDIUM_IGATE) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Use 'IG' rather than %d for FROM-channel.\n",
line, from_chan);
@@ -2703,14 +2822,14 @@ void config_init (char *fname, struct audio_s *p_audio_config,
MAX_CHANS-1, line);
continue;
}
- if (p_audio_config->achan[to_chan].medium != MEDIUM_RADIO &&
- p_audio_config->achan[to_chan].medium != MEDIUM_NETTNC) {
+ if (p_audio_config->chan_medium[to_chan] != MEDIUM_RADIO &&
+ p_audio_config->chan_medium[to_chan] != MEDIUM_NETTNC) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: TO-channel %d is not valid.\n",
line, to_chan);
continue;
}
- if (p_audio_config->achan[to_chan].medium == MEDIUM_IGATE) {
+ if (p_audio_config->chan_medium[to_chan] == MEDIUM_IGATE) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Use 'IG' rather than %d for TO-channel.\n",
line, to_chan);
@@ -2769,7 +2888,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
// DO NOT allow a network TNC here.
// Must be internal modem to have necessary knowledge about channel status.
- if (p_audio_config->achan[from_chan].medium != MEDIUM_RADIO) {
+ if (p_audio_config->chan_medium[from_chan] != MEDIUM_RADIO) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n",
line, from_chan);
@@ -2790,7 +2909,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
MAX_CHANS-1, line);
continue;
}
- if (p_audio_config->achan[to_chan].medium != MEDIUM_RADIO) {
+ if (p_audio_config->chan_medium[to_chan] != MEDIUM_RADIO) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: TO-channel %d is not valid.\n",
line, to_chan);
@@ -3104,7 +3223,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
- dw_printf ("Line %d: Missing latitude for TTGRID command.\n", line);
+ dw_printf ("Line %d: Missing minimum latitude for TTGRID command.\n", line);
continue;
}
tl->grid.lat0 = parse_ll(t,LAT,line);
@@ -3114,7 +3233,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
- dw_printf ("Line %d: Missing longitude for TTGRID command.\n", line);
+ dw_printf ("Line %d: Missing minimum longitude for TTGRID command.\n", line);
continue;
}
tl->grid.lon0 = parse_ll(t,LON,line);
@@ -3124,7 +3243,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
- dw_printf ("Line %d: Missing latitude for TTGRID command.\n", line);
+ dw_printf ("Line %d: Missing maximum latitude for TTGRID command.\n", line);
continue;
}
tl->grid.lat9 = parse_ll(t,LAT,line);
@@ -3134,13 +3253,16 @@ void config_init (char *fname, struct audio_s *p_audio_config,
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
- dw_printf ("Line %d: Missing longitude for TTGRID command.\n", line);
+ dw_printf ("Line %d: Missing maximum longitude for TTGRID command.\n", line);
continue;
}
- tl->grid.lon0 = parse_ll(t,LON,line);
+ tl->grid.lon9 = parse_ll(t,LON,line);
/* temp debugging */
+ // dw_printf ("CONFIG TTGRID min %f %f\n", tl->grid.lat0, tl->grid.lon0);
+ // dw_printf ("CONFIG TTGRID max %f %f\n", tl->grid.lat9, tl->grid.lon9);
+
//for (j=0; jttloc_len; j++) {
// dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size,
// p_tt_config->ttloc_ptr[j].pattern);
@@ -3677,7 +3799,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
strlcpy(tl->pattern, "", sizeof(tl->pattern));
/* Pattern: Any combination of digits, x, y, and z. */
- /* Also make note of which letters are used in pattern and defintition. */
+ /* Also make note of which letters are used in pattern and definition. */
/* Version 1.2: also allow A,B,C,D in the pattern. */
t = split(NULL,0);
@@ -3951,7 +4073,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
// I suppose we need internal modem channel here.
// otherwise a DTMF decoder would not be available.
- if (p_audio_config->achan[r].medium != MEDIUM_RADIO) {
+ if (p_audio_config->chan_medium[r] != MEDIUM_RADIO) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: TTOBJ DTMF receive channel %d is not valid.\n",
line, r);
@@ -3977,8 +4099,8 @@ void config_init (char *fname, struct audio_s *p_audio_config,
dw_printf ("Config file: Transmit channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line);
x = -1;
}
- else if (p_audio_config->achan[x].medium != MEDIUM_RADIO &&
- p_audio_config->achan[x].medium != MEDIUM_NETTNC) {
+ else if (p_audio_config->chan_medium[x] != MEDIUM_RADIO &&
+ p_audio_config->chan_medium[x] != MEDIUM_NETTNC) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: TTOBJ transmit channel %d is not valid.\n", line, x);
x = -1;
@@ -4453,6 +4575,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
*
* In version 1.2 we allow 0 to disable listening.
*/
+// FIXME: complain if extra parameter e.g. port as in KISSPORT
else if (strcasecmp(t, "AGWPORT") == 0) {
int n;
@@ -4475,27 +4598,89 @@ void config_init (char *fname, struct audio_s *p_audio_config,
}
/*
- * KISSPORT - Port number for KISS over IP.
+ * KISSPORT port [ chan ] - Port number for KISS over IP.
*/
+ // Previously we allowed only a single TCP port for KISS.
+ // An increasing number of people want to run multiple radios.
+ // Unfortunately, most applications don't know how to deal with multi-radio TNCs.
+ // They ignore the channel on receive and always transmit to channel 0.
+ // Running multiple instances of direwolf is a work-around but this leads to
+ // more complex configuration and we lose the cross-channel digipeating capability.
+ // In release 1.7 we add a new feature to assign a single radio channel to a TCP port.
+ // e.g.
+ // KISSPORT 8001 # default, all channels. Radio channel = KISS channel.
+ //
+ // KISSPORT 7000 0 # Only radio channel 0 for receive.
+ // # Transmit to radio channel 0, ignoring KISS channel.
+ //
+ // KISSPORT 7001 1 # Only radio channel 1 for receive. KISS channel set to 0.
+ // # Transmit to radio channel 1, ignoring KISS channel.
+
+// FIXME
else if (strcasecmp(t, "KISSPORT") == 0) {
int n;
+ int tcp_port = 0;
+ int chan = -1; // optional. default to all if not specified.
t = split(NULL,0);
if (t == NULL) {
text_color_set(DW_COLOR_ERROR);
- dw_printf ("Line %d: Missing port number for KISSPORT command.\n", line);
+ dw_printf ("Line %d: Missing TCP port number for KISSPORT command.\n", line);
continue;
}
n = atoi(t);
if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) {
- p_misc_config->kiss_port = n;
+ tcp_port = n;
}
else {
- p_misc_config->kiss_port = DEFAULT_KISS_PORT;
text_color_set(DW_COLOR_ERROR);
- dw_printf ("Line %d: Invalid port number for KISS TCPIP Socket Interface. Using %d.\n",
- line, p_misc_config->kiss_port);
+ dw_printf ("Line %d: Invalid TCP port number for KISS TCPIP Socket Interface.\n", line);
+ dw_printf ("Use something in the range of %d to %d.\n", MIN_IP_PORT_NUMBER, MAX_IP_PORT_NUMBER);
+ continue;
}
+
+ t = split(NULL,0);
+ if (t != NULL) {
+ chan = atoi(t);
+ if (chan < 0 || chan >= MAX_CHANS) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Invalid channel %d for KISSPORT command. Must be in range 0 thru %d.\n", line, chan, MAX_CHANS-1);
+ continue;
+ }
+ }
+
+ // "KISSPORT 0" is used to remove the default entry.
+
+ if (tcp_port == 0) {
+ p_misc_config->kiss_port[0] = 0; // Should all be wiped out?
+ }
+ else {
+
+ // Try to find an empty slot.
+ // A duplicate TCP port number will overwrite the previous value.
+
+ int slot = -1;
+ for (int i = 0; i < MAX_KISS_TCP_PORTS && slot == -1; i++) {
+ if (p_misc_config->kiss_port[i] == tcp_port) {
+ slot = i;
+ if ( ! (slot == 0 && tcp_port == DEFAULT_KISS_PORT)) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Warning: Duplicate TCP port %d will overwrite previous value.\n", line, tcp_port);
+ }
+ }
+ else if (p_misc_config->kiss_port[i] == 0) {
+ slot = i;
+ }
+ }
+ if (slot >= 0) {
+ p_misc_config->kiss_port[slot] = tcp_port;
+ p_misc_config->kiss_chan[slot] = chan;
+ }
+ else {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Too many KISSPORT commands.\n", line);
+ }
+ }
}
/*
@@ -4557,6 +4742,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
/*
* KISSCOPY - Data from network KISS client is copied to all others.
+ * This does not apply to pseudo terminal KISS.
*/
else if (strcasecmp(t, "KISSCOPY") == 0) {
@@ -4565,7 +4751,44 @@ void config_init (char *fname, struct audio_s *p_audio_config,
/*
- * GPSNMEA - Device name for reading from GPS receiver.
+ * DNSSD - Enable or disable (1/0) dns-sd, DNS Service Discovery announcements
+ * DNSSDNAME - Set DNS-SD service name, defaults to "Dire Wolf on "
+ */
+
+ else if (strcasecmp(t, "DNSSD") == 0) {
+ int n;
+ t = split(NULL,0);
+ if (t == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Missing integer value for DNSSD command.\n", line);
+ continue;
+ }
+ n = atoi(t);
+ if (n == 0 || n == 1) {
+ p_misc_config->dns_sd_enabled = n;
+ } else {
+ p_misc_config->dns_sd_enabled = 0;
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Invalid integer value for DNSSD. Disabling dns-sd.\n", line);
+ }
+ }
+
+ else if (strcasecmp(t, "DNSSDNAME") == 0) {
+ t = split(NULL, 1);
+ if (t == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Line %d: Missing service name for DNSSDNAME.\n", line);
+ continue;
+ }
+ else {
+ strlcpy(p_misc_config->dns_sd_name, t, sizeof(p_misc_config->dns_sd_name));
+ }
+ }
+
+
+
+/*
+ * GPSNMEA serial-device [ speed ] - Direct connection to GPS receiver.
*/
else if (strcasecmp(t, "gpsnmea") == 0) {
t = split(NULL,0);
@@ -4574,8 +4797,15 @@ void config_init (char *fname, struct audio_s *p_audio_config,
dw_printf ("Config file, line %d: Missing serial port name for GPS receiver.\n", line);
continue;
}
+ strlcpy (p_misc_config->gpsnmea_port, t, sizeof(p_misc_config->gpsnmea_port));
+
+ t = split(NULL,0);
+ if (t != NULL) {
+ int n = atoi(t);
+ p_misc_config->gpsnmea_speed = n;
+ }
else {
- strlcpy (p_misc_config->gpsnmea_port, t, sizeof(p_misc_config->gpsnmea_port));
+ p_misc_config->gpsnmea_speed = 4800; // The standard at one time.
}
}
@@ -4664,26 +4894,26 @@ void config_init (char *fname, struct audio_s *p_audio_config,
strlcpy (p_misc_config->waypoint_serial_port, t, sizeof(p_misc_config->waypoint_serial_port));
}
- /* Anthing remaining is the formats to enable. */
+ /* Anything remaining is the formats to enable. */
t = split(NULL,1);
if (t != NULL) {
for ( ; *t != '\0' ; t++ ) {
switch (toupper(*t)) {
case 'N':
- p_misc_config->waypoint_formats |= WPT_FORMAT_NMEA_GENERIC;
+ p_misc_config->waypoint_formats |= WPL_FORMAT_NMEA_GENERIC;
break;
case 'G':
- p_misc_config->waypoint_formats |= WPT_FORMAT_GARMIN;
+ p_misc_config->waypoint_formats |= WPL_FORMAT_GARMIN;
break;
case 'M':
- p_misc_config->waypoint_formats |= WPT_FORMAT_MAGELLAN;
+ p_misc_config->waypoint_formats |= WPL_FORMAT_MAGELLAN;
break;
case 'K':
- p_misc_config->waypoint_formats |= WPT_FORMAT_KENWOOD;
+ p_misc_config->waypoint_formats |= WPL_FORMAT_KENWOOD;
break;
case 'A':
- p_misc_config->waypoint_formats |= WPT_FORMAT_AIS;
+ p_misc_config->waypoint_formats |= WPL_FORMAT_AIS;
break;
case ' ':
case ',':
@@ -5031,7 +5261,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
/*
* V20 address [ address ... ] - Stations known to support only AX.25 v2.0.
* When connecting to these, skip SABME and go right to SABM.
- * Possible to have multiple and they are cummulative.
+ * Possible to have multiple and they are cumulative.
*/
else if (strcasecmp(t, "V20") == 0) {
@@ -5065,9 +5295,9 @@ void config_init (char *fname, struct audio_s *p_audio_config,
/*
* NOXID address [ address ... ] - Stations known not to understand XID.
- * After connecting to these (with v2.2 obviously), don't try using XID commmand.
+ * After connecting to these (with v2.2 obviously), don't try using XID command.
* AX.25 for Linux is the one known case so far.
- * Possible to have multiple and they are cummulative.
+ * Possible to have multiple and they are cumulative.
*/
else if (strcasecmp(t, "NOXID") == 0) {
@@ -5197,7 +5427,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
/* When IGate is enabled, all radio channels must have a callsign associated. */
if (strlen(p_igate_config->t2_login) > 0 &&
- (p_audio_config->achan[i].medium == MEDIUM_RADIO || p_audio_config->achan[i].medium == MEDIUM_NETTNC)) {
+ (p_audio_config->chan_medium[i] == MEDIUM_RADIO || p_audio_config->chan_medium[i] == MEDIUM_NETTNC)) {
if (strcmp(p_audio_config->achan[i].mycall, "NOCALL") == 0 || strcmp(p_audio_config->achan[i].mycall, "N0CALL") == 0) {
text_color_set(DW_COLOR_ERROR);
@@ -5223,7 +5453,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
if (strlen(p_igate_config->t2_login) > 0) {
for (j=0; jachan[j].medium == MEDIUM_RADIO || p_audio_config->achan[j].medium == 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) {
p_digi_config->filter_str[MAX_CHANS][j] = strdup("i/60");
}
@@ -5245,6 +5475,10 @@ void config_init (char *fname, struct audio_s *p_audio_config,
* Returns 1 for success, 0 for serious error.
*/
+// FIXME: provide error messages when non applicable option is used for particular beacon type.
+// e.g. IBEACON DELAY=1 EVERY=1 SENDTO=IG OVERLAY=R SYMBOL="igate" LAT=37^44.46N LONG=122^27.19W COMMENT="N1KOL-1 IGATE"
+// Just ignores overlay, symbol, lat, long, and comment.
+
static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_s *p_audio_config)
{
char *t;
@@ -5273,11 +5507,13 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
b->freq = G_UNKNOWN;
b->tone = G_UNKNOWN;
b->offset = G_UNKNOWN;
+ b->source = NULL;
+ b->dest = NULL;
while ((t = split(NULL,0)) != NULL) {
char keyword[20];
- char value[200];
+ char value[1000];
char *e;
char *p;
@@ -5292,6 +5528,44 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
strlcpy (keyword, t, sizeof(keyword));
strlcpy (value, e+1, sizeof(value));
+// QUICK TEMP EXPERIMENT, maybe permanent new feature.
+// Recognize \xnn as hexadecimal value. Handy for UTF-8 in comment.
+// Maybe recognize the <0xnn> form that we print.
+//
+// # Convert between languages here: https://translate.google.com/ then
+// # Convert to UTF-8 bytes here: https://codebeautify.org/utf8-converter
+//
+// pbeacon delay=0:05 every=0:30 sendto=R0 lat=12.5N long=69.97W comment="\xe3\x82\xa2\xe3\x83\x9e\xe3\x83\x81\xe3\x83\xa5\xe3\x82\xa2\xe7\x84\xa1\xe7\xb7\x9a \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"
+
+ char temp[256];
+ int tlen = 0;
+
+ for (char *p = value; *p != '\0'; ) {
+ if (p[0] == '\\' && p[1] == 'x' && strlen(p) >= 4 && isxdigit(p[2]) && isxdigit(p[3])) {
+ int n = 0;
+ for (int i = 2; i < 4; i++) {
+ n = n * 16;
+ if (islower(p[i])) {
+ n += p[i] - 'a' + 10;
+ }
+ else if (isupper(p[i])) {
+ n += p[i] - 'A' + 10;
+ }
+ else { // must be digit due to isxdigit test above.
+ n += p[i] - '0';
+ }
+ }
+ temp[tlen++] = n;
+ p += 4;
+ }
+ else {
+ temp[tlen++] = *p++;
+ }
+ }
+ temp[tlen] = '\0';
+ strlcpy (value, temp, sizeof(value));
+
+// end
if (strcasecmp(keyword, "DELAY") == 0) {
b->delay = parse_interval(value,line);
}
@@ -5314,7 +5588,7 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
}
else if (value[0] == 'r' || value[0] == 'R') {
int n = atoi(value+1);
- if ( n < 0 || n >= MAX_CHANS || p_audio_config->achan[n].medium == MEDIUM_NONE) {
+ if ( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Simulated receive on channel %d is not valid.\n", line, n);
continue;
@@ -5324,7 +5598,7 @@ 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') {
int n = atoi(value+1);
- if ( n < 0 || n >= MAX_CHANS || p_audio_config->achan[n].medium == MEDIUM_NONE) {
+ if ( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n);
continue;
@@ -5335,7 +5609,7 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
}
else {
int n = atoi(value);
- if ( n < 0 || n >= MAX_CHANS || p_audio_config->achan[n].medium == MEDIUM_NONE) {
+ if ( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n);
continue;
@@ -5344,6 +5618,17 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
b->sendto_chan = n;
}
}
+ else if (strcasecmp(keyword, "SOURCE") == 0) {
+ b->source = strdup(value);
+ for (p = b->source; *p != '\0'; p++) {
+ if (islower(*p)) {
+ *p = toupper(*p); /* silently force upper case. */
+ }
+ }
+ if (strlen(b->source) > 9) {
+ b->source[9] = '\0';
+ }
+ }
else if (strcasecmp(keyword, "DEST") == 0) {
b->dest = strdup(value);
for (p = b->dest; *p != '\0'; p++) {
@@ -5404,7 +5689,29 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
}
}
else if (strcasecmp(keyword, "ALT") == 0 || strcasecmp(keyword, "ALTITUDE") == 0) {
- b->alt_m = atof(value);
+
+ char *unit = strpbrk(value, "abcedfghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ if (unit != NULL) {
+ float meters = 0;
+ for (int j=0; jalt_m = atof(value);
+ }
+ else {
+ // valid unit
+ b->alt_m = atof(value) * meters;
+ }
+ } else {
+ // no unit specified
+ b->alt_m = atof(value);
+ }
}
else if (strcasecmp(keyword, "ZONE") == 0) {
strlcpy(zone, value, sizeof(zone));
@@ -5480,7 +5787,7 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
}
/*
- * Convert UTM coordintes to lat / long.
+ * Convert UTM coordinates to lat / long.
*/
if (strlen(zone) > 0 || easting != G_UNKNOWN || northing != G_UNKNOWN) {
@@ -5547,7 +5854,7 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_
if (b->sendto_type == SENDTO_XMIT) {
- if ( b->sendto_chan < 0 || b->sendto_chan >= MAX_CHANS || p_audio_config->achan[b->sendto_chan].medium == MEDIUM_NONE) {
+ if ( b->sendto_chan < 0 || b->sendto_chan >= MAX_CHANS || p_audio_config->chan_medium[b->sendto_chan] == MEDIUM_NONE) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, b->sendto_chan);
return (0);
diff --git a/src/config.h b/src/config.h
index 562d307c..5ab2ae9a 100644
--- a/src/config.h
+++ b/src/config.h
@@ -30,11 +30,31 @@ enum sendto_type_e { SENDTO_XMIT, SENDTO_IGATE, SENDTO_RECV };
#define MAX_BEACONS 30
+#define MAX_KISS_TCP_PORTS (MAX_CHANS+1)
struct misc_config_s {
- int agwpe_port; /* Port number for the "AGW TCPIP Socket Interface" */
- int kiss_port; /* Port number for the "TCP KISS" protocol. */
+ int agwpe_port; /* TCP Port number for the "AGW TCPIP Socket Interface" */
+
+ // Previously we allowed only a single TCP port for KISS.
+ // An increasing number of people want to run multiple radios.
+ // Unfortunately, most applications don't know how to deal with multi-radio TNCs.
+ // They ignore the channel on receive and always transmit to channel 0.
+ // Running multiple instances of direwolf is a work-around but this leads to
+ // more complex configuration and we lose the cross-channel digipeating capability.
+ // In release 1.7 we add a new feature to assign a single radio channel to a TCP port.
+ // e.g.
+ // KISSPORT 8001 # default, all channels. Radio channel = KISS channel.
+ //
+ // KISSPORT 7000 0 # Only radio channel 0 for receive.
+ // # Transmit to radio channel 0, ignoring KISS channel.
+ //
+ // KISSPORT 7001 1 # Only radio channel 1 for receive. KISS channel set to 0.
+ // # Transmit to radio channel 1, ignoring KISS channel.
+
+ int kiss_port[MAX_KISS_TCP_PORTS]; /* TCP Port number for the "TCP KISS" protocol. */
+ int kiss_chan[MAX_KISS_TCP_PORTS]; /* Radio Channel number for this port or -1 for all. */
+
int kiss_copy; /* Data from network KISS client is copied to all others. */
int enable_kiss_pt; /* Enable pseudo terminal for KISS. */
/* Want this to be off by default because it hangs */
@@ -56,7 +76,8 @@ struct misc_config_s {
char gpsnmea_port[20]; /* Serial port name for reading NMEA sentences from GPS. */
/* e.g. COM22, /dev/ttyACM0 */
- /* Currently no option for setting non-standard speed. */
+
+ int gpsnmea_speed; /* Speed for above, baud, default 4800. */
char gpsd_host[20]; /* Host for gpsd server. */
/* e.g. localhost, 192.168.1.2 */
@@ -77,17 +98,20 @@ struct misc_config_s {
int waypoint_formats; /* Which sentence formats should be generated? */
-#define WPT_FORMAT_NMEA_GENERIC 0x01 /* N $GPWPT */
-#define WPT_FORMAT_GARMIN 0x02 /* G $PGRMW */
-#define WPT_FORMAT_MAGELLAN 0x04 /* M $PMGNWPL */
-#define WPT_FORMAT_KENWOOD 0x08 /* K $PKWDWPL */
-#define WPT_FORMAT_AIS 0x10 /* A !AIVDM */
+#define WPL_FORMAT_NMEA_GENERIC 0x01 /* N $GPWPL */
+#define WPL_FORMAT_GARMIN 0x02 /* G $PGRMW */
+#define WPL_FORMAT_MAGELLAN 0x04 /* M $PMGNWPL */
+#define WPL_FORMAT_KENWOOD 0x08 /* K $PKWDWPL */
+#define WPL_FORMAT_AIS 0x10 /* A !AIVDM */
int log_daily_names; /* True to generate new log file each day. */
char log_path[80]; /* Either directory or full file name depending on above. */
+ int dns_sd_enabled; /* DNS Service Discovery announcement enabled. */
+ char dns_sd_name[64]; /* Name announced on dns-sd; defaults to "Dire Wolf on " */
+
int sb_configured; /* TRUE if SmartBeaconing is configured. */
int sb_fast_speed; /* MPH */
int sb_fast_rate; /* seconds */
@@ -160,6 +184,9 @@ struct misc_config_s {
time_t next; /* Unix time to transmit next one. */
+ char *source; /* NULL or explicit AX.25 source address to use */
+ /* instead of the mycall value for the channel. */
+
char *dest; /* NULL or explicit AX.25 destination to use */
/* instead of the software version such as APDW11. */
diff --git a/src/decode_aprs.c b/src/decode_aprs.c
index 3afa3773..9543a4a2 100644
--- a/src/decode_aprs.c
+++ b/src/decode_aprs.c
@@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
-// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2017 John Langner, WB2OSZ
+// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2017, 2022 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -26,7 +26,7 @@
*
* Description: Present the packet contents in human readable format.
* This is a fairly complete implementation with error messages
- * pointing out various specication violations.
+ * pointing out various specification violations.
*
* Assumptions: ax25_from_frame() has been called to
* separate the header and information.
@@ -116,7 +116,6 @@ static void aprs_morse_code (decode_aprs_t *A, char *, int);
static void aprs_positionless_weather_report (decode_aprs_t *A, unsigned char *, int);
static void weather_data (decode_aprs_t *A, char *wdata, int wind_prefix);
static void aprs_ultimeter (decode_aprs_t *A, char *, int);
-static void third_party_header (decode_aprs_t *A, char *, int);
static void decode_position (decode_aprs_t *A, position_t *ppos);
static void decode_compressed_position (decode_aprs_t *A, compressed_position_t *ppos);
static double get_latitude_8 (char *p, int quiet);
@@ -141,6 +140,11 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen);
*
* quiet - Suppress error messages.
*
+ * third_party - True when parsing a third party header.
+ * (decode_aprs is called recursively.)
+ * This is mostly found when an IGate transmits a message
+ * that came via APRS-IS.
+ *
* Outputs: A-> g_symbol_table, g_symbol_code,
* g_lat, g_lon,
* g_speed_mph, g_course, g_altitude_ft,
@@ -152,25 +156,28 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen);
*
*------------------------------------------------------------------*/
-void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet)
+void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, int third_party)
{
+ //dw_printf ("DEBUG decode_aprs quiet=%d, third_party=%d\n", quiet, third_party);
- char dest[AX25_MAX_ADDR_LEN];
+ //char dest[AX25_MAX_ADDR_LEN];
unsigned char *pinfo;
int info_len;
info_len = ax25_get_info (pp, &pinfo);
+ //dw_printf ("DEBUG decode_aprs info=\"%s\"\n", pinfo);
+
memset (A, 0, sizeof (*A));
A->g_quiet = quiet;
if (isprint(*pinfo)) {
- snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Unknown APRS Data Type Indicator \"%c\"", *pinfo);
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Unknown APRS Data Type Indicator \"%c\"", *pinfo);
}
else {
- snprintf (A->g_msg_type, sizeof(A->g_msg_type), "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. */
@@ -197,14 +204,42 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet)
A->g_footprint_lon = G_UNKNOWN;
A->g_footprint_radius = G_UNKNOWN;
+// If third-party header, try to decode just the payload.
+
+ if (*pinfo == '}') {
+
+ //dw_printf ("DEBUG decode_aprs recursively process third party header\n");
+ // This must not be strict because the addresses in third party payload doesn't
+ // need to adhere to the AX.25 address format (i.e. 6 upper case alphanumeric.)
+ // SSID can be 2 alphanumeric characters.
+ // Addresses can include lower case, e.g. q construct.
+
+ // e.g. WR2X-2>APRS,WA1PLE-13*:}
+ // K1BOS-B>APOSB,TCPIP,WR2X-2*:@122015z4221.42ND07111.93W&/A=000000SharkRF openSPOT3 MMDVM446.025 MA/SW
+
+ packet_t pp_payload = ax25_from_text ((char*)pinfo+1, 0);
+ if (pp_payload != NULL) {
+ decode_aprs (A, pp_payload, quiet, 1); // 1 means used recursively
+ ax25_delete (pp_payload);
+ return;
+ }
+ else {
+ strlcpy (A->g_data_type_desc, "Third Party Header: Unable to parse payload.", sizeof(A->g_data_type_desc));
+ ax25_get_addr_with_ssid (pp, AX25_SOURCE, A->g_src);
+ ax25_get_addr_with_ssid (pp, AX25_DESTINATION, A->g_dest);
+ }
+ }
/*
* Extract source and destination including the SSID.
*/
ax25_get_addr_with_ssid (pp, AX25_SOURCE, A->g_src);
- ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dest);
+ 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);
+
/*
* Report error if the information part contains a nul character.
@@ -246,7 +281,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet)
break;
default:
- decode_tocall (A, dest);
+ decode_tocall (A, A->g_dest);
break;
}
@@ -302,7 +337,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet)
break;
- case ':': /* Message: for one person, a group, or a bulletin. */
+ case ':': /* "Message" (special APRS meaning): for one person, a group, or a bulletin. */
/* Directed Station Query */
/* Telemetry metadata. */
@@ -362,11 +397,9 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet)
aprs_morse_code (A, (char*)pinfo, info_len);
break;
- case '}': /* third party header */
-
- third_party_header (A, (char*)pinfo, info_len);
- break;
+ //case '}': /* third party header */
+ // was already caught earlier.
//case '\r': /* CR or LF? */
//case '\n':
@@ -380,12 +413,27 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet)
/*
- * Look in other locations if not found in information field.
+ * Priority order for determining the symbol is:
+ * - Information part, where appropriate. Already done above.
+ * - Destination field starting with GPS, SPC, or SYM.
+ * - Source SSID - Confusing to most people. Even I forgot about it when
+ * someone questioned where the symbol came from. It's in the APRS
+ * protocol spec, end of Chapter 20.
*/
if (A->g_symbol_table == ' ' || A->g_symbol_code == ' ') {
- symbols_from_dest_or_src (*pinfo, A->g_src, dest, &A->g_symbol_table, &A->g_symbol_code);
+ // A symbol on a "message" makes no sense and confuses people.
+ // Third party too. Set from the payload.
+ // Maybe eliminate for a couple others.
+
+ //dw_printf ("DEBUG decode_aprs@end1 third_party=%d, symbol_table=%c, symbol_code=%c, *pinfo=%c\n", third_party, A->g_symbol_table, A->g_symbol_code, *pinfo);
+
+ if (*pinfo != ':' && *pinfo != '}') {
+ symbols_from_dest_or_src (*pinfo, A->g_src, A->g_dest, &A->g_symbol_table, &A->g_symbol_code);
+ }
+
+ //dw_printf ("DEBUG decode_aprs@end2 third_party=%d, symbol_table=%c, symbol_code=%c, *pinfo=%c\n", third_party, A->g_symbol_table, A->g_symbol_code, *pinfo);
}
} /* end decode_aprs */
@@ -393,8 +441,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet)
void decode_aprs_print (decode_aprs_t *A) {
- char stemp[200];
- //char tmp2[2];
+ char stemp[500];
double absll;
char news;
int deg;
@@ -402,18 +449,19 @@ void decode_aprs_print (decode_aprs_t *A) {
char s_lat[30];
char s_lon[30];
int n;
- char symbol_description[100];
/*
* First line has:
- * - message type
+ * - packet type
* - object name
* - symbol
* - manufacturer/application
* - mic-e status
* - power/height/gain, range
*/
- strlcpy (stemp, A->g_msg_type, sizeof(stemp));
+ strlcpy (stemp, A->g_data_type_desc, sizeof(stemp));
+
+ //dw_printf ("DEBUG decode_aprs_print stemp1=%s\n", stemp);
if (strlen(A->g_name) > 0) {
strlcat (stemp, ", \"", sizeof(stemp));
@@ -421,27 +469,50 @@ void decode_aprs_print (decode_aprs_t *A) {
strlcat (stemp, "\"", sizeof(stemp));
}
+ //dw_printf ("DEBUG decode_aprs_print stemp2=%s\n", stemp);
+
+ //dw_printf ("DEBUG decode_aprs_print symbol_code=%c=0x%02x\n", A->g_symbol_code, A->g_symbol_code);
+
if (A->g_symbol_code != ' ') {
- symbols_get_description (A->g_symbol_table, A->g_symbol_code, symbol_description, sizeof(symbol_description));
+ char symbol_description[100];
+ symbols_get_description (A->g_symbol_table, A->g_symbol_code, symbol_description, sizeof(symbol_description));
+
+ //dw_printf ("DEBUG decode_aprs_print symbol_description_description=%s\n", symbol_description);
+
strlcat (stemp, ", ", sizeof(stemp));
strlcat (stemp, symbol_description, sizeof(stemp));
}
+ //dw_printf ("DEBUG decode_aprs_print stemp3=%s mfr=%s\n", stemp, A->g_mfr);
+
if (strlen(A->g_mfr) > 0) {
- strlcat (stemp, ", ", sizeof(stemp));
- strlcat (stemp, A->g_mfr, sizeof(stemp));
+ if (strcmp(A->g_dest, "APRS") == 0) {
+ strlcat (stemp, "\nUse of \"APRS\" in the destination field is obsolete.", sizeof(stemp));
+ strlcat (stemp, " You can help to improve the quality of APRS signals.", sizeof(stemp));
+ strlcat (stemp, "\nTell the sender (", sizeof(stemp));
+ strlcat (stemp, A->g_src, sizeof(stemp));
+ strlcat (stemp, ") to use the proper product code from", sizeof(stemp));
+ strlcat (stemp, " http://www.aprs.org/aprs11/tocalls.txt", sizeof(stemp));
+ }
+ else {
+ strlcat (stemp, ", ", sizeof(stemp));
+ strlcat (stemp, A->g_mfr, sizeof(stemp));
+ }
}
+ //dw_printf ("DEBUG decode_aprs_print stemp4=%s\n", stemp);
+
if (strlen(A->g_mic_e_status) > 0) {
strlcat (stemp, ", ", sizeof(stemp));
strlcat (stemp, A->g_mic_e_status, sizeof(stemp));
}
+ //dw_printf ("DEBUG decode_aprs_print stemp5=%s\n", stemp);
if (A->g_power > 0) {
char phg[100];
- /* Protcol spec doesn't mention whether this is dBd or dBi. */
+ /* Protocol spec doesn't mention whether this is dBd or dBi. */
/* Clarified later. */
/* http://eng.usna.navy.mil/~bruninga/aprs/aprs11.html */
/* "The Antenna Gain in the PHG format on page 28 is in dBi." */
@@ -492,7 +563,7 @@ void decode_aprs_print (decode_aprs_t *A) {
if (A->g_lat != G_UNKNOWN || A->g_lon != G_UNKNOWN) {
-// Have location but it is posible one part is invalid.
+// Have location but it is possible one part is invalid.
if (A->g_lat != G_UNKNOWN) {
@@ -539,10 +610,10 @@ void decode_aprs_print (decode_aprs_t *A) {
};
if (A->g_speed_mph != G_UNKNOWN) {
- char spd[20];
+ char spd[32];
if (strlen(stemp) > 0) strlcat (stemp, ", ", sizeof(stemp));
- snprintf (spd, sizeof(spd), "%.0f MPH", A->g_speed_mph);
+ snprintf (spd, sizeof(spd), "%.0f km/h (%.0f MPH)", DW_MILES_TO_KM(A->g_speed_mph), A->g_speed_mph);
strlcat (stemp, spd, sizeof(stemp));
};
@@ -555,10 +626,10 @@ void decode_aprs_print (decode_aprs_t *A) {
};
if (A->g_altitude_ft != G_UNKNOWN) {
- char alt[20];
+ char alt[32];
if (strlen(stemp) > 0) strlcat (stemp, ", ", sizeof(stemp));
- snprintf (alt, sizeof(alt), "alt %.0f ft", A->g_altitude_ft);
+ snprintf (alt, sizeof(alt), "alt %.0f m (%.0f ft)", DW_FEET_TO_METERS(A->g_altitude_ft), A->g_altitude_ft);
strlcat (stemp, alt, sizeof(stemp));
};
@@ -660,6 +731,26 @@ void decode_aprs_print (decode_aprs_t *A) {
* To be part of a valid UTF-8 sequence, it would need to be followed by 10xxxxxx.
*/
+// For values 00-7F, ASCII, Unicode, and ISO Latin-1 are all the same.
+// ISO Latin-1 adds 80-FF range with a few common symbols, such as degree, and
+// letters, with diacritical marks, for many European languages.
+// Unicode range 80-FF is called "Latin-1 Supplement." Exactly the same as ISO Latin-1.
+// For UTF-8, an additional byte is inserted.
+// Unicode UTF-8
+// ------- -----
+// 8x C2 8x Insert C2, keep original
+// 9x C2 9x "
+// Ax C2 Ax "
+// Bx C2 Bx "
+// Cx C3 8x Insert C3, subtract 40 from original
+// Dx C3 9x "
+// Ex C3 Ax "
+// Fx C3 Bx "
+//
+// Can we use this knowledge to provide guidance on other ISO Latin-1 characters besides degree?
+// Should we?
+// Reference: https://www.fileformat.info/info/unicode/utf8test.htm
+
if ( ! A->g_quiet) {
for (j=0; jg_msg_type, "Position", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Position", sizeof(A->g_data_type_desc));
p = (struct aprs_ll_pos_s *)info;
q = (struct aprs_compressed_pos_s *)info;
@@ -742,7 +833,7 @@ static void aprs_ll_pos (decode_aprs_t *A, unsigned char *info, int ilen)
/* In this case, we expect 7 byte "data extension" */
/* for the wind direction and speed. */
- strlcpy (A->g_msg_type, "Weather Report", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Weather Report", sizeof(A->g_data_type_desc));
weather_data (A, p->comment, TRUE);
}
else {
@@ -761,7 +852,7 @@ static void aprs_ll_pos (decode_aprs_t *A, unsigned char *info, int ilen)
/* compressed data so we don't expect a 7 byte "data */
/* extension" for them. */
- strlcpy (A->g_msg_type, "Weather Report", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Weather Report", sizeof(A->g_data_type_desc));
weather_data (A, q->comment, FALSE);
}
else {
@@ -831,7 +922,7 @@ static void aprs_ll_pos_time (decode_aprs_t *A, unsigned char *info, int ilen)
} *q;
- strlcpy (A->g_msg_type, "Position with time", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Position with time", sizeof(A->g_data_type_desc));
time_t ts = 0;
@@ -850,7 +941,7 @@ static void aprs_ll_pos_time (decode_aprs_t *A, unsigned char *info, int ilen)
/* In this case, we expect 7 byte "data extension" */
/* for the wind direction and speed. */
- strlcpy (A->g_msg_type, "Weather Report", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Weather Report", sizeof(A->g_data_type_desc));
weather_data (A, p->comment, TRUE);
}
else {
@@ -871,7 +962,7 @@ static void aprs_ll_pos_time (decode_aprs_t *A, unsigned char *info, int ilen)
/* compressed data so we don't expect a 7 byte "data */
/* extension" for them. */
- strlcpy (A->g_msg_type, "Weather Report", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Weather Report", sizeof(A->g_data_type_desc));
weather_data (A, q->comment, FALSE);
}
else {
@@ -926,7 +1017,7 @@ static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen)
(void) dwgpsnmea_gprmc ((char*)info, A->g_quiet, &(A->g_lat), &(A->g_lon), &speed_knots, &(A->g_course));
A->g_speed_mph = DW_KNOTS_TO_MPH(speed_knots);
- strlcpy (A->g_msg_type, "Raw GPS data", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Raw GPS data", sizeof(A->g_data_type_desc));
}
else if (strncmp((char*)info, "$GPGGA,", 7) == 0 ||
strncmp((char*)info, "$GNGGA,", 7) == 0)
@@ -936,7 +1027,7 @@ static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen)
(void) dwgpsnmea_gpgga ((char*)info, A->g_quiet, &(A->g_lat), &(A->g_lon), &alt_meters, &num_sat);
A->g_altitude_ft = DW_METERS_TO_FEET(alt_meters);
- strlcpy (A->g_msg_type, "Raw GPS data", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Raw GPS data", sizeof(A->g_data_type_desc));
}
// TODO (low): add a few other sentence types.
@@ -1022,7 +1113,7 @@ N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}Byonics TinyTrack4|4
# The next group starts with metacharacter "T" which can be any of space > ] ` '
# But space is for original Mic-E, # > and ] are for Kenwood,
-# so ` ' would probably be less ambigous choices but any appear to be valid.
+# so ` ' would probably be less ambiguous choices but any appear to be valid.
N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}Hamhud\9
N1ZZN-9>T2SP0W:'c_Vm6hk/`"49}Argent/9
@@ -1103,7 +1194,7 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
char sym_table_id;
} *p;
- char dest[10];
+ char dest[12];
int ch;
int n;
int offset;
@@ -1113,7 +1204,7 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
const char *cust_text[8] = {"Emergency", "Custom-6", "Custom-5", "Custom-4", "Custom-3", "Custom-2", "Custom-1", "Custom-0" };
unsigned char *pfirst, *plast;
- strlcpy (A->g_msg_type, "MIC-E", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "MIC-E", sizeof(A->g_data_type_desc));
p = (struct aprs_mic_e_s *)info;
@@ -1130,7 +1221,7 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
mic_e_digit(A, dest[5], 0, &std_msg, &cust_msg)) / 6000.0;
-/* 4th character of desination indicates north / south. */
+/* 4th character of destination indicates north / south. */
if ((dest[3] >= '0' && dest[3] <= '9') || dest[3] == 'L') {
/* South */
@@ -1172,7 +1263,7 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
/* First character of information field is longitude in degrees. */
/* It is possible for the unprintable DEL character to occur here. */
-/* 5th character of desination indicates longitude offset of +100. */
+/* 5th character of destination indicates longitude offset of +100. */
/* Not quite that simple :-( */
ch = p->lon[0];
@@ -1360,9 +1451,14 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
#define isT(c) ((c) == ' ' || (c) == '>' || (c) == ']' || (c) == '`' || (c) == '\'')
-// Last updated Sept. 2016 for TH-D74A
+// Last Updated Dec. 2021
+
+// This does not change very often but I'm wondering if we could parse
+// http://www.aprs.org/aprs12/mic-e-types.txt similar to how we use tocalls.txt.
if (isT(*pfirst)) {
+
+// "legacy" formats.
if (*pfirst == ' ' ) { strlcpy (A->g_mfr, "Original MIC-E", sizeof(A->g_mfr)); pfirst++; }
@@ -1373,6 +1469,8 @@ 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 TM-D710", sizeof(A->g_mfr)); pfirst++; plast--; }
else if (*pfirst == ']' ) { strlcpy (A->g_mfr, "Kenwood TM-D700", sizeof(A->g_mfr)); pfirst++; }
+// ` should be used for message capable devices.
+
else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ' ') { strlcpy (A->g_mfr, "Yaesu VX-8", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '"') { strlcpy (A->g_mfr, "Yaesu FTM-350", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '#') { strlcpy (A->g_mfr, "Yaesu VX-8G", sizeof(A->g_mfr)); pfirst++; plast-=2; }
@@ -1381,11 +1479,15 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ')') { strlcpy (A->g_mfr, "Yaesu FTM-100D", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '(') { strlcpy (A->g_mfr, "Yaesu FT2D", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '0') { strlcpy (A->g_mfr, "Yaesu FT3D", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+ else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '3') { strlcpy (A->g_mfr, "Yaesu FT5D", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+ else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '1') { strlcpy (A->g_mfr, "Yaesu FTM-300D", sizeof(A->g_mfr)); pfirst++; plast-=2; }
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 == '5') { strlcpy (A->g_mfr, "Anytone D578UV", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+ else if (*pfirst == '`' ) { strlcpy (A->g_mfr, "Generic Mic-Emsg", sizeof(A->g_mfr)); pfirst++; }
- else if (*pfirst == '`' ) { strlcpy (A->g_mfr, "Mic-Emsg", sizeof(A->g_mfr)); pfirst++; }
+// ' should be used for trackers (not message capable).
else if (*pfirst == '\'' && *(plast-1) == '(' && *plast == '8') { strlcpy (A->g_mfr, "Anytone D878UV", sizeof(A->g_mfr)); pfirst++; plast-=2; }
@@ -1395,13 +1497,13 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
else if (*pfirst == '\'' && *(plast-1) == ':' && *plast == '4') { strlcpy (A->g_mfr, "SCS GmbH & Co. P4dragon DR-7400 modems", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if (*pfirst == '\'' && *(plast-1) == ':' && *plast == '8') { strlcpy (A->g_mfr, "SCS GmbH & Co. P4dragon DR-7800 modems", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if (*pfirst == '\'' ) { strlcpy (A->g_mfr, "McTrackr", sizeof(A->g_mfr)); pfirst++; }
+ else if (*pfirst == '\'' ) { strlcpy (A->g_mfr, "Generic McTrackr", sizeof(A->g_mfr)); pfirst++; }
else if ( *(plast-1) == '\\' ) { strlcpy (A->g_mfr, "Hamhud ?", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if ( *(plast-1) == '/' ) { strlcpy (A->g_mfr, "Argent ?", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if ( *(plast-1) == '^' ) { strlcpy (A->g_mfr, "HinzTec anyfrog", sizeof(A->g_mfr)); pfirst++; plast-=2; }
else if ( *(plast-1) == '*' ) { strlcpy (A->g_mfr, "APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if ( *(plast-1) == '~' ) { strlcpy (A->g_mfr, "OTHER", sizeof(A->g_mfr)); pfirst++; plast-=2; }
+ else if ( *(plast-1) == '~' ) { strlcpy (A->g_mfr, "Unknown OTHER", sizeof(A->g_mfr)); pfirst++; plast-=2; }
}
/*
@@ -1456,9 +1558,9 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
*
* Inputs: info - Pointer to Information field.
* ilen - Information field length.
- * quiet - supress error messages.
+ * quiet - suppress error messages.
*
- * Outputs: A->g_msg_type Text description for screen display.
+ * Outputs: A->g_data_type_desc Text description for screen display.
*
* A->g_addressee To whom is it addressed.
* Could be a specific station, alias, bulletin, etc.
@@ -1480,16 +1582,21 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
*
* Cases: :xxxxxxxxx:PARM. Telemetry metadata, parameter name
* :xxxxxxxxx:UNIT. Telemetry metadata, unit/label
- * :xxxxxxxxx:EQNS. Telemetry metadata, Equation Coefficents
+ * :xxxxxxxxx:EQNS. Telemetry metadata, Equation Coefficients
* :xxxxxxxxx:BITS. Telemetry metadata, Bit Sense/Project Name
* :xxxxxxxxx:? Directed Station Query
- * :xxxxxxxxx:ack Message acknowledged (received)
- * :xxxxxxxxx:rej Message rejected (unable to accept)
+ * :xxxxxxxxx:ackNNNN Message acknowledged (received)
+ * :xxxxxxxxx:rejNNNNN Message rejected (unable to accept)
*
* :xxxxxxxxx: ... Message with no message number.
* (Text may not contain the { character because
* it indicates beginning of optional message number.)
- * :xxxxxxxxx: ... {num Message with message number.
+ * :xxxxxxxxx: ... {NNNNN Message with message number, 1 to 5 alphanumeric.
+ * :xxxxxxxxx: ... {mm} Message with new style message number.
+ * :xxxxxxxxx: ... {mm}aa Message with new style message number and ack.
+ *
+ *
+ * Reference: See new message id style: http://www.aprs.org/aprs11/replyacks.txt
*
*------------------------------------------------------------------*/
@@ -1500,10 +1607,12 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
char dti; /* : */
char addressee[9];
char colon; /* : */
- char message[73]; /* 0-67 characters for message */
- /* Optional { followed by 1-5 characters for message number */
+ 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. */
+ /* Wouldn't surprise me if others did not pay attention to the limit. */
+ /* Optional { followed by 1-5 alphanumeric characters for message number */
- /* If the first chracter is '?' it is a Directed Station Query. */
+ /* If the first character is '?' it is a Directed Station Query. */
} *p;
char addressee[AX25_MAX_ADDR_LEN];
@@ -1511,7 +1620,7 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
p = (struct aprs_message_s *)info;
- strlcpy (A->g_msg_type, "APRS Message", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "APRS Message", sizeof(A->g_data_type_desc));
A->g_message_subtype = message_subtype_message; /* until found otherwise */
if (ilen < 11) {
@@ -1526,7 +1635,8 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
if (p->colon != ':') {
if (! quiet) {
text_color_set(DW_COLOR_ERROR);
- dw_printf("APRS Message must begin with : 9 character addressee :\n");
+ dw_printf("APRS Message must begin with ':' 9 character addressee ':'\n");
+ dw_printf("Spaces must be added to shorter addressee to make 9 characters.\n");
}
A->g_message_subtype = message_subtype_invalid;
return;
@@ -1541,6 +1651,38 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
addressee[i--] = '\0';
}
+ // Anytone AT-D878UV 2 plus would pad out station name to 6 characters
+ // before appending the SSID. e.g. "AE7MK -5 "
+
+ // Test cases. First is valid. Others should produce errors:
+ //
+ // cbeacon sendto=r0 delay=0:10 info=":AE7MK-5 :test0"
+ // cbeacon sendto=r0 delay=0:15 info=":AE7MK-5:test1"
+ // cbeacon sendto=r0 delay=0:20 info=":AE7MK -5 :test2"
+ // cbeacon sendto=r0 delay=0:25 info=":AE7 -5 :test3"
+
+ static regex_t bad_addressee_re; /* Probably bad addressee. */
+ static int first_time = 1;
+
+ if (first_time) {
+ char emsg[100];
+ int e = regcomp (&bad_addressee_re, "[A-Z0-9]+ +-[0-9]", REG_EXTENDED);
+ if (e) {
+ regerror (e, &bad_addressee_re, emsg, sizeof(emsg));
+ dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
+ }
+ first_time = 0;
+ }
+
+#define MAXMATCH_AT 2
+ regmatch_t match[MAXMATCH_AT];
+
+ if (regexec (&bad_addressee_re, addressee, MAXMATCH_AT, match, 0) == 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("Malformed addressee with space between station name and SSID.\n");
+ dw_printf("Please tell message sender this is invalid.\n");
+ }
+
strlcpy (A->g_addressee, addressee, sizeof(A->g_addressee));
@@ -1556,22 +1698,22 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
*/
if (strncmp(p->message,"PARM.",5) == 0) {
- snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Parameter Name Message for \"%s\"", addressee);
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Telemetry Parameter Name Message for \"%s\"", addressee);
A->g_message_subtype = message_subtype_telem_parm;
telemetry_name_message (addressee, p->message+5);
}
else if (strncmp(p->message,"UNIT.",5) == 0) {
- snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Unit/Label Message for \"%s\"", addressee);
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Telemetry Unit/Label Message for \"%s\"", addressee);
A->g_message_subtype = message_subtype_telem_unit;
telemetry_unit_label_message (addressee, p->message+5);
}
else if (strncmp(p->message,"EQNS.",5) == 0) {
- snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Equation Coefficents Message for \"%s\"", addressee);
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Telemetry Equation Coefficients Message for \"%s\"", addressee);
A->g_message_subtype = message_subtype_telem_eqns;
telemetry_coefficents_message (addressee, p->message+5, quiet);
}
else if (strncmp(p->message,"BITS.",5) == 0) {
- snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Telemetry Bit Sense/Project Name Message for \"%s\"", addressee);
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "Telemetry Bit Sense/Project Name Message for \"%s\"", addressee);
A->g_message_subtype = message_subtype_telem_bits;
telemetry_bit_sense_message (addressee, p->message+5, quiet);
}
@@ -1582,7 +1724,7 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
else if (p->message[0] == '?') {
- strlcpy (A->g_msg_type, "Directed Station Query", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Directed Station Query", sizeof(A->g_data_type_desc));
A->g_message_subtype = message_subtype_directed_query;
aprs_directed_station_query (A, addressee, p->message+1, quiet);
@@ -1590,25 +1732,84 @@ static void aprs_message (decode_aprs_t *A, unsigned char *info, int ilen, int q
/* ack or rej? Message number is required for these. */
- else if (strncmp(p->message,"ack",3) == 0) {
+ else if (strncasecmp(p->message,"ack",3) == 0) {
+ if (strncmp(p->message,"ack",3) != 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("ERROR: \"%s\" must be lower case \"ack\"\n", p->message);
+ }
strlcpy (A->g_message_number, p->message + 3, sizeof(A->g_message_number));
- snprintf (A->g_msg_type, sizeof(A->g_msg_type), "ACK message %s for \"%s\"", A->g_message_number, addressee);
+ if (strlen(A->g_message_number) == 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("ERROR: Message number is missing after \"ack\".\n");
+ }
+ 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);
A->g_message_subtype = message_subtype_ack;
}
- else if (strncmp(p->message,"rej",3) == 0) {
+ else if (strncasecmp(p->message,"rej",3) == 0) {
+ if (strncmp(p->message,"rej",3) != 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("ERROR: \"%s\" must be lower case \"rej\"\n", p->message);
+ }
strlcpy (A->g_message_number, p->message + 3, sizeof(A->g_message_number));
- snprintf (A->g_msg_type, sizeof(A->g_msg_type), "REJ message %s for \"%s\"", A->g_message_number, addressee);
+ if (strlen(A->g_message_number) == 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("ERROR: Message number is missing after \"rej\".\n");
+ }
+ 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);
A->g_message_subtype = message_subtype_ack;
}
-/* message number is optional here. */
+// Message to a particular station or a bulletin.
+// message number is optional here.
+// Test cases. Wrap in third party too.
+// A>B::WA1XYX-15:Howdy y'all
+// A>B::WA1XYX-15:Howdy y'all{12345
+// A>B::WA1XYX-15:Howdy y'all{12}
+// A>B::WA1XYX-15:Howdy y'all{12}34
+// A>B::WA1XYX-15:Howdy y'all{toolong
+// X>Y:}A>B::WA1XYX-15:Howdy y'all
+// X>Y:}A>B::WA1XYX-15:Howdy y'all{12345
+// X>Y:}A>B::WA1XYX-15:Howdy y'all{12}
+// X>Y:}A>B::WA1XYX-15:Howdy y'all{12}34
+// X>Y:}A>B::WA1XYX-15:Howdy y'all{toolong
else {
+ // Look for message number.
char *pno = strchr(p->message, '{');
if (pno != NULL) {
+ *pno = '\0';
+ int mlen = strlen(pno+1);
+ if (mlen < 1 || mlen > 5) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("Message number \"%s\" has length outside range of 1 to 5.\n", pno+1);
+ }
strlcpy (A->g_message_number, pno+1, sizeof(A->g_message_number));
+ // TODO: Complain if not alphanumeric.
+
+ char ack[8] = "";
+
+ if (mlen >= 3 && A->g_message_number[2] == '}') {
+ // New (1999) style.
+ A->g_message_number[2] = '\0';
+ strlcpy (ack, A->g_message_number + 3, sizeof(ack));
+ }
+
+ if (strlen(ack) > 0) {
+ // With ACK. Message number should be 2 characters.
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "APRS Message, number \"%s\", from \"%s\" to \"%s\", with ACK for \"%s\"", A->g_message_number, A->g_src, addressee, ack);
+ }
+ else {
+ // Message number can be 1-5 characters.
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "APRS Message, number \"%s\", from \"%s\" to \"%s\"", A->g_message_number, A->g_src, addressee);
+ }
+ }
+ else {
+ // No message number.
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "APRS Message, with no number, from \"%s\" to \"%s\"", A->g_src, addressee);
}
- snprintf (A->g_msg_type, sizeof(A->g_msg_type), "APRS Message %s for \"%s\"", A->g_message_number, addressee);
+
A->g_message_subtype = message_subtype_message;
/* No location so don't use process_comment () */
@@ -1685,11 +1886,11 @@ static void aprs_object (decode_aprs_t *A, unsigned char *info, int ilen)
}
if (p->live_killed == '*')
- strlcpy (A->g_msg_type, "Object", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Object", sizeof(A->g_data_type_desc));
else if (p->live_killed == '_')
- strlcpy (A->g_msg_type, "Killed Object", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Killed Object", sizeof(A->g_data_type_desc));
else
- strlcpy (A->g_msg_type, "Object - invalid live/killed", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Object - invalid live/killed", sizeof(A->g_data_type_desc));
ts = get_timestamp (A, p->time_stamp);
@@ -1702,7 +1903,7 @@ static void aprs_object (decode_aprs_t *A, unsigned char *info, int ilen)
/* In this case, we expect 7 byte "data extension" */
/* for the wind direction and speed. */
- strlcpy (A->g_msg_type, "Weather Report with Object", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Weather Report with Object", sizeof(A->g_data_type_desc));
weather_data (A, p->comment, TRUE);
}
else {
@@ -1721,7 +1922,7 @@ static void aprs_object (decode_aprs_t *A, unsigned char *info, int ilen)
/* of weather report and object with compressed */
/* position. */
- strlcpy (A->g_msg_type, "Weather Report with Object", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Weather Report with Object", sizeof(A->g_data_type_desc));
weather_data (A, q->comment, FALSE);
}
else {
@@ -1802,15 +2003,15 @@ static void aprs_item (decode_aprs_t *A, unsigned char *info, int ilen)
}
if (p->name[i] == '!')
- strlcpy (A->g_msg_type, "Item", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Item", sizeof(A->g_data_type_desc));
else if (p->name[i] == '_')
- strlcpy (A->g_msg_type, "Killed Item", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Killed Item", sizeof(A->g_data_type_desc));
else {
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Item name too long or not followed by ! or _.\n");
}
- strlcpy (A->g_msg_type, "Object - invalid live/killed", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Object - invalid live/killed", sizeof(A->g_data_type_desc));
}
ppos = p->name + i + 1;
@@ -1854,7 +2055,7 @@ static void aprs_item (decode_aprs_t *A, unsigned char *info, int ilen)
static void aprs_station_capabilities (decode_aprs_t *A, char *info, int ilen)
{
- strlcpy (A->g_msg_type, "Station Capabilities", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Station Capabilities", sizeof(A->g_data_type_desc));
// process_comment() not applicable here because it
// extracts information found in certain formats.
@@ -1941,7 +2142,7 @@ static void aprs_status_report (decode_aprs_t *A, char *info, int ilen)
} *ps;
- strlcpy (A->g_msg_type, "Status Report", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Status Report", sizeof(A->g_data_type_desc));
pt = (struct aprs_status_time_s *)info;
pm4 = (struct aprs_status_m4_s *)info;
@@ -2122,7 +2323,7 @@ static void aprs_general_query (decode_aprs_t *A, char *info, int ilen, int quie
double lat, lon;
float radius;
- strlcpy (A->g_msg_type, "General Query", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "General Query", sizeof(A->g_data_type_desc));
/*
* First make a copy because we will modify it while parsing it.
@@ -2304,7 +2505,7 @@ static void aprs_directed_station_query (decode_aprs_t *A, char *addressee, char
static void aprs_telemetry (decode_aprs_t *A, char *info, int ilen, int quiet)
{
- strlcpy (A->g_msg_type, "Telemetry", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Telemetry", sizeof(A->g_data_type_desc));
telemetry_data_original (A->g_src, info, quiet, A->g_telemetry, sizeof(A->g_telemetry), A->g_comment, sizeof(A->g_comment));
@@ -2328,10 +2529,12 @@ static void aprs_telemetry (decode_aprs_t *A, char *info, int ilen, int quiet)
static void aprs_user_defined (decode_aprs_t *A, char *info, int ilen)
{
- if (strncmp(info, "{tt", 3) == 0) { // Historical. Should probably use DT.
+ if (strncmp(info, "{tt", 3) == 0 || // Historical.
+ strncmp(info, "{DT", 3) == 0) { // Official after registering {D*
aprs_raw_touch_tone (A, info, ilen);
}
- else if (strncmp(info, "{mc", 3) == 0) { // Historical. Should probably use DM.
+ else if (strncmp(info, "{mc", 3) == 0 || // Historical.
+ strncmp(info, "{DM", 3) == 0) { // Official after registering {D*
aprs_morse_code (A, info, ilen);
}
else if (info[0] == '{' && info[1] == USER_DEF_USER_ID && info[2] == USER_DEF_TYPE_AIS) {
@@ -2339,7 +2542,7 @@ static void aprs_user_defined (decode_aprs_t *A, char *info, int ilen)
float knots, course;
float alt_meters;
- ais_parse (info+3, 0, A->g_msg_type, sizeof(A->g_msg_type), A->g_name, sizeof(A->g_name),
+ ais_parse (info+3, 0, A->g_data_type_desc, sizeof(A->g_data_type_desc), A->g_name, sizeof(A->g_name),
&lat, &lon, &knots, &course, &alt_meters, &(A->g_symbol_table), &(A->g_symbol_code),
A->g_comment, sizeof(A->g_comment));
@@ -2351,10 +2554,10 @@ static void aprs_user_defined (decode_aprs_t *A, char *info, int ilen)
strcpy (A->g_mfr, "");
}
else if (strncmp(info, "{{", 2) == 0) {
- snprintf (A->g_msg_type, sizeof(A->g_msg_type), "User-Defined Experimental");
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "User-Defined Experimental");
}
else {
- snprintf (A->g_msg_type, sizeof(A->g_msg_type), "User-Defined Data");
+ snprintf (A->g_data_type_desc, sizeof(A->g_data_type_desc), "User-Defined Data");
}
} /* end aprs_user_defined */
@@ -2379,7 +2582,7 @@ static void aprs_user_defined (decode_aprs_t *A, char *info, int ilen)
static void aprs_raw_touch_tone (decode_aprs_t *A, char *info, int ilen)
{
- strlcpy (A->g_msg_type, "Raw Touch Tone Data", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Raw Touch Tone Data", sizeof(A->g_data_type_desc));
/* Just copy the info field without the message type. */
@@ -2410,7 +2613,7 @@ static void aprs_raw_touch_tone (decode_aprs_t *A, char *info, int ilen)
static void aprs_morse_code (decode_aprs_t *A, char *info, int ilen)
{
- strlcpy (A->g_msg_type, "Morse Code Data", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Morse Code Data", sizeof(A->g_data_type_desc));
/* Just copy the info field without the message type. */
@@ -2450,7 +2653,7 @@ static void aprs_positionless_weather_report (decode_aprs_t *A, unsigned char *i
} *p;
- strlcpy (A->g_msg_type, "Positionless Weather Report", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Positionless Weather Report", sizeof(A->g_data_type_desc));
//time_t ts = 0;
@@ -2855,7 +3058,7 @@ static void aprs_ultimeter (decode_aprs_t *A, char *info, int ilen)
int n;
- strlcpy (A->g_msg_type, "Ultimeter", sizeof(A->g_msg_type));
+ strlcpy (A->g_data_type_desc, "Ultimeter", sizeof(A->g_data_type_desc));
if (*info == '$')
{
@@ -2934,31 +3137,6 @@ static void aprs_ultimeter (decode_aprs_t *A, char *info, int ilen)
} /* end aprs_ultimeter */
-/*------------------------------------------------------------------
- *
- * Function: third_party_header
- *
- * Purpose: Decode packet from a third party network.
- *
- * Inputs: info - Pointer to Information field.
- * ilen - Information field length.
- *
- * Outputs: A->g_comment
- *
- * Description:
- *
- *------------------------------------------------------------------*/
-
-static void third_party_header (decode_aprs_t *A, char *info, int ilen)
-{
-
- strlcpy (A->g_msg_type, "Third Party Header", sizeof(A->g_msg_type));
-
- /* more later? */
-
-} /* end third_party_header */
-
-
/*------------------------------------------------------------------
*
@@ -3494,6 +3672,8 @@ time_t get_timestamp (decode_aprs_t *A, char *p)
time_t ts;
ts = time(NULL);
+ // FIXME: use gmtime_r instead.
+ // Besides not being thread safe, gmtime could possibly return null.
ptm = gmtime(&ts);
pdhm = (void *)p;
@@ -3706,6 +3886,9 @@ static int data_extension_comment (decode_aprs_t *A, char *pdext)
strlcpy (A->g_directivity, dir[pdext[6]-'0'], sizeof(A->g_directivity));
}
+// TODO: look for another 0-9 A-Z followed by a /
+// http://www.aprs.org/aprs12/probes.txt
+
process_comment (A, pdext+7, -1);
return 1;
}
@@ -4034,7 +4217,7 @@ static void substr_se (char *dest, const char *src, int start, int endp1)
*
* !DAO! APRS precision and Datum option.
*
- * Protocol reference, end of chaper 6.
+ * Protocol reference, end of chapter 6.
*
* /A=123456 Altitude
*
@@ -4074,7 +4257,7 @@ static void substr_se (char *dest, const char *src, int start, int endp1)
* This same thing has been observed from others and is intermittent.
*
* * AGW Tracker can send UTF-16 if an option is selected. This can introduce nul bytes.
- * This is wrong. It should be using UTF-8 and I'm not going to accomodate it here.
+ * This is wrong. It should be using UTF-8 and I'm not going to accommodate it here.
*
*
* The digipeater and IGate functions should pass along anything exactly the
@@ -4130,6 +4313,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen)
static regex_t base91_tel_re; /* Base 91 compressed telemetry data. */
+
int e;
char emsg[100];
#define MAXMATCH 4
@@ -4219,9 +4403,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen)
dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
}
-// TODO: Would like to restrict to even length something like this: ([!-{][!-{]){2,7}
-
- e = regcomp (&base91_tel_re, "\\|([!-{]{4,14})\\|", REG_EXTENDED);
+ e = regcomp (&base91_tel_re, "\\|(([!-{][!-{]){2,7})\\|", REG_EXTENDED);
if (e) {
regerror (e, &base91_tel_re, emsg, sizeof(emsg));
dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg);
@@ -4241,7 +4423,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen)
/*
* Watch out for buffer overflow.
* KG6AZZ reports that there is a local digipeater that seems to
- * malfunction ocassionally. It corrupts the packet, as it is
+ * malfunction occasionally. It corrupts the packet, as it is
* digipeated, causing the comment to be hundreds of characters long.
*/
@@ -4419,20 +4601,20 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen)
/*
* Telemetry data, in base 91 compressed format appears as 2 to 7 pairs
- * of base 91 digits, surrounded by | at start and end.
+ * of base 91 digits, ! thru {, surrounded by | at start and end.
*/
if (regexec (&base91_tel_re, A->g_comment, MAXMATCH, match, 0) == 0)
{
- char tdata[30]; /* Should be 4 to 14 characters. */
+ char tdata[30]; /* Should be even number of 4 to 14 characters. */
- //dw_printf("compressed telemetry start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo));
+ //dw_printf("compressed telemetry start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo));
substr_se (tdata, A->g_comment, match[1].rm_so, match[1].rm_eo);
- //dw_printf("compressed telemetry data = \"%s\"\n", tdata);
+ //dw_printf("compressed telemetry data = \"%s\"\n", tdata);
telemetry_data_base91 (A->g_src, tdata, A->g_telemetry, sizeof(A->g_telemetry));
@@ -4448,8 +4630,10 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen)
* This would not make sense to use this with a compressed location which
* already has much greater resolution.
*
- * It surprized me to see this in a MIC-E message.
+ * It surprised me to see this in a MIC-E message.
* MIC-E has resolution of .01 minute so it would make sense to have it as an option.
+ * We also find an example in http://www.aprs.org/aprs12/mic-e-examples.txt
+ * 'abc123R/'123}FFF.FFFMHztext.../A=123456...!DAO! Mv
*/
if (regexec (&dao_re, A->g_comment, MAXMATCH, match, 0) == 0)
@@ -4459,7 +4643,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen)
int a = A->g_comment[match[0].rm_so+2];
int o = A->g_comment[match[0].rm_so+3];
- //dw_printf("start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo));
+ dw_printf("DAO start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo));
/*
@@ -4511,7 +4695,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen)
*/
/*
- * Here is an interesting case.
+ * Here are a couple situations where it is seen.
*
* W8SAT-1>T2UV0P:`qC<0x1f>l!Xu\'"69}WMNI EDS Response Unit #1|+/%0'n|!w:X!|3
*
@@ -4528,13 +4712,26 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen)
* Comment earlier points out that MIC-E format has resolution of 0.01 minute,
* same as non-compressed format, so the DAO does work out, after thinking
* about it for a while.
+ * We also find a MIC-E example with !DAO! here: http://www.aprs.org/aprs12/mic-e-examples.txt
+ *
+ * Another one:
+ *
+ * KS4FUN-12>3X0PRU,W6CX-3,BKELEY,WIDE2*:`2^=l!<0x1c>+/'"48}MT-RTG|%B%p'a|!wqR!|3
+ *
+ * MIC-E, Red Cross, Special
+ * N 38 00.2588, W 122 06.3354
+ * 0 MPH, course 100, alt 108 ft
+ * MT-RTG comment
+ * |%B%p'a| Seq=397, A1=443, A2=610
+ * !wqR! DAO
+ * |3 Byonics TinyTrack3
+ *
*/
/*
* The spec appears to be wrong. It says '}' is the maximum value when it should be '{'.
*/
-
if (isdigit91(a)) {
A->g_lat += (a - B91_MIN) * 1.1 / 600000.0 * sign(A->g_lat);
}
@@ -4904,7 +5101,7 @@ int main (int argc, char *argv[])
hex_dump (kiss_frame, kiss_len);
// Put FEND at end to keep kiss_unwrap happy.
- // Having one at the begining is optional.
+ // Having one at the beginning is optional.
kiss_frame[kiss_len++] = FEND;
@@ -4941,7 +5138,7 @@ int main (int argc, char *argv[])
ax25_safe_print ((char *)pinfo, info_len, 1); // Display non-ASCII to hexadecimal.
dw_printf ("\n");
- decode_aprs (&A, pp, 0); // Extract information into structure.
+ decode_aprs (&A, pp, 0, 0); // Extract information into structure.
decode_aprs_print (&A); // Now print it in human readable format.
@@ -4962,7 +5159,7 @@ int main (int argc, char *argv[])
if (pp != NULL) {
decode_aprs_t A;
- decode_aprs (&A, pp, 0); // Extract information into structure.
+ decode_aprs (&A, pp, 0, 0); // Extract information into structure.
decode_aprs_print (&A); // Now print it in human readable format.
diff --git a/src/decode_aprs.h b/src/decode_aprs.h
index cdbb1678..324be2b9 100644
--- a/src/decode_aprs.h
+++ b/src/decode_aprs.h
@@ -26,9 +26,9 @@ typedef struct decode_aprs_s {
char g_src[AX25_MAX_ADDR_LEN];
- char g_msg_type[60]; /* APRS data type. Telemetry descriptions get pretty long. */
- /* Putting msg in the name was a poor choice because */
- /* "message" has a specific meaning. Rename it someday. */
+ char g_dest[AX25_MAX_ADDR_LEN];
+
+ char g_data_type_desc[100]; /* APRS data type description. Telemetry descriptions get pretty long. */
char g_symbol_table; /* The Symbol Table Identifier character selects one */
/* of the two Symbol Tables, or it may be used as */
@@ -77,9 +77,13 @@ typedef struct decode_aprs_s {
message_subtype_directed_query
} g_message_subtype; /* Various cases of the overloaded "message." */
- char g_message_number[8]; /* Message number. Should be 1 - 5 characters if used. */
+ char g_message_number[8]; /* Message number. Should be 1 - 5 alphanumeric characters if used. */
+ /* Addendum 1.1 has new format {mm} or {mm}aa with only two */
+ /* characters for message number and an ack riding piggyback. */
float g_speed_mph; /* Speed in MPH. */
+ /* The APRS transmission uses knots so watch out for */
+ /* conversions when sending and receiving APRS packets. */
float g_course; /* 0 = North, 90 = East, etc. */
@@ -94,6 +98,9 @@ typedef struct decode_aprs_s {
float g_range; /* Precomputed radio range in miles. */
float g_altitude_ft; /* Feet above median sea level. */
+ /* I used feet here because the APRS specification */
+ /* has units of feet for alititude. Meters would be */
+ /* more natural to the other 96% of the world. */
char g_mfr[80]; /* Manufacturer or application. */
@@ -135,7 +142,7 @@ typedef struct decode_aprs_s {
-extern void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet);
+extern void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, int third_party);
extern void decode_aprs_print (decode_aprs_t *A);
diff --git a/src/demod.c b/src/demod.c
index 281367bc..482c1076 100644
--- a/src/demod.c
+++ b/src/demod.c
@@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
-// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2019 John Langner, WB2OSZ
+// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2019, 2021 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -60,10 +60,6 @@
static struct audio_s *save_audio_config_p;
-// TODO: temp experiment.
-
-
-static int zerostuff = 1; // temp experiment.
// Current state of all the decoders.
@@ -106,7 +102,7 @@ int demod_init (struct audio_s *pa)
for (chan = 0; chan < MAX_CHANS; chan++) {
- if (save_audio_config_p->achan[chan].medium == MEDIUM_RADIO) {
+ if (save_audio_config_p->chan_medium[chan] == MEDIUM_RADIO) {
char *p;
char just_letters[16];
@@ -198,46 +194,57 @@ int demod_init (struct audio_s *pa)
assert (num_letters == (int)(strlen(just_letters)));
/*
- * Pick a good default demodulator if none specified.
+ * Pick a good default demodulator if none specified.
+ * Previously, we had "D" optimized for 300 bps.
+ * Gone in 1.7 so it is always "A+".
*/
if (num_letters == 0) {
+ strlcpy (just_letters, "A", sizeof(just_letters));
+ num_letters = strlen(just_letters);
- if (save_audio_config_p->achan[chan].baud < 600) {
-
- /* This has been optimized for 300 baud. */
-
- strlcpy (just_letters, "D", sizeof(just_letters));
+ if (have_plus != -1) have_plus = 1; // Add as default for version 1.2
+ // If not explicitly turned off.
+ }
- }
- else {
+/*
+ * Special case for ARM.
+ * The higher end ARM chips have loads of power but many people
+ * are using a single core Pi Zero or similar.
+ * (I'm still using a model 1 for my digipeater/IGate!)
+ * Decreasing CPU requirement has a negligible impact on decoding performance.
+ *
+ * atest -PA- 01_Track_1.wav --> 1002 packets decoded.
+ * atest -PA- -D3 01_Track_1.wav --> 997 packets decoded.
+ *
+ * Someone concerned about 1/2 of one percent difference can add "-D 1"
+ */
#if __arm__
- /* We probably don't have a lot of CPU power available. */
- /* Previously we would use F if possible otherwise fall back to A. */
-
- /* In version 1.2, new default is E+ /3. */
- strlcpy (just_letters, "E", sizeof(just_letters)); // version 1.2 now E.
- if (have_plus != -1) have_plus = 1; // Add as default for version 1.2
- // If not explicitly turned off.
- if (save_audio_config_p->achan[chan].decimate == 0) {
- if (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec > 40000) {
- save_audio_config_p->achan[chan].decimate = 3;
- }
- }
-#else
- strlcpy (just_letters, "E", sizeof(just_letters)); // version 1.2 changed C to E.
- if (have_plus != -1) have_plus = 1; // Add as default for version 1.2
- // If not explicitly turned off.
-#endif
+ if (save_audio_config_p->achan[chan].decimate == 0) {
+ if (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec > 40000) {
+ save_audio_config_p->achan[chan].decimate = 3;
}
- num_letters = 1;
}
+#endif
+
+/*
+ * Number of filter taps is proportional to number of audio samples in a "symbol" duration.
+ * These can get extremely large for low speeds, e.g. 300 baud.
+ * In this case, increase the decimation ration. Crude approximation. Could be improved.
+ */
+ if (save_audio_config_p->achan[chan].decimate == 0 &&
+ save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec > 40000 &&
+ save_audio_config_p->achan[chan].baud < 600) {
+ // Avoid enormous number of filter taps.
+
+ save_audio_config_p->achan[chan].decimate = 3;
+ }
- assert (num_letters == (int)(strlen(just_letters)));
/*
* Put it back together again.
*/
+ assert (num_letters == (int)(strlen(just_letters)));
/* At this point, have_plus can have 3 values: */
/* 1 = turned on, either explicitly or by applied default */
@@ -286,7 +293,7 @@ int demod_init (struct audio_s *pa)
if (save_audio_config_p->achan[chan].decimate == 0) {
save_audio_config_p->achan[chan].decimate = 1;
- if (strchr (just_letters, 'D') != NULL && save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec > 40000) {
+ if (strchr (just_letters, 'B') != NULL && save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec > 40000) {
save_audio_config_p->achan[chan].decimate = 3;
}
}
@@ -492,7 +499,7 @@ int demod_init (struct audio_s *pa)
// In versions 1.4 and 1.5, V.26 "Alternative A" was used.
// years later, I discover that the MFJ-2400 used "Alternative B."
// It looks like the other two manufacturers use the same but we
- // can't be sure until we find one for compatbility testing.
+ // can't be sure until we find one for compatibility testing.
// In version 1.6 we add a choice for the user.
// If neither one was explicitly specified, print a message and take
@@ -503,8 +510,8 @@ int demod_init (struct audio_s *pa)
text_color_set(DW_COLOR_ERROR);
dw_printf ("Two incompatible versions of 2400 bps QPSK are now available.\n");
- dw_printf ("For compatbility with direwolf <= 1.5, use 'V26A' modem option in config file.\n");
- dw_printf ("For compatbility MFJ-2400 use 'V26B' modem option in config file.\n");
+ dw_printf ("For compatibility with direwolf <= 1.5, use 'V26A' modem option in config file.\n");
+ dw_printf ("For compatibility MFJ-2400 use 'V26B' modem option in config file.\n");
dw_printf ("Command line options -j and -J can be used for channel 0.\n");
dw_printf ("For more information, read the Dire Wolf User Guide and\n");
dw_printf ("2400-4800-PSK-for-APRS-Packet-Radio.pdf.\n");
@@ -665,12 +672,6 @@ int demod_init (struct audio_s *pa)
strlcpy (save_audio_config_p->achan[chan].profiles, "+", sizeof(save_audio_config_p->achan[chan].profiles));
}
-
-#ifdef TUNE_ZEROSTUFF
- zerostuff = TUNE_ZEROSTUFF;
-#endif
-
-
/*
* We need a minimum number of audio samples per bit time for good performance.
* Easier to check here because demod_9600_init might have an adjusted sample rate.
@@ -685,26 +686,32 @@ int demod_init (struct audio_s *pa)
if (save_audio_config_p->achan[chan].upsample == 0) {
- if (ratio < 5) {
+ if (ratio < 4) {
- // example: 44100 / 9600 is 4.59
- // Big improvement with x2.
- // x4 seems to work the best.
- // The other parameters are not as touchy.
- // Might reduce on ARM if it takes too much CPU power.
+ // This is extreme.
+ // No one should be using a sample rate this low but
+ // amazingly a recording with 22050 rate can be decoded.
+ // 3 and 4 are the same. Need more tests.
save_audio_config_p->achan[chan].upsample = 4;
}
+ else if (ratio < 5) {
+
+ // example: 44100 / 9600 is 4.59
+ // 3 is slightly better than 2 or 4.
+
+ save_audio_config_p->achan[chan].upsample = 3;
+ }
else if (ratio < 10) {
- // 48000 / 9600 is 5.00
- // Need more reasearch. Treat like above for now.
+ // example: 48000 / 9600 = 5
+ // 3 is slightly better than 2 or 4.
- save_audio_config_p->achan[chan].upsample = 4;
+ save_audio_config_p->achan[chan].upsample = 3;
}
else if (ratio < 15) {
- // ...
+ // ... guessing
save_audio_config_p->achan[chan].upsample = 2;
}
@@ -775,7 +782,8 @@ int demod_init (struct audio_s *pa)
}
demod_9600_init (save_audio_config_p->achan[chan].modem_type,
- save_audio_config_p->achan[chan].upsample * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec,
+ save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec,
+ save_audio_config_p->achan[chan].upsample,
save_audio_config_p->achan[chan].baud, D);
if (strchr(save_audio_config_p->achan[chan].profiles, '+') != NULL) {
@@ -796,10 +804,23 @@ int demod_init (struct audio_s *pa)
} /* switch on modulation type. */
- } /* if channel number is valid */
+ } /* if channel medium is radio */
+
+// FIXME dw_printf ("-------- end of loop for chn %d \n", chan);
} /* for chan ... */
+ // Now the virtual channels. FIXME: could be single loop.
+
+ for (chan = MAX_CHANS; chan < MAX_TOTAL_CHANS; chan++) {
+
+// FIXME dw_printf ("-------- virtual channel loop %d \n", chan);
+
+ if (chan == save_audio_config_p->igate_vchannel) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("Channel %d: IGate virtual channel.\n", chan);
+ }
+ }
return (0);
@@ -821,7 +842,7 @@ int demod_init (struct audio_s *pa)
* Global In: save_audio_config_p->adev[ACHAN2ADEV(chan)].bits_per_sample - So we know whether to
* read 1 or 2 bytes from audio stream.
*
- * Description: Grab 1 or two btyes depending on data source.
+ * Description: Grab 1 or two bytes depending on data source.
*
* When processing stereo, the caller will call this
* at twice the normal rate to obtain alternating left
@@ -836,7 +857,7 @@ __attribute__((hot))
int demod_get_sample (int a)
{
int x1, x2;
- signed short sam; /* short to force sign extention. */
+ signed short sam; /* short to force sign extension. */
assert (save_audio_config_p->adev[a].bits_per_sample == 8 || save_audio_config_p->adev[a].bits_per_sample == 16);
@@ -913,7 +934,7 @@ __attribute__((hot))
void demod_process_sample (int chan, int subchan, int sam)
{
float fsam;
- int k;
+ //int k;
struct demodulator_state_s *D;
@@ -1005,47 +1026,7 @@ void demod_process_sample (int chan, int subchan, int sam)
case MODEM_AIS:
default:
- if (zerostuff) {
- /* Literature says this is better if followed */
- /* by appropriate low pass filter. */
- /* So far, both are same in tests with different */
- /* optimal low pass filter parameters. */
-
- for (k=1; kachan[chan].upsample; k++) {
- demod_9600_process_sample (chan, 0, D);
- }
- demod_9600_process_sample (chan, sam * save_audio_config_p->achan[chan].upsample, D);
- }
- else {
-
- /* Linear interpolation. */
- static int prev_sam;
-
- switch (save_audio_config_p->achan[chan].upsample) {
- case 1:
- demod_9600_process_sample (chan, sam, D);
- break;
- case 2:
- demod_9600_process_sample (chan, (prev_sam + sam) / 2, D);
- demod_9600_process_sample (chan, sam, D);
- break;
- case 3:
- demod_9600_process_sample (chan, (2 * prev_sam + sam) / 3, D);
- demod_9600_process_sample (chan, (prev_sam + 2 * sam) / 3, D);
- demod_9600_process_sample (chan, sam, D);
- break;
- case 4:
- demod_9600_process_sample (chan, (3 * prev_sam + sam) / 4, D);
- demod_9600_process_sample (chan, (prev_sam + sam) / 2, D);
- demod_9600_process_sample (chan, (prev_sam + 3 * sam) / 4, D);
- demod_9600_process_sample (chan, sam, D);
- break;
- default:
- assert (0);
- break;
- }
- prev_sam = sam;
- }
+ demod_9600_process_sample (chan, sam, save_audio_config_p->achan[chan].upsample, D);
break;
} /* switch modem_type */
diff --git a/src/demod_9600.c b/src/demod_9600.c
index 2f989830..705d1fa7 100644
--- a/src/demod_9600.c
+++ b/src/demod_9600.c
@@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
-// Copyright (C) 2011, 2012, 2013, 2015, 2019 John Langner, WB2OSZ
+// Copyright (C) 2011, 2012, 2013, 2015, 2019, 2021 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -25,7 +25,8 @@
*
* Module: demod_9600.c
*
- * Purpose: Demodulator for scrambled baseband encoding.
+ * Purpose: Demodulator for baseband signal.
+ * This is used for AX.25 (with scrambling) and IL2P without.
*
* Input: Audio samples from either a file or the "sound card."
*
@@ -45,12 +46,14 @@
#include
// Fine tuning for different demodulator types.
+// Don't remove this section. It is here for a reason.
+
+#define DCD_THRESH_ON 32 // Hysteresis: Can miss 0 out of 32 for detecting lock.
+ // This is best for actual on-the-air signals.
+ // Still too many brief false matches.
+#define DCD_THRESH_OFF 8 // Might want a little more fine tuning.
+#define DCD_GOOD_WIDTH 1024 // No more than 1024!!!
-#define DCD_THRESH_ON 32 // Hysteresis: Can miss 0 out of 32 for detecting lock.
- // This is best for actual on-the-air signals.
- // Still too many brief false matches.
-#define DCD_THRESH_OFF 8 // Might want a little more fine tuning.
-#define DCD_GOOD_WIDTH 1024 // No more than 1024!!!
#include "fsk_demod_state.h" // Values above override defaults.
#include "tune.h"
@@ -125,9 +128,12 @@ static inline float agc (float in, float fast_attack, float slow_decay, float *p
*
* Inputs: modem_type - Determines whether scrambling is used.
*
- * samples_per_sec - Number of samples per second.
- * Might be upsampled in hopes of
- * reducing the PLL jitter.
+ * samples_per_sec - Number of samples per second for audio.
+ *
+ * upsample - Factor to upsample the incoming stream.
+ * After a lot of experimentation, I discovered that
+ * it works better if the data is upsampled.
+ * This reduces the jitter for PLL synchronization.
*
* baud - Data rate in bits per second.
*
@@ -137,10 +143,13 @@ static inline float agc (float in, float fast_attack, float slow_decay, float *p
*
*----------------------------------------------------------------*/
-void demod_9600_init (enum modem_t modem_type, int samples_per_sec, int baud, struct demodulator_state_s *D)
+void demod_9600_init (enum modem_t modem_type, int original_sample_rate, int upsample, int baud, struct demodulator_state_s *D)
{
float fc;
int j;
+ if (upsample < 1) upsample = 1;
+ if (upsample > 4) upsample = 4;
+
memset (D, 0, sizeof(struct demodulator_state_s));
D->modem_type = modem_type;
@@ -155,12 +164,13 @@ void demod_9600_init (enum modem_t modem_type, int samples_per_sec, int baud, st
// case 'L': // upsample x4 with filtering.
- D->lp_filter_len_bits = 1.0;
+ D->lp_filter_len_bits = 1.0; // -U4 = 61 4.59 samples/symbol
// Works best with odd number in some tests. Even is better in others.
- //D->lp_filter_size = ((int) (0.5f * ( D->lp_filter_len_bits * (float)samples_per_sec / (float)baud ))) * 2 + 1;
+ //D->lp_filter_size = ((int) (0.5f * ( D->lp_filter_len_bits * (float)original_sample_rate / (float)baud ))) * 2 + 1;
- D->lp_filter_size = (int) (( D->lp_filter_len_bits * (float)samples_per_sec / baud) + 0.5f);
+ // Just round to nearest integer.
+ D->lp_filter_size = (int) (( D->lp_filter_len_bits * (float)original_sample_rate / baud) + 0.5f);
D->lp_window = BP_WINDOW_COSINE;
@@ -185,8 +195,11 @@ void demod_9600_init (enum modem_t modem_type, int samples_per_sec, int baud, st
dw_printf ("samples per bit = %.1f\n", (double)samples_per_sec / baud);
#endif
+
+ // PLL needs to use the upsampled rate.
+
D->pll_step_per_sample =
- (int) round(TICKS_PER_PLL_CYCLE * (double) baud / (double)samples_per_sec);
+ (int) round(TICKS_PER_PLL_CYCLE * (double) baud / (double)(original_sample_rate * upsample));
#ifdef TUNE_LP_WINDOW
@@ -217,13 +230,87 @@ void demod_9600_init (enum modem_t modem_type, int samples_per_sec, int baud, st
D->pll_searching_inertia = TUNE_PLL_SEARCHING;
#endif
- fc = (float)baud * D->lpf_baud / (float)samples_per_sec;
+ // Initial filter (before scattering) is based on upsampled rate.
+
+ fc = (float)baud * D->lpf_baud / (float)(original_sample_rate * upsample);
//dw_printf ("demod_9600_init: call gen_lowpass(fc=%.2f, , size=%d, )\n", fc, D->lp_filter_size);
- (void)gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window, 0);
+ gen_lowpass (fc, D->u.bb.lp_filter, D->lp_filter_size * upsample, D->lp_window);
+
+// New in 1.7 -
+// Use a polyphase filter to reduce the CPU load.
+// Originally I used zero stuffing to upsample.
+// Here is the general idea.
+//
+// Suppose the input samples are 1 2 3 4 5 6 7 8 9 ...
+// Filter coefficients are a b c d e f g h i ...
+//
+// With original sampling rate, the filtering would involve multiplying and adding:
+//
+// 1a 2b 3c 4d 5e 6f ...
+//
+// When upsampling by 3, each of these would need to be evaluated
+// for each audio sample:
+//
+// 1a 0b 0c 2d 0e 0f 3g 0h 0i ...
+// 0a 1b 0c 0d 2e 0f 0g 3h 0i ...
+// 0a 0b 1c 0d 0e 2f 0g 0h 3i ...
+//
+// 2/3 of the multiplies are always by a stuffed zero.
+// We can do this more efficiently by removing them.
+//
+// 1a 2d 3g ...
+// 1b 2e 3h ...
+// 1c 2f 3i ...
+//
+// We scatter the original filter across multiple shorter filters.
+// Each input sample cycles around them to produce the upsampled rate.
+//
+// a d g ...
+// b e h ...
+// c f i ...
+//
+// There are countless sources of information DSP but this one is unique
+// in that it is a college course that mentions APRS.
+// https://www2.eecs.berkeley.edu/Courses/EE123
+//
+// Was the effort worthwhile? Times on an RPi 3.
+//
+// command: atest -B9600 ~/walkabout9600[abc]-compressed*.wav
+//
+// These are 3 recordings of a portable system being carried out of
+// range and back in again. It is a real world test for weak signals.
+//
+// options num decoded seconds x realtime
+// 1.6 1.7 1.6 1.7 1.6 1.7
+// --- --- --- --- --- ---
+// -P- 171 172 23.928 17.967 14.9 19.9
+// -P+ 180 180 54.688 48.772 6.5 7.3
+// -P- -F1 177 178 32.686 26.517 10.9 13.5
+//
+// So, it turns out that -P+ doesn't have a dramatic improvement, only
+// around 4%, for drastically increased CPU requirements.
+// Maybe we should turn that off by default, especially for ARM.
+//
+
+ int k = 0;
+ for (int i = 0; i < D->lp_filter_size; i++) {
+ D->u.bb.lp_polyphase_1[i] = D->u.bb.lp_filter[k++];
+ if (upsample >= 2) {
+ D->u.bb.lp_polyphase_2[i] = D->u.bb.lp_filter[k++];
+ if (upsample >= 3) {
+ D->u.bb.lp_polyphase_3[i] = D->u.bb.lp_filter[k++];
+ if (upsample >= 4) {
+ D->u.bb.lp_polyphase_4[i] = D->u.bb.lp_filter[k++];
+ }
+ }
+ }
+ }
+
/* Version 1.2: Experiment with different slicing levels. */
+ // Really didn't help that much because we should have a symmetrical signal.
for (j = 0; j < MAX_SUBCHANS; j++) {
slice_point[j] = 0.02f * (j - 0.5f * (MAX_SUBCHANS-1));
@@ -259,7 +346,7 @@ void demod_9600_init (enum modem_t modem_type, int samples_per_sec, int baud, st
* been distorted by going thru voice transceivers not
* intended to pass this sort of "audio" signal.
*
- * Data is "scrambled" to reduce the amount of DC bias.
+ * For G3RUH mode, data is "scrambled" to reduce the amount of DC bias.
* The data stream must be unscrambled at the receiving end.
*
* We also have a digital phase locked loop (PLL)
@@ -276,6 +363,9 @@ void demod_9600_init (enum modem_t modem_type, int samples_per_sec, int baud, st
* of the function to be called for each bit recovered
* from the demodulator. For now, it's simply hard-coded.
*
+ * After experimentation, I found that this works better if
+ * the original signal is upsampled by 2x or even 4x.
+ *
* References: 9600 Baud Packet Radio Modem Design
* http://www.amsat.org/amsat/articles/g3ruh/109.html
*
@@ -290,63 +380,57 @@ void demod_9600_init (enum modem_t modem_type, int samples_per_sec, int baud, st
inline static void nudge_pll (int chan, int subchan, int slice, float demod_out, struct demodulator_state_s *D);
+static void process_filtered_sample (int chan, float fsam, struct demodulator_state_s *D);
+
+
__attribute__((hot))
-void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D)
+void demod_9600_process_sample (int chan, int sam, int upsample, struct demodulator_state_s *D)
{
-
float fsam;
- float amp;
- float demod_out;
#if DEBUG4
static FILE *demod_log_fp = NULL;
static int log_file_seq = 0; /* Part of log file name */
#endif
-
int subchan = 0;
- int demod_data; /* Still scrambled. */
-
assert (chan >= 0 && chan < MAX_CHANS);
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
-
-/*
- * Filters use last 'filter_size' samples.
- *
- * First push the older samples down.
- *
- * Finally, put the most recent at the beginning.
- *
- * Future project? Rather than shifting the samples,
- * it might be faster to add another variable to keep
- * track of the most recent sample and change the
- * indexing in the later loops that multipy and add.
- */
-
/* Scale to nice number for convenience. */
/* Consistent with the AFSK demodulator, we'd like to use */
/* only half of the dynamic range to have some headroom. */
/* i.e. input range +-16k becomes +-1 here and is */
/* displayed in the heard line as audio level 100. */
- fsam = sam / 16384.0;
-
-#if defined(TUNE_ZEROSTUFF) && TUNE_ZEROSTUFF == 0
-// experiment - no filtering.
-
- amp = fsam;
+ fsam = (float)sam / 16384.0f;
+
+ // Low pass filter
+ push_sample (fsam, D->u.bb.audio_in, D->lp_filter_size);
+
+ fsam = convolve (D->u.bb.audio_in, D->u.bb.lp_polyphase_1, D->lp_filter_size);
+ process_filtered_sample (chan, fsam, D);
+ if (upsample >= 2) {
+ fsam = convolve (D->u.bb.audio_in, D->u.bb.lp_polyphase_2, D->lp_filter_size);
+ process_filtered_sample (chan, fsam, D);
+ if (upsample >= 3) {
+ fsam = convolve (D->u.bb.audio_in, D->u.bb.lp_polyphase_3, D->lp_filter_size);
+ process_filtered_sample (chan, fsam, D);
+ if (upsample >= 4) {
+ fsam = convolve (D->u.bb.audio_in, D->u.bb.lp_polyphase_4, D->lp_filter_size);
+ process_filtered_sample (chan, fsam, D);
+ }
+ }
+ }
+}
-#else
- push_sample (fsam, D->raw_cb, D->lp_filter_size);
-/*
- * Low pass filter to reduce noise yet pass the data.
- */
+__attribute__((hot))
+static void process_filtered_sample (int chan, float fsam, struct demodulator_state_s *D)
+{
- amp = convolve (D->raw_cb, D->lp_filter, D->lp_filter_size);
-#endif
+ int subchan = 0;
/*
* Version 1.2: Capture the post-filtering amplitude for display.
@@ -359,18 +443,18 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D
// TODO: probably no need for this. Just use D->m_peak, D->m_valley
- if (amp >= D->alevel_mark_peak) {
- D->alevel_mark_peak = amp * D->quick_attack + D->alevel_mark_peak * (1.0f - D->quick_attack);
+ if (fsam >= D->alevel_mark_peak) {
+ D->alevel_mark_peak = fsam * D->quick_attack + D->alevel_mark_peak * (1.0f - D->quick_attack);
}
else {
- D->alevel_mark_peak = amp * D->sluggish_decay + D->alevel_mark_peak * (1.0f - D->sluggish_decay);
+ D->alevel_mark_peak = fsam * D->sluggish_decay + D->alevel_mark_peak * (1.0f - D->sluggish_decay);
}
- if (amp <= D->alevel_space_peak) {
- D->alevel_space_peak = amp * D->quick_attack + D->alevel_space_peak * (1.0f - D->quick_attack);
+ if (fsam <= D->alevel_space_peak) {
+ D->alevel_space_peak = fsam * D->quick_attack + D->alevel_space_peak * (1.0f - D->quick_attack);
}
else {
- D->alevel_space_peak = amp * D->sluggish_decay + D->alevel_space_peak * (1.0f - D->sluggish_decay);
+ D->alevel_space_peak = fsam * D->sluggish_decay + D->alevel_space_peak * (1.0f - D->sluggish_decay);
}
/*
@@ -381,12 +465,14 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D
* This works by looking at the minimum and maximum signal peaks
* and scaling the results to be roughly in the -1.0 to +1.0 range.
*/
+ float demod_out;
+ int demod_data; /* Still scrambled. */
- demod_out = agc (amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley));
+ demod_out = agc (fsam, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley));
// TODO: There is potential for multiple decoders with one filter.
-//dw_printf ("peak=%.2f valley=%.2f amp=%.2f norm=%.2f\n", D->m_peak, D->m_valley, amp, norm);
+//dw_printf ("peak=%.2f valley=%.2f fsam=%.2f norm=%.2f\n", D->m_peak, D->m_valley, fsam, norm);
if (D->num_slicers <= 1) {
@@ -435,7 +521,7 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D
fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.3f, %d, %.2f\n",
fsam + 6,
- amp + 4,
+ fsam + 4,
D->m_peak + 4,
D->m_valley + 4,
demod_out + 2,
@@ -478,7 +564,7 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D
*
* Returns: None
*
- * Descripton: A PLL is used to sample near the centers of the data bits.
+ * Description: A PLL is used to sample near the centers of the data bits.
*
* D->data_clock_pll is a SIGNED 32 bit variable.
* When it overflows from a large positive value to a negative value, we
diff --git a/src/demod_9600.h b/src/demod_9600.h
index ac3e7474..51fc15e4 100644
--- a/src/demod_9600.h
+++ b/src/demod_9600.h
@@ -6,9 +6,9 @@
#include "fsk_demod_state.h"
-void demod_9600_init (enum modem_t modem_type, int samples_per_sec, int baud, struct demodulator_state_s *D);
+void demod_9600_init (enum modem_t modem_type, int original_sample_rate, int upsample, int baud, struct demodulator_state_s *D);
-void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D);
+void demod_9600_process_sample (int chan, int sam, int upsample, struct demodulator_state_s *D);
diff --git a/src/demod_afsk.c b/src/demod_afsk.c
index 7a007d1f..f32137c5 100644
--- a/src/demod_afsk.c
+++ b/src/demod_afsk.c
@@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
-// Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ
+// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2020 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -23,8 +23,9 @@
// #define DEBUG3 1 /* print carrier detect changes. */
// #define DEBUG4 1 /* capture AFSK demodulator output to log files */
+ /* Can be used to make nice plots. */
-// #define DEBUG5 1 /* capture 9600 output to log files */
+// #define DEBUG5 1 // Write just demodulated bit stream to file. */
/*------------------------------------------------------------------
@@ -62,15 +63,32 @@
#define MIN(a,b) ((a)<(b)?(a):(b))
#define MAX(a,b) ((a)>(b)?(a):(b))
+#define TUNE(envvar,param,name,fmt) { \
+ char *e = getenv(envvar); \
+ if (e != NULL) { \
+ param = atof(e); \
+ text_color_set (DW_COLOR_ERROR); \
+ dw_printf ("TUNE: " name " = " fmt "\n", param); \
+ } }
-/* Quick approximation to sqrt(x*x+y*y) */
+
+// Cosine table indexed by unsigned byte.
+static float fcos256_table[256];
+
+#define fcos256(x) (fcos256_table[((x)>>24)&0xff])
+#define fsin256(x) (fcos256_table[(((x)>>24)-64)&0xff])
+
+static void nudge_pll (int chan, int subchan, int slice, float demod_out, struct demodulator_state_s *D, float amplitude);
+
+
+/* Quick approximation to sqrt(x*x + y*y) */
/* No benefit for regular PC. */
-/* Should help with microcomputer platform. */
+/* Might help with microcomputer platform??? */
-#if 0 // not using anymore
__attribute__((hot)) __attribute__((always_inline))
-static inline float z (float x, float y)
+static inline float fast_hypot(float x, float y)
{
+#if 0
x = fabsf(x);
y = fabsf(y);
@@ -80,8 +98,11 @@ static inline float z (float x, float y)
else {
return (y * .941246f + x * .41f);
}
-}
+#else
+ return (hypotf(x,y));
#endif
+}
+
/* Add sample to buffer and shift the rest down. */
@@ -96,22 +117,31 @@ static inline void push_sample (float val, float *buff, int size)
/* FIR filter kernel. */
__attribute__((hot)) __attribute__((always_inline))
-static inline float convolve (const float *__restrict__ data, const float *__restrict__ filter, int filter_size)
+static inline float convolve (const float *__restrict__ data, const float *__restrict__ filter, int filter_taps)
{
float sum = 0.0f;
int j;
-
//#pragma GCC ivdep // ignored until gcc 4.9
- for (j=0; j *ppeak) x = *ppeak; // experiment: clip to envelope?
+ if (x < *pvalley) x = *pvalley;
+#endif
if (*ppeak > *pvalley) {
- return ((in - 0.5f * (*ppeak + *pvalley)) / (*ppeak - *pvalley));
+
+ return ((x - 0.5f * (*ppeak + *pvalley)) / (*ppeak - *pvalley)); // my original AGC
+
+ //return (( x - 0.5f * (*ppeak + *pvalley )) * ( *ppeak - *pvalley )); // see note below.
+ //return (x - 0.5f * (*ppeak + *pvalley)); // not as good either.
}
return (0.0f);
}
+// K6JQ pointed me to this wonderful article:
+// Improved Automatic Threshold Correction Methods for FSK by Kok Chen, W7AY.
+// http://www.w7ay.net/site/Technical/ATC/index.html
+//
+// The stated problem is a little different, selective fading for HF RTTY, but the
+// general idea is the similar: Compensating for imbalance of the two tones.
+//
+// The stronger tone probably has a better S/N ratio so we apply a larger
+// weight to it. Effectively it is comparing power rather than amplitude.
+// This is the optimal method from the article referenced.
+//
+// Interesting idea but it did not work as well as the original AGC in this case.
+// For VHF FM we are not dealing with rapid deep selective fading of one tone.
+// Instead we have an imbalance which is the same for the whole frame.
+// It might be interesting to try this with HF SSB packet which is much like RTTY.
+//
+// I use the term valley rather than noise floor.
+// After a little algebra, it looks remarkably similar to the function above.
+//
+// return (( x - valley ) * ( peak - valley ) - 0.5f * ( peak - valley ) * ( peak - valley ));
+// return (( x - valley ) - 0.5f * ( peak - valley )) * ( peak - valley ));
+// return (( x - 0.5f * (peak + valley )) * ( peak - valley ));
+
+
+
/*
* for multi-slicer experiment.
*/
@@ -162,11 +226,7 @@ static inline float agc (float in, float fast_attack, float slow_decay, float *p
*
* D - Pointer to demodulator state for given channel.
*
- * Outputs: D->ms_filter_size
- * D->m_sin_table[]
- * D->m_cos_table[]
- * D->s_sin_table[]
- * D->s_cos_table[]
+ * Outputs:
*
* Returns: None.
*
@@ -180,6 +240,10 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq,
{
int j;
+
+ for (j = 0; j < 256; j++) {
+ fcos256_table[j] = cosf((float)j * 2.0f * (float)M_PI / 256.0f);
+ }
memset (D, 0, sizeof(struct demodulator_state_s));
D->num_slicers = 1;
@@ -188,128 +252,162 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq,
dw_printf ("demod_afsk_init (rate=%d, baud=%d, mark=%d, space=%d, profile=%c\n",
samples_per_sec, baud, mark_freq, space_freq, profile);
#endif
-
-#ifdef TUNE_PROFILE
- profile = TUNE_PROFILE;
-#endif
+ D->profile = profile;
+ switch (D->profile) {
- D->profile = profile; // so we know whether to take fast path later.
+ case 'A': // Official name
+ case 'E': // For compatibility during transition
- switch (profile) {
+ D->profile = 'A';
- case 'D':
-
- /* Prefilter, Cosine window, FIR lowpass. Tweeked for 300 baud. */
+ /* New in version 1.7 */
+ /* This is a simpler version of what has been used all along. */
+ /* Rather than convolving each sample with a pre-computed mark and */
+ /* space filter, we have two free running local oscillators. */
+ /* Also see if we can do better with a Root Raised Cosine filter */
+ /* which supposedly reduces intersymbol interference. */
D->use_prefilter = 1; /* first, a bandpass filter. */
- D->prefilter_baud = 0.87;
- D->pre_filter_len_bits = 1.857;
- D->pre_window = BP_WINDOW_COSINE;
- D->ms_filter_len_bits = 1.857; /* 91 @ 44100/3, 300 */
- D->ms_window = BP_WINDOW_COSINE;
-
- //D->bp_window = BP_WINDOW_COSINE;
+ if (baud > 600) {
+ D->prefilter_baud = 0.155;
+ // Low cutoff below mark, high cutoff above space
+ // as fraction of the symbol rate.
+ // Intuitively you might expect this to be about
+ // half the symbol rate, e.g. 600 Hz outside
+ // the two tones of interest for 1200 baud.
+ // It turns out that narrower is better.
+
+ D->pre_filter_len_sym = 383 * 1200. / 44100.; // about 8 symbols
+ D->pre_window = BP_WINDOW_TRUNCATED;
+ }
+ else {
+ D->prefilter_baud = 0.87; // TOTO: fine tune
+ D->pre_filter_len_sym = 1.857;
+ D->pre_window = BP_WINDOW_COSINE;
+ }
+
+ // Local oscillators for Mark and Space tones.
+
+ D->u.afsk.m_osc_phase = 0;
+ D->u.afsk.m_osc_delta = round ( pow(2., 32.) * (double)mark_freq / (double)samples_per_sec );
+
+ D->u.afsk.s_osc_phase = 0;
+ D->u.afsk.s_osc_delta = round ( pow(2., 32.) * (double)space_freq / (double)samples_per_sec );
+
+ D->u.afsk.use_rrc = 1;
+ TUNE("TUNE_USE_RRC", D->u.afsk.use_rrc, "use_rrc", "%d")
+
+ if (D->u.afsk.use_rrc) {
+ D->u.afsk.rrc_width_sym = 2.80;
+ D->u.afsk.rrc_rolloff = 0.20;
+ }
+ else {
+ D->lpf_baud = 0.14;
+ D->lp_filter_width_sym = 1.388;
+ D->lp_window = BP_WINDOW_TRUNCATED;
+ }
- D->lpf_use_fir = 1;
- D->lpf_baud = 1.10;
- D->lp_filter_len_bits = D->ms_filter_len_bits;
- D->lp_window = BP_WINDOW_TRUNCATED;
-
- D->agc_fast_attack = 0.495;
- D->agc_slow_decay = 0.00022;
- D->hysteresis = 0.027;
+ D->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_slow_decay = 0.000090;
- D->pll_locked_inertia = 0.620;
- D->pll_searching_inertia = 0.350;
+ D->pll_locked_inertia = 0.74;
+ D->pll_searching_inertia = 0.50;
break;
- case 'F': // removed obsolete. treat as E for now.
- case 'E':
+ case 'B': // official name
+ case 'D': // backward compatibility
- /* 1200 baud - Started out similar to C but add prefilter. */
- /* Version 1.2 */
- /* Enhancements: */
- /* + Add prefilter. Previously used for 300 baud D, but not 1200. */
- /* + Prefilter length now independent of M/S filters. */
- /* + Lowpass filter length now independent of M/S filters. */
- /* + Allow mixed window types. */
+ D->profile = 'B';
- //D->bp_window = BP_WINDOW_COSINE; /* The name says BP but it is used for all of them. */
+ // Experiment for version 1.7.
+ // Up to this point, I've always used separate mark and space
+ // filters and compared the amplitudes.
+ // Another technique for an FM demodulator is to mix with
+ // the center frequency and look for the rate of change of the phase.
D->use_prefilter = 1; /* first, a bandpass filter. */
- D->prefilter_baud = 0.23;
- D->pre_filter_len_bits = 156 * 1200. / 44100.;
- D->pre_window = BP_WINDOW_TRUNCATED;
-
- D->ms_filter_len_bits = 74 * 1200. / 44100.;
- D->ms_window = BP_WINDOW_COSINE;
-
- D->lpf_use_fir = 1;
- D->lpf_baud = 1.18;
- D->lp_filter_len_bits = 63 * 1200. / 44100.;
- D->lp_window = BP_WINDOW_TRUNCATED;
- //D->agc_fast_attack = 0.300;
- //D->agc_slow_decay = 0.000185;
- D->agc_fast_attack = 0.820;
- D->agc_slow_decay = 0.000214;
- D->hysteresis = 0.01;
+ if (baud > 600) {
+ D->prefilter_baud = 0.19;
+ // Low cutoff below mark, high cutoff above space
+ // as fraction of the symbol rate.
+ // Intuitively you might expect this to be about
+ // half the symbol rate, e.g. 600 Hz outside
+ // the two tones of interest for 1200 baud.
+ // It turns out that narrower is better.
+
+ D->pre_filter_len_sym = 8.163; // Filter length in symbol times.
+ D->pre_window = BP_WINDOW_TRUNCATED;
+ }
+ else {
+ D->prefilter_baud = 0.87; // TOTO: fine tune
+ D->pre_filter_len_sym = 1.857;
+ D->pre_window = BP_WINDOW_COSINE;
+ }
+
+ // Local oscillator for Center frequency.
+
+ D->u.afsk.c_osc_phase = 0;
+ D->u.afsk.c_osc_delta = round ( pow(2., 32.) * 0.5 * (mark_freq + space_freq) / (double)samples_per_sec );
+
+ D->u.afsk.use_rrc = 1;
+ TUNE("TUNE_USE_RRC", D->u.afsk.use_rrc, "use_rrc", "%d")
+
+ if (D->u.afsk.use_rrc) {
+ D->u.afsk.rrc_width_sym = 2.00;
+ D->u.afsk.rrc_rolloff = 0.40;
+ }
+ else {
+ D->lpf_baud = 0.5;
+ D->lp_filter_width_sym = 1.714286; // 63 * 1200. / 44100.;
+ D->lp_window = BP_WINDOW_TRUNCATED;
+ }
+
+ // 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->pll_locked_inertia = 0.57;
- //D->pll_searching_inertia = 0.33;
D->pll_locked_inertia = 0.74;
D->pll_searching_inertia = 0.50;
+
+ D->alevel_mark_peak = -1; // FIXME: disable display
+ D->alevel_space_peak = -1;
break;
default:
text_color_set(DW_COLOR_ERROR);
- dw_printf ("Invalid filter profile = %c\n", profile);
+ dw_printf ("Invalid AFSK demodulator profile = %c\n", profile);
exit (1);
}
-#ifdef TUNE_PRE_WINDOW
- D->pre_window = TUNE_PRE_WINDOW;
-#endif
-#ifdef TUNE_MS_WINDOW
- D->ms_window = TUNE_MS_WINDOW;
-#endif
-#ifdef TUNE_MS2_WINDOW
- D->ms2_window = TUNE_MS2_WINDOW;
-#endif
-#ifdef TUNE_LP_WINDOW
- D->lp_window = TUNE_LP_WINDOW;
-#endif
+ TUNE("TUNE_PRE_BAUD", D->prefilter_baud, "prefilter_baud", "%.3f")
+ TUNE("TUNE_PRE_WINDOW", D->pre_window, "pre_window", "%d")
+
+ TUNE("TUNE_LPF_BAUD", D->lpf_baud, "lpf_baud", "%.3f")
+ TUNE("TUNE_LP_WINDOW", D->lp_window, "lp_window", "%d")
+
+ TUNE("TUNE_RRC_ROLLOFF", D->u.afsk.rrc_rolloff, "rrc_rolloff", "%.2f")
+ TUNE("TUNE_RRC_WIDTH_SYM", D->u.afsk.rrc_width_sym, "rrc_width_sym", "%.2f")
+
+ TUNE("TUNE_AGC_FAST", D->agc_fast_attack, "agc_fast_attack", "%.3f")
+ TUNE("TUNE_AGC_SLOW", D->agc_slow_decay, "agc_slow_decay", "%.6f")
+
+ TUNE("TUNE_PLL_LOCKED", D->pll_locked_inertia, "pll_locked_inertia", "%.2f")
+ TUNE("TUNE_PLL_SEARCHING", D->pll_searching_inertia, "pll_searching_inertia", "%.2f")
-#if defined(TUNE_AGC_FAST) && defined(TUNE_AGC_SLOW)
- D->agc_fast_attack = TUNE_AGC_FAST;
- D->agc_slow_decay = TUNE_AGC_SLOW;
-#endif
-#ifdef TUNE_HYST
- D->hysteresis = TUNE_HYST;
-#endif
-#if defined(TUNE_PLL_LOCKED) && defined(TUNE_PLL_SEARCHING)
- D->pll_locked_inertia = TUNE_PLL_LOCKED;
- D->pll_searching_inertia = TUNE_PLL_SEARCHING;
-#endif
-#ifdef TUNE_LPF_BAUD
- D->lpf_baud = TUNE_LPF_BAUD;
-#endif
-#ifdef TUNE_PRE_BAUD
- D->prefilter_baud = TUNE_PRE_BAUD;
-#endif
-#ifdef TUNE_LP_DELAY_FRACT
- D->lp_delay_fract = TUNE_LP_DELAY_FRACT;
-#endif
/*
* Calculate constants used for timing.
* The audio sample rate must be at least a few times the data rate.
*
- * Baud is an integer so we hack in a fine ajustment for EAS.
+ * Baud is an integer so we hack in a fine adjustment for EAS.
* Probably makes no difference because the DPLL keeps it in sync.
*
* A fraction if a Hz would make no difference for the filters.
@@ -321,77 +419,34 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq,
D->pll_step_per_sample = (int) round((TICKS_PER_PLL_CYCLE * (double)baud) / ((double)samples_per_sec));
}
-/*
- * Convert number of bit times to number of taps.
- */
-
- D->pre_filter_size = (int) round( D->pre_filter_len_bits * (float)samples_per_sec / (float)baud );
- D->ms_filter_size = (int) round( D->ms_filter_len_bits * (float)samples_per_sec / (float)baud );
- D->lp_filter_size = (int) round( D->lp_filter_len_bits * (float)samples_per_sec / (float)baud );
-
-/* Experiment with other sizes. */
-
-#ifdef TUNE_PRE_FILTER_SIZE
- D->pre_filter_size = TUNE_PRE_FILTER_SIZE;
-#endif
-#ifdef TUNE_MS_FILTER_SIZE
- D->ms_filter_size = TUNE_MS_FILTER_SIZE;
-#endif
-#ifdef TUNE_LP_FILTER_SIZE
- D->lp_filter_size = TUNE_LP_FILTER_SIZE;
-#endif
-
- //assert (D->pre_filter_size >= 4);
- assert (D->ms_filter_size >= 4);
- //assert (D->lp_filter_size >= 4);
-
- if (D->pre_filter_size > MAX_FILTER_SIZE)
- {
- text_color_set (DW_COLOR_ERROR);
- dw_printf ("Calculated filter size of %d is too large.\n", D->pre_filter_size);
- dw_printf ("Decrease the audio sample rate or increase the baud rate or\n");
- dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n",
- MAX_FILTER_SIZE);
- exit (1);
- }
-
- if (D->ms_filter_size > MAX_FILTER_SIZE)
- {
- text_color_set (DW_COLOR_ERROR);
- dw_printf ("Calculated filter size of %d is too large.\n", D->ms_filter_size);
- dw_printf ("Decrease the audio sample rate or increase the baud rate or\n");
- dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n",
- MAX_FILTER_SIZE);
- exit (1);
- }
-
-
-
- if (D->lp_filter_size > MAX_FILTER_SIZE)
- {
- text_color_set (DW_COLOR_ERROR);
- dw_printf ("Calculated filter size of %d is too large.\n", D->pre_filter_size);
- dw_printf ("Decrease the audio sample rate or increase the baud rate or\n");
- dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n",
- MAX_FILTER_SIZE);
- exit (1);
- }
-
/*
* Optionally apply a bandpass ("pre") filter to attenuate
* frequencies outside the range of interest.
- * This was first used for the "D" profile for 300 baud
- * which uses narrow shift. We expect it to have significant
- * benefit for a narrow shift.
- * In version 1.2, we will also try it with 1200 baud "E" as
- * an experiment to see how much it actually helps.
*/
if (D->use_prefilter) {
- float f1, f2;
- f1 = MIN(mark_freq,space_freq) - D->prefilter_baud * baud;
- f2 = MAX(mark_freq,space_freq) + D->prefilter_baud * baud;
+ // odd number is a little better
+ D->pre_filter_taps = ((int)( D->pre_filter_len_sym * (float)samples_per_sec / (float)baud )) | 1;
+
+ TUNE("TUNE_PRE_FILTER_TAPS", D->pre_filter_taps, "pre_filter_taps", "%d")
+
+// TODO: Size comes out to 417 for 1200 bps with 48000 sample rate.
+// The message is upsetting. Can we handle this better?
+
+ if (D->pre_filter_taps > MAX_FILTER_SIZE) {
+ text_color_set (DW_COLOR_ERROR);
+ dw_printf ("Warning: Calculated pre filter size of %d is too large.\n", D->pre_filter_taps);
+ dw_printf ("Decrease the audio sample rate or increase the decimation factor.\n");
+ dw_printf ("You can use -D2 or -D3, on the command line, to down-sample the audio rate\n");
+ dw_printf ("before demodulating. This greatly decreases the CPU requirements with little\n");
+ dw_printf ("impact on the decoding performance. This is useful for a slow ARM processor,\n");
+ dw_printf ("such as with a Raspberry Pi model 1.\n");
+ D->pre_filter_taps = (MAX_FILTER_SIZE - 1) | 1;
+ }
+
+ float f1 = MIN(mark_freq,space_freq) - D->prefilter_baud * baud;
+ float f2 = MAX(mark_freq,space_freq) + D->prefilter_baud * baud;
#if 0
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Generating prefilter %.0f to %.0f Hz.\n", f1, f2);
@@ -399,121 +454,67 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq,
f1 = f1 / (float)samples_per_sec;
f2 = f2 / (float)samples_per_sec;
- gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, D->pre_window);
+ gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_taps, D->pre_window);
}
-/*
- * Filters for detecting mark and space tones.
- */
-
-#if DEBUG1
- text_color_set(DW_COLOR_DEBUG);
- dw_printf ("%s: \n", __FILE__);
- dw_printf ("%d baud, %d samples_per_sec\n", baud, samples_per_sec);
- dw_printf ("AFSK %d & %d Hz\n", mark_freq, space_freq);
- dw_printf ("spll_step_per_sample = %d = 0x%08x\n", D->pll_step_per_sample, D->pll_step_per_sample);
- dw_printf ("D->ms_filter_size = %d = 0x%08x\n", D->ms_filter_size, D->ms_filter_size);
- dw_printf ("\n");
- dw_printf ("Mark\n");
- dw_printf (" j shape M sin M cos \n");
-#endif
-
-
- gen_ms (mark_freq, samples_per_sec, D->m_sin_table, D->m_cos_table, D->ms_filter_size, D->ms_window);
-
-#if DEBUG1
- text_color_set(DW_COLOR_DEBUG);
-
- dw_printf ("Space\n");
- dw_printf (" j shape S sin S cos\n");
-#endif
-
- gen_ms (space_freq, samples_per_sec, D->s_sin_table, D->s_cos_table, D->ms_filter_size, D->ms_window);
-
/*
* Now the lowpass filter.
- * I thought we'd want a cutoff of about 0.5 the baud rate
- * but it turns out about 1.1x is better. Still investigating...
+ * In version 1.7 a Root Raised Cosine filter is added as an alternative
+ * to the generic low pass filter.
+ * In both cases, lp_filter and lp_filter_taps are used but the
+ * contents will be generated differently. Later code does not care.
*/
+ if (D->u.afsk.use_rrc) {
- if (D->lpf_use_fir) {
- float fc;
- fc = baud * D->lpf_baud / (float)samples_per_sec;
- D->lp_filter_delay = gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window, D->lp_delay_fract);
- }
- else {
- // D->lp_filter_delay =
- // Only needed for looking back and I don't expect to use IIR in that case.
- }
+ assert (D->u.afsk.rrc_width_sym >= 1 && D->u.afsk.rrc_width_sym <= 16);
+ assert (D->u.afsk.rrc_rolloff >= 0. && D->u.afsk.rrc_rolloff <= 1.);
-/*
- * A non-whole number of cycles results in a DC bias.
- * Let's see if it helps to take it out.
- * Actually makes things worse: 20 fewer decoded.
- * Might want to try again after EXPERIMENTC.
- */
+ D->lp_filter_taps = ((int) (D->u.afsk.rrc_width_sym * (float)samples_per_sec / baud)) | 1; // odd works better
-#if 0
-#ifndef AVOID_FLOATING_POINT
+ TUNE("TUNE_LP_FILTER_TAPS", D->lp_filter_taps, "lp_filter_taps (RRC)", "%d")
-failed experiment
+ if (D->lp_filter_taps > MAX_FILTER_SIZE) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Calculated RRC low pass filter size of %d is too large.\n", D->lp_filter_taps);
+ dw_printf ("Decrease the audio sample rate or increase the decimation factor or\n");
+ dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n", MAX_FILTER_SIZE);
+ D->lp_filter_taps = (MAX_FILTER_SIZE - 1) | 1;
+ }
- dc_bias = 0;
- for (j=0; jms_filter_size; j++) {
- dc_bias += D->m_sin_table[j];
- }
- for (j=0; jms_filter_size; j++) {
- D->m_sin_table[j] -= dc_bias / D->ms_filter_size;
+ assert (D->lp_filter_taps > 8 && D->lp_filter_taps <= MAX_FILTER_SIZE);
+ (void)gen_rrc_lowpass (D->lp_filter, D->lp_filter_taps, D->u.afsk.rrc_rolloff, (float)samples_per_sec / baud);
}
+ else {
+ D->lp_filter_taps = (int) round( D->lp_filter_width_sym * (float)samples_per_sec / (float)baud );
- dc_bias = 0;
- for (j=0; jms_filter_size; j++) {
- dc_bias += D->m_cos_table[j];
- }
- for (j=0; jms_filter_size; j++) {
- D->m_cos_table[j] -= dc_bias / D->ms_filter_size;
- }
+ TUNE("TUNE_LP_FILTER_TAPS", D->lp_filter_taps, "lp_filter_taps (FIR)", "%d")
+ if (D->lp_filter_taps > MAX_FILTER_SIZE) {
+ text_color_set (DW_COLOR_ERROR);
+ dw_printf ("Calculated FIR low pass filter size of %d is too large.\n", D->lp_filter_taps);
+ dw_printf ("Decrease the audio sample rate or increase the decimation factor or\n");
+ dw_printf ("recompile the application with MAX_FILTER_SIZE larger than %d.\n", MAX_FILTER_SIZE);
+ D->lp_filter_taps = (MAX_FILTER_SIZE - 1) | 1;
+ }
- dc_bias = 0;
- for (j=0; jms_filter_size; j++) {
- dc_bias += D->s_sin_table[j];
- }
- for (j=0; jms_filter_size; j++) {
- D->s_sin_table[j] -= dc_bias / D->ms_filter_size;
- }
+ assert (D->lp_filter_taps > 8 && D->lp_filter_taps <= MAX_FILTER_SIZE);
- dc_bias = 0;
- for (j=0; jms_filter_size; j++) {
- dc_bias += D->s_cos_table[j];
- }
- for (j=0; jms_filter_size; j++) {
- D->s_cos_table[j] -= dc_bias / D->ms_filter_size;
+ float fc = baud * D->lpf_baud / (float)samples_per_sec;
+ gen_lowpass (fc, D->lp_filter, D->lp_filter_taps, D->lp_window);
}
-#endif
-#endif
/*
- * In version 1.2 we try another experiment.
- * Try using multiple slicing points instead of the traditional AGC.
+ * Starting with version 1.2
+ * try using multiple slicing points instead of the traditional AGC.
*/
-
space_gain[0] = MIN_G;
float step = powf(10.0, log10f(MAX_G/MIN_G) / (MAX_SUBCHANS-1));
for (j=1; j= 0 && chan < MAX_CHANS);
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
/*
- * Filters use last 'filter_size' samples.
+ * Filters use last 'filter_taps' samples.
*
* First push the older samples down.
*
@@ -588,187 +620,182 @@ void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulat
* Future project? Can we do better than shifting each time?
*/
- /* Scale to nice number, TODO: range -1.0 to +1.0, not 2. */
+ /* Scale to nice number. */
- fsam = sam / 16384.0f;
+ float fsam = (float)sam / 16384.0f;
- //abs_fsam = fsam >= 0.0f ? fsam : -fsam;
+ switch (D->profile) {
+ case 'E':
+ default:
+ case 'A': {
+ /* ========== New in Version 1.7 ========== */
-/*
- * Optional bandpass filter before the mark/space discriminator.
- */
+ // Cleaner & simpler than earlier 'A' thru 'E'
-// FIXME: calculate how much we really need.
+ if (D->use_prefilter) {
+ push_sample (fsam, D->raw_cb, D->pre_filter_taps);
+ fsam = convolve (D->raw_cb, D->pre_filter, D->pre_filter_taps);
+ }
- int extra = 0;
+ push_sample (fsam * fcos256(D->u.afsk.m_osc_phase), D->u.afsk.m_I_raw, D->lp_filter_taps);
+ push_sample (fsam * fsin256(D->u.afsk.m_osc_phase), D->u.afsk.m_Q_raw, D->lp_filter_taps);
+ D->u.afsk.m_osc_phase += D->u.afsk.m_osc_delta;
- if (D->use_prefilter) {
- float cleaner;
+ push_sample (fsam * fcos256(D->u.afsk.s_osc_phase), D->u.afsk.s_I_raw, D->lp_filter_taps);
+ push_sample (fsam * fsin256(D->u.afsk.s_osc_phase), D->u.afsk.s_Q_raw, D->lp_filter_taps);
+ D->u.afsk.s_osc_phase += D->u.afsk.s_osc_delta;
- push_sample (fsam, D->raw_cb, D->pre_filter_size);
- cleaner = convolve (D->raw_cb, D->pre_filter, D->pre_filter_size);
- push_sample (cleaner, D->ms_in_cb, D->ms_filter_size + extra);
- }
- else {
- push_sample (fsam, D->ms_in_cb, D->ms_filter_size + extra);
- }
+ float m_I = convolve (D->u.afsk.m_I_raw, D->lp_filter, D->lp_filter_taps);
+ float m_Q = convolve (D->u.afsk.m_Q_raw, D->lp_filter, D->lp_filter_taps);
+ float m_amp = fast_hypot(m_I, m_Q);
-/*
- * Next we have bandpass filters for the mark and space tones.
- */
+ float s_I = convolve (D->u.afsk.s_I_raw, D->lp_filter, D->lp_filter_taps);
+ float s_Q = convolve (D->u.afsk.s_Q_raw, D->lp_filter, D->lp_filter_taps);
+ float s_amp = fast_hypot(s_I, s_Q);
/*
- * find amplitude of "Mark" tone.
- */
- m_sum1 = convolve (D->ms_in_cb, D->m_sin_table, D->ms_filter_size);
- m_sum2 = convolve (D->ms_in_cb, D->m_cos_table, D->ms_filter_size);
-
- m_amp = sqrtf(m_sum1 * m_sum1 + m_sum2 * m_sum2);
-
-/*
- * Find amplitude of "Space" tone.
- */
- s_sum1 = convolve (D->ms_in_cb, D->s_sin_table, D->ms_filter_size);
- s_sum2 = convolve (D->ms_in_cb, D->s_cos_table, D->ms_filter_size);
-
- s_amp = sqrtf(s_sum1 * s_sum1 + s_sum2 * s_sum2);
-
-
-/*
- * Apply some low pass filtering BEFORE the AGC to remove
- * overshoot, ringing, and other bad stuff.
- *
- * A simple IIR filter is faster but FIR produces better results.
- *
- * It is a balancing act between removing high frequency components
- * from the tone dectection while letting the data thru.
+ * Capture the mark and space peak amplitudes for display.
+ * It uses fast attack and slow decay to get an idea of the
+ * overall amplitude.
*/
+ if (m_amp >= D->alevel_mark_peak) {
+ D->alevel_mark_peak = m_amp * D->quick_attack + D->alevel_mark_peak * (1.0f - D->quick_attack);
+ }
+ else {
+ D->alevel_mark_peak = m_amp * D->sluggish_decay + D->alevel_mark_peak * (1.0f - D->sluggish_decay);
+ }
+
+ if (s_amp >= D->alevel_space_peak) {
+ D->alevel_space_peak = s_amp * D->quick_attack + D->alevel_space_peak * (1.0f - D->quick_attack);
+ }
+ else {
+ D->alevel_space_peak = s_amp * D->sluggish_decay + D->alevel_space_peak * (1.0f - D->sluggish_decay);
+ }
+
+ if (D->num_slicers <= 1) {
+
+ // Which tone is stonger? That's simple with an ideal signal.
+ // However, we don't see too many ideal signals.
+ // Due to mismatching pre-emphasis and de-emphasis, the two
+ // tones will often have greatly different amplitudes so we use
+ // automatic gain control (AGC) to scale each to the same range
+ // before comparing.
+ // This is probably over complicated and could be combined with
+ // the signal amplitude measurement, above.
+ // It works so let's move along to other topics.
+
+ float m_norm = agc (m_amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley));
+ float s_norm = agc (s_amp, D->agc_fast_attack, D->agc_slow_decay, &(D->s_peak), &(D->s_valley));
+
+ // The normalized values should be around -0.5 to +0.5 so the difference
+ // should work out to be around -1 to +1.
+ // This is important because nudge_pll uses the demod_out amplitude to assign
+ // a quality or confidence score to the symbol.
+
+ float demod_out = m_norm - s_norm;
+
+ // Tested and it looks good. Range of about -1 to +1.
+ //printf ("JWL DEBUG demod A with agc = %6.2f\n", demod_out);
+
+ nudge_pll (chan, subchan, 0, demod_out, D, 1.0);
+
+ }
+ else {
+ // Multiple slice case.
+ // Rather than trying to find the best threshold location, use multiple
+ // slicer thresholds in parallel.
+ // The best slicing point will vary from packet to packet but should
+ // remain about the same for a given packet.
+
+ // We are not performing the AGC step here but still want the envelope
+ // for caluculating the confidence level (or quality) of the sample.
+
+ (void) agc (m_amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley));
+ (void) agc (s_amp, D->agc_fast_attack, D->agc_slow_decay, &(D->s_peak), &(D->s_valley));
+
+ for (int slice=0; slicenum_slicers; slice++) {
+ float demod_out = m_amp - s_amp * space_gain[slice];
+ float amp = 0.5f * (D->m_peak - D->m_valley + (D->s_peak - D->s_valley) * space_gain[slice]);
+ if (amp < 0.0000001f) amp = 1; // avoid divide by zero with no signal.
+
+ // Tested and it looks good. Range of about -1 to +1 relative to amp.
+ // Biased one way or the other depending on the space gain.
+ //printf ("JWL DEBUG demod A with slicer %d: %6.2f / %6.2f = %6.2f\n", slice, demod_out, amp, demod_out/amp);
+
+ nudge_pll (chan, subchan, slice, demod_out, D, amp);
+ }
+ }
+ }
+ break;
- if (D->lpf_use_fir) {
+ case 'D':
+ case 'B': {
+ /* ========== Version 1.7 Experiment ========== */
- push_sample (m_amp, D->m_amp_cb, D->lp_filter_size);
- m_amp = convolve (D->m_amp_cb, D->lp_filter, D->lp_filter_size);
+ // New - Convert frequency to a value proportional to frequency.
- push_sample (s_amp, D->s_amp_cb, D->lp_filter_size);
- s_amp = convolve (D->s_amp_cb, D->lp_filter, D->lp_filter_size);
- }
- else {
-
- /* Original, but faster, IIR. */
+ if (D->use_prefilter) {
+ push_sample (fsam, D->raw_cb, D->pre_filter_taps);
+ fsam = convolve (D->raw_cb, D->pre_filter, D->pre_filter_taps);
+ }
- m_amp = D->lpf_iir * m_amp + (1.0f - D->lpf_iir) * D->m_amp_prev;
- D->m_amp_prev = m_amp;
+ push_sample (fsam * fcos256(D->u.afsk.c_osc_phase), D->u.afsk.c_I_raw, D->lp_filter_taps);
+ push_sample (fsam * fsin256(D->u.afsk.c_osc_phase), D->u.afsk.c_Q_raw, D->lp_filter_taps);
+ D->u.afsk.c_osc_phase += D->u.afsk.c_osc_delta;
- s_amp = D->lpf_iir * s_amp + (1.0f - D->lpf_iir) * D->s_amp_prev;
- D->s_amp_prev = s_amp;
- }
+ float c_I = convolve (D->u.afsk.c_I_raw, D->lp_filter, D->lp_filter_taps);
+ float c_Q = convolve (D->u.afsk.c_Q_raw, D->lp_filter, D->lp_filter_taps);
-/*
- * Version 1.2: Try new approach to capturing the amplitude for display.
- * This is same as the AGC above without the normalization step.
- * We want decay to be substantially slower to get a longer
- * range idea of the received audio.
- */
+ float phase = atan2f (c_Q, c_I);
+ float rate = phase - D->u.afsk.prev_phase;
+ if (rate > M_PI) rate -= 2 * M_PI;
+ else if (rate < -M_PI) rate += 2 * M_PI;
+ D->u.afsk.prev_phase = phase;
- if (m_amp >= D->alevel_mark_peak) {
- D->alevel_mark_peak = m_amp * D->quick_attack + D->alevel_mark_peak * (1.0f - D->quick_attack);
- }
- else {
- D->alevel_mark_peak = m_amp * D->sluggish_decay + D->alevel_mark_peak * (1.0f - D->sluggish_decay);
- }
+ // Rate is radians per audio sample interval or something like that.
+ // Scale scale that into -1 to +1 for expected tones.
- if (s_amp >= D->alevel_space_peak) {
- D->alevel_space_peak = s_amp * D->quick_attack + D->alevel_space_peak * (1.0f - D->quick_attack);
- }
- else {
- D->alevel_space_peak = s_amp * D->sluggish_decay + D->alevel_space_peak * (1.0f - D->sluggish_decay);
- }
+ float norm_rate = rate * D->u.afsk.normalize_rpsam;
+ // We really don't have mark and space amplitudes available in this case.
-/*
- * Which tone is stronger?
- *
- * In an ideal world, simply compare. In my first naive attempt, that
- * worked perfectly with perfect signals. In the real world, we don't
- * have too many perfect signals.
- *
- * Here is an excellent explanation:
- * http://www.febo.com/packet/layer-one/transmit.html
- *
- * Under real conditions, we find that the higher tone usually has a
- * considerably smaller amplitude due to the passband characteristics
- * of the transmitter and receiver. To make matters worse, it
- * varies considerably from one station to another.
- *
- * The two filters also have different amounts of DC bias.
- *
- * My solution was to apply automatic gain control (AGC) to the mark and space
- * levels. This works by looking at the minimum and maximum outputs
- * for each filter and scaling the results to be roughly in the -0.5 to +0.5 range.
- * Results were excellent after tweaking the attack and decay times.
- *
- * 4X6IZ took a different approach. See QEX Jul-Aug 2012.
- *
- * He ran two different demodulators in parallel. One of them boosted the higher
- * frequency tone by 6 dB. Any duplicates were removed. This produced similar results.
- * He also used a bandpass filter before the mark/space filters.
- * I haven't tried this combination yet for 1200 baud.
- *
- * First, let's take a look at Track 1 of the TNC test CD. Here the receiver
- * has a flat response. We find the mark/space strength ratios very from 0.53 to 1.38
- * with a median of 0.81. This in in line with expections because most
- * transmitters add pre-emphasis to boost the higher audio frequencies.
- * Track 2 should more closely resemble what comes out of the speaker on a typical
- * transceiver. Here we see a ratio from 1.73 to 3.81 with a median of 2.48.
- *
- * This is similar to my observations of local signals, from the speaker.
- * The amplitude ratio varies from 1.48 to 3.41 with a median of 2.70.
- *
- * Rather than only two filters, let's try slicing the data in more places.
- */
+ if (D->num_slicers <= 1) {
- /* Fast attack and slow decay. */
- /* Numbers were obtained by trial and error from actual */
- /* recorded less-than-optimal signals. */
+ float demod_out = norm_rate;
+ // Tested and it looks good. Range roughly -1 to +1.
+ //printf ("JWL DEBUG demod B single = %6.2f\n", demod_out);
- /* See fsk_demod_agc.h for more information. */
+ nudge_pll (chan, subchan, 0, demod_out, D, 1.0);
- m_norm = agc (m_amp, D->agc_fast_attack, D->agc_slow_decay, &(D->m_peak), &(D->m_valley));
- s_norm = agc (s_amp, D->agc_fast_attack, D->agc_slow_decay, &(D->s_peak), &(D->s_valley));
+ }
+ else {
- if (D->num_slicers <= 1) {
+ // This would be useful for HF SSB where a tuning error
+ // would shift the frequency. Multiple slicing points would
+ // then compensate for differences in transmit/receive frequencies.
+ //
+ // Where should we set the thresholds?
+ // I'm thinking something like:
+ // -.5 -.375 -.25 -.125 0 .125 .25 .375 .5
+ //
+ // Assuming a 300 Hz shift, this would put slicing thresholds up
+ // to +-75 Hz from the center.
- /* Normal case of one demodulator to one HDLC decoder. */
- /* Demodulator output is difference between response from two filters. */
- /* AGC should generally keep this around -1 to +1 range. */
+ for (int slice=0; slicenum_slicers; slice++) {
- demod_out = m_norm - s_norm;
+ float offset = -0.5 + slice * (1. / (D->num_slicers - 1));
+ float demod_out = norm_rate + offset;
- /* Try adding some Hysteresis. */
- /* (Not to be confused with Hysteria.) */
+ //printf ("JWL DEBUG demod B slice %d, offset = %6.3f, demod_out = %6.2f\n", slice, offset, demod_out);
- if (demod_out > D->hysteresis) {
- demod_data = 1;
+ nudge_pll (chan, subchan, slice, demod_out, D, 1.0);
+ }
}
- else if (demod_out < (- (D->hysteresis))) {
- demod_data = 0;
- }
- else {
- demod_data = D->slicer[subchan].prev_demod_data;
}
- nudge_pll (chan, subchan, 0, demod_data, D);
+ break;
}
- else {
- int slice;
-
- for (slice=0; slicenum_slicers; slice++) {
- demod_data = m_amp > s_amp * space_gain[slice];
- nudge_pll (chan, subchan, slice, demod_data, D);
- }
- }
-
-
+
#if DEBUG4
if (chan == 0) {
@@ -805,9 +832,6 @@ void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulat
} /* end demod_afsk_process_sample */
-__attribute__((hot))
-inline static void nudge_pll (int chan, int subchan, int slice, int demod_data, struct demodulator_state_s *D)
-{
/*
* Finally, a PLL is used to sample near the centers of the data bits.
@@ -831,7 +855,7 @@ inline static void nudge_pll (int chan, int subchan, int slice, int demod_data,
* If we adjust it too quickly, the clock will have too much jitter.
* If we adjust it too slowly, it will take too long to lock on to a new signal.
*
- * Be a little more agressive about adjusting the PLL
+ * Be a little more aggressive about adjusting the PLL
* phase when searching for a signal. Don't change it as much when
* locked on to a signal.
*
@@ -839,6 +863,9 @@ inline static void nudge_pll (int chan, int subchan, int slice, int demod_data,
* because this happens for each transition from the demodulator.
*/
+__attribute__((hot))
+static void nudge_pll (int chan, int subchan, int slice, float demod_out, struct demodulator_state_s *D, float amplitude)
+{
D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll;
// Perform the add as unsigned to avoid signed overflow error.
@@ -850,12 +877,37 @@ inline static void nudge_pll (int chan, int subchan, int slice, int demod_data,
if (D->slicer[slice].data_clock_pll < 0 && D->slicer[slice].prev_d_c_pll > 0) {
/* Overflow - this is where we sample. */
- hdlc_rec_bit (chan, subchan, slice, demod_data, 0, -1);
+ // Assign it a confidence level or quality, 0 to 100, based on the amplitude.
+ // Those very close to 0 are suspect. We'll get back to this later.
+
+ int quality = fabsf(demod_out) * 100.0f / amplitude;
+ if (quality > 100) quality = 100;
+
+#if DEBUG5
+ // Write bit stream to a file.
+
+ static FILE *bsfp = NULL;
+ static int bcount = 0;
+ if (chan == 0 && subchan == 0 && slice == 0) {
+ if (bsfp == NULL) {
+ bsfp = fopen ("bitstream.txt", "w");
+ }
+ fprintf (bsfp, "%d", demod_out > 0);
+ bcount++;
+ if (bcount % 64 == 0) {
+ fprintf (bsfp, "\n");
+ }
+ }
+
+#endif
+
+ hdlc_rec_bit (chan, subchan, slice, demod_out > 0, 0, quality);
pll_dcd_each_symbol2 (D, chan, subchan, slice);
}
// Transitions nudge the DPLL phase toward the incoming signal.
+ int demod_data = demod_out > 0;
if (demod_data != D->slicer[slice].prev_demod_data) {
pll_dcd_signal_transition2 (D, slice, D->slicer[slice].data_clock_pll);
diff --git a/src/demod_psk.c b/src/demod_psk.c
index f01ee217..bc058185 100644
--- a/src/demod_psk.c
+++ b/src/demod_psk.c
@@ -60,7 +60,7 @@
* "bis" and "ter" are from Latin for second and third.
* I used the "ter" version which has phase shifts of 0, 90, 180, and 270 degrees.
*
- * There are ealier references to an alternative B which uses other phase shifts offset
+ * There are earlier references to an alternative B which uses other phase shifts offset
* by another 45 degrees.
*
* After getting QPSK working, it was not much more effort to add V.27 with 8 phases.
@@ -501,7 +501,7 @@ void demod_psk_init (enum modem_t modem_type, enum v26_e v26_alt, int samples_pe
*/
float fc = correct_baud * D->u.psk.lpf_baud / (float)samples_per_sec;
- gen_lowpass (fc, D->u.psk.lp_filter, D->u.psk.lp_filter_taps, D->u.psk.lp_window, 0);
+ gen_lowpass (fc, D->u.psk.lp_filter, D->u.psk.lp_filter_taps, D->u.psk.lp_window);
/*
* No point in having multiple numbers for signal level.
@@ -781,7 +781,7 @@ static void nudge_pll (int chan, int subchan, int slice, int demod_bits, struct
* If we adjust it too quickly, the clock will have too much jitter.
* If we adjust it too slowly, it will take too long to lock on to a new signal.
*
- * Be a little more agressive about adjusting the PLL
+ * Be a little more aggressive about adjusting the PLL
* phase when searching for a signal.
* Don't change it as much when locked on to a signal.
*/
diff --git a/src/digipeater.c b/src/digipeater.c
index 1e4c8147..006ee7b7 100644
--- a/src/digipeater.c
+++ b/src/digipeater.c
@@ -73,7 +73,7 @@
static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit,
- regex_t *uidigi, regex_t *uitrace, int to_chan, enum preempt_e preempt, char *type_filter);
+ regex_t *uidigi, regex_t *uitrace, int to_chan, enum preempt_e preempt, char *atgp, char *type_filter);
/*
@@ -154,8 +154,8 @@ void digipeater (int from_chan, packet_t pp)
// Network TNC is OK for UI frames where we don't care about timing.
if ( from_chan < 0 || from_chan >= MAX_CHANS ||
- (save_audio_config_p->achan[from_chan].medium != MEDIUM_RADIO &&
- save_audio_config_p->achan[from_chan].medium != MEDIUM_NETTNC)) {
+ (save_audio_config_p->chan_medium[from_chan] != MEDIUM_RADIO &&
+ save_audio_config_p->chan_medium[from_chan] != MEDIUM_NETTNC)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("APRS digipeater: Did not expect to receive on invalid channel %d.\n", from_chan);
}
@@ -176,6 +176,7 @@ void digipeater (int from_chan, packet_t pp)
save_audio_config_p->achan[to_chan].mycall,
&save_digi_config_p->alias[from_chan][to_chan], &save_digi_config_p->wide[from_chan][to_chan],
to_chan, save_digi_config_p->preempt[from_chan][to_chan],
+ save_digi_config_p->atgp[from_chan][to_chan],
save_digi_config_p->filter_str[from_chan][to_chan]);
if (result != NULL) {
dedupe_remember (pp, to_chan);
@@ -202,6 +203,7 @@ void digipeater (int from_chan, packet_t pp)
save_audio_config_p->achan[to_chan].mycall,
&save_digi_config_p->alias[from_chan][to_chan], &save_digi_config_p->wide[from_chan][to_chan],
to_chan, save_digi_config_p->preempt[from_chan][to_chan],
+ save_digi_config_p->atgp[from_chan][to_chan],
save_digi_config_p->filter_str[from_chan][to_chan]);
if (result != NULL) {
dedupe_remember (pp, to_chan);
@@ -244,6 +246,9 @@ void digipeater (int from_chan, packet_t pp)
*
* preempt - Option for "preemptive" digipeating.
*
+ * atgp - No tracing if this matches alias prefix.
+ * Hack added for special needs of ATGP.
+ *
* filter_str - Filter expression string or NULL.
*
* Returns: Packet object for transmission or NULL.
@@ -260,32 +265,9 @@ void digipeater (int from_chan, packet_t pp)
*
*------------------------------------------------------------------------------*/
-#define OBSOLETE14 1
-
-
-#ifndef OBSOLETE14
-static char *dest_ssid_path[16] = {
- "", /* Use VIA path */
- "WIDE1-1",
- "WIDE2-2",
- "WIDE3-3",
- "WIDE4-4",
- "WIDE5-5",
- "WIDE6-6",
- "WIDE7-7",
- "WIDE1-1", /* North */
- "WIDE1-1", /* South */
- "WIDE1-1", /* East */
- "WIDE1-1", /* West */
- "WIDE2-2", /* North */
- "WIDE2-2", /* South */
- "WIDE2-2", /* East */
- "WIDE2-2" }; /* West */
-#endif
-
static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, char *mycall_xmit,
- regex_t *alias, regex_t *wide, int to_chan, enum preempt_e preempt, char *filter_str)
+ regex_t *alias, regex_t *wide, int to_chan, enum preempt_e preempt, char *atgp, char *filter_str)
{
char source[AX25_MAX_ADDR_LEN];
int ssid;
@@ -323,15 +305,6 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch
* Otherwise we don't want to modify the input because this could be called multiple times.
*/
-#ifndef OBSOLETE14 // Took it out in 1.4
-
- if (ax25_get_num_repeaters(pp) == 0 && (ssid = ax25_get_ssid(pp, AX25_DESTINATION)) > 0) {
- ax25_set_addr(pp, AX25_REPEATER_1, dest_ssid_path[ssid]);
- ax25_set_ssid(pp, AX25_DESTINATION, 0);
- /* Continue with general case, below. */
- }
-#endif
-
/*
* Find the first repeater station which doesn't have "has been repeated" set.
@@ -510,6 +483,40 @@ static packet_t digipeat_match (int from_chan, packet_t pp, char *mycall_rec, ch
err = regexec(wide,repeater,0,NULL,0);
if (err == 0) {
+// Special hack added for ATGP to behave like some combination of options in some old TNC
+// so the via path does not continue to grow and exceed the 8 available positions.
+// The strange thing about this is that the used up digipeater is left there but
+// removed by the next digipeater.
+
+ if (strlen(atgp) > 0 && strncasecmp(repeater, atgp, strlen(atgp)) == 0) {
+
+ if (ssid >= 1 && ssid <= 7) {
+ packet_t result;
+
+ result = ax25_dup (pp);
+ assert (result != NULL);
+
+ // First, remove any already used digipeaters.
+
+ while (ax25_get_num_addr(result) >= 3 && ax25_get_h(result,AX25_REPEATER_1) == 1) {
+ ax25_remove_addr (result, AX25_REPEATER_1);
+ r--;
+ }
+
+ ssid = ssid - 1;
+ ax25_set_ssid(result, r, ssid); // could be zero.
+ if (ssid == 0) {
+ ax25_set_h (result, r);
+ }
+
+ // Insert own call at beginning and mark it used.
+
+ ax25_insert_addr (result, AX25_REPEATER_1, mycall_xmit);
+ ax25_set_h (result, AX25_REPEATER_1);
+ return (result);
+ }
+ }
+
/*
* If ssid == 1, we simply replace the repeater with my call and
* mark it as being used.
@@ -608,7 +615,7 @@ void digi_regen (int from_chan, packet_t pp)
*
* Name: main
*
- * Purpose: Standalone test case for this funtionality.
+ * Purpose: Standalone test case for this functionality.
*
* Usage: make -f Makefile. dtest
* ./dtest
@@ -617,7 +624,7 @@ void digi_regen (int from_chan, packet_t pp)
#if DIGITEST
-static char mycall[] = "WB2OSZ-9";
+static char mycall[12];
static regex_t alias_re;
@@ -627,6 +634,7 @@ static int failed;
static enum preempt_e preempt = PREEMPT_OFF;
+static char config_atgp[AX25_MAX_ADDR_LEN] = "HOP";
static void test (char *in, char *out)
@@ -640,6 +648,7 @@ static void test (char *in, char *out)
int frame_len;
alevel_t alevel;
+
dw_printf ("\n");
/*
@@ -689,9 +698,9 @@ static void test (char *in, char *out)
text_color_set(DW_COLOR_REC);
dw_printf ("Rec\t%s\n", rec);
-//TODO: Add filtering to test.
-// V
- result = digipeat_match (0, pp, mycall, mycall, &alias_re, &wide_re, 0, preempt, NULL);
+//TODO: Add filtering to test.
+// V
+ result = digipeat_match (0, pp, mycall, mycall, &alias_re, &wide_re, 0, preempt, config_atgp, NULL);
if (result != NULL) {
@@ -726,6 +735,7 @@ int main (int argc, char *argv[])
int e;
failed = 0;
char message[256];
+ strlcpy(mycall, "WB2OSZ-9", sizeof(mycall));
dedupe_init (4);
@@ -740,7 +750,7 @@ int main (int argc, char *argv[])
exit (1);
}
- e = regcomp (&wide_re, "^WIDE[1-7]-[1-7]$|^TRACE[1-7]-[1-7]$|^MA[1-7]-[1-7]$", REG_EXTENDED|REG_NOSUB);
+ e = regcomp (&wide_re, "^WIDE[1-7]-[1-7]$|^TRACE[1-7]-[1-7]$|^MA[1-7]-[1-7]$|^HOP[1-7]-[1-7]$", REG_EXTENDED|REG_NOSUB);
if (e != 0) {
regerror (e, &wide_re, message, sizeof(message));
text_color_set(DW_COLOR_ERROR);
@@ -830,11 +840,8 @@ int main (int argc, char *argv[])
*/
test ( "W1ABC>TEST-3:",
-#ifndef OBSOLETE14
- "W1ABC>TEST,WB2OSZ-9*,WIDE3-2:");
-#else
"");
-#endif
+
test ( "W1DEF>TEST-3,WIDE2-2:",
"W1DEF>TEST-3,WB2OSZ-9*,WIDE2-1:");
@@ -844,17 +851,17 @@ int main (int argc, char *argv[])
* The 4th case might be controversial.
*/
- test ( "W1XYZ>TEST,R1*,WIDE3-2:info1",
- "W1XYZ>TEST,R1,WB2OSZ-9*,WIDE3-1:info1");
+ test ( "W1XYZ>TESTD,R1*,WIDE3-2:info1",
+ "W1XYZ>TESTD,R1,WB2OSZ-9*,WIDE3-1:info1");
- test ( "W1XYZ>TEST,R2*,WIDE3-2:info1",
+ test ( "W1XYZ>TESTD,R2*,WIDE3-2:info1",
"");
- test ( "W1XYZ>TEST,R3*,WIDE3-2:info1",
+ test ( "W1XYZ>TESTD,R3*,WIDE3-2:info1",
"");
- test ( "W1XYZ>TEST,R1*,WB2OSZ-9:has explicit routing",
- "W1XYZ>TEST,R1,WB2OSZ-9*:has explicit routing");
+ test ( "W1XYZ>TESTD,R1*,WB2OSZ-9:has explicit routing",
+ "W1XYZ>TESTD,R1,WB2OSZ-9*:has explicit routing");
/*
@@ -931,6 +938,54 @@ int main (int argc, char *argv[])
test ( "WB2OSZ-15>TEST14,WIDE1-1,WIDE1-1:stuff",
"WB2OSZ-15>TEST14,WB2OSZ-9*,WIDE1-1:stuff");
+// New in 1.7 - ATGP Hack
+
+ preempt = PREEMPT_OFF; // Shouldn't make a difference here.
+
+ test ( "W1ABC>TEST51,HOP7-7,HOP7-7:stuff1",
+ "W1ABC>TEST51,WB2OSZ-9*,HOP7-6,HOP7-7:stuff1");
+
+ test ( "W1ABC>TEST52,ABCD*,HOP7-1,HOP7-7:stuff2",
+ "W1ABC>TEST52,WB2OSZ-9,HOP7*,HOP7-7:stuff2"); // Used up address remains.
+
+ test ( "W1ABC>TEST53,HOP7*,HOP7-7:stuff3",
+ "W1ABC>TEST53,WB2OSZ-9*,HOP7-6:stuff3"); // But it gets removed here.
+
+ test ( "W1ABC>TEST54,HOP7*,HOP7-1:stuff4",
+ "W1ABC>TEST54,WB2OSZ-9,HOP7*:stuff4"); // Remains again here.
+
+ test ( "W1ABC>TEST55,HOP7,HOP7*:stuff5",
+ "");
+
+// Examples given for desired result.
+
+ strlcpy (mycall, "CLNGMN-1", sizeof(mycall));
+ test ( "W1ABC>TEST60,HOP7-7,HOP7-7:",
+ "W1ABC>TEST60,CLNGMN-1*,HOP7-6,HOP7-7:");
+ test ( "W1ABC>TEST61,ROAN-3*,HOP7-6,HOP7-7:",
+ "W1ABC>TEST61,CLNGMN-1*,HOP7-5,HOP7-7:");
+
+ strlcpy (mycall, "GDHILL-8", sizeof(mycall));
+ test ( "W1ABC>TEST62,MDMTNS-7*,HOP7-1,HOP7-7:",
+ "W1ABC>TEST62,GDHILL-8,HOP7*,HOP7-7:");
+ test ( "W1ABC>TEST63,CAMLBK-9*,HOP7-1,HOP7-7:",
+ "W1ABC>TEST63,GDHILL-8,HOP7*,HOP7-7:");
+
+ strlcpy (mycall, "MDMTNS-7", sizeof(mycall));
+ test ( "W1ABC>TEST64,GDHILL-8*,HOP7*,HOP7-7:",
+ "W1ABC>TEST64,MDMTNS-7*,HOP7-6:");
+
+ strlcpy (mycall, "CAMLBK-9", sizeof(mycall));
+ test ( "W1ABC>TEST65,GDHILL-8,HOP7*,HOP7-7:",
+ "W1ABC>TEST65,CAMLBK-9*,HOP7-6:");
+
+ strlcpy (mycall, "KATHDN-15", sizeof(mycall));
+ test ( "W1ABC>TEST66,MTWASH-14*,HOP7-1:",
+ "W1ABC>TEST66,KATHDN-15,HOP7*:");
+
+ strlcpy (mycall, "SPRNGR-1", sizeof(mycall));
+ test ( "W1ABC>TEST67,CLNGMN-1*,HOP7-1:",
+ "W1ABC>TEST67,SPRNGR-1,HOP7*:");
if (failed == 0) {
diff --git a/src/digipeater.h b/src/digipeater.h
index 9885859e..5c849769 100644
--- a/src/digipeater.h
+++ b/src/digipeater.h
@@ -1,5 +1,4 @@
-
#ifndef DIGIPEATER_H
#define DIGIPEATER_H 1
@@ -38,6 +37,11 @@ struct digi_config_s {
enum preempt_e { PREEMPT_OFF, PREEMPT_DROP, PREEMPT_MARK, PREEMPT_TRACE } preempt[MAX_CHANS][MAX_CHANS];
+ // ATGP is an ugly hack for the specific need of ATGP which needs more that 8 digipeaters.
+ // DO NOT put this in the User Guide. On a need to know basis.
+
+ char atgp[MAX_CHANS][MAX_CHANS][AX25_MAX_ADDR_LEN];
+
char *filter_str[MAX_CHANS+1][MAX_CHANS+1];
// NULL or optional Packet Filter strings such as "t/m".
// Notice the size of arrays is one larger than normal.
diff --git a/src/direwolf.c b/src/direwolf.c
index 456b16f0..c7d79c09 100644
--- a/src/direwolf.c
+++ b/src/direwolf.c
@@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
-// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019, 2020 John Langner, WB2OSZ
+// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019, 2020, 2021 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -33,7 +33,9 @@
* Internet Gateway (IGate)
* Ham Radio of Things - IoT with Ham Radio
* FX.25 Forward Error Correction.
- *
+ * IL2P Forward Error Correction.
+ * Emergency Alert System (EAS) Specific Area Message Encoding (SAME) receiver.
+ * AIS receiver for tracking ships.
*
*---------------------------------------------------------------*/
@@ -67,9 +69,8 @@
#include
#include
#include
-#ifdef __OpenBSD__
-#include
-#elif __APPLE__
+#if USE_SNDIO || __APPLE__
+// no need to include
#else
#include
#endif
@@ -124,7 +125,9 @@
#include "ax25_link.h"
#include "dtime_now.h"
#include "fx25.h"
+#include "il2p.h"
#include "dwsock.h"
+#include "dns_sd_dw.h"
//static int idx_decoded = 0;
@@ -193,7 +196,6 @@ int main (int argc, char *argv[])
//int eof;
int j;
char config_file[100];
- int xmit_calibrate_option = 0;
int enable_pseudo_terminal = 0;
struct digi_config_s digi_config;
struct cdigi_config_s cdigi_config;
@@ -223,13 +225,22 @@ int main (int argc, char *argv[])
int d_h_opt = 0; /* "-d h" option for hamlib debugging. Repeat for more detail */
#endif
int d_x_opt = 1; /* "-d x" option for FX.25. Default minimal. Repeat for more detail. -qx to silence. */
+ int d_2_opt = 0; /* "-d 2" option for IL2P. Default minimal. Repeat for more detail. */
- int E_tx_opt = 0; /* "-E n" Error rate % for clobbering trasmit frames. */
+ int aprstt_debug = 0; /* "-d d" option for APRStt (think Dtmf) debug. */
+
+ int E_tx_opt = 0; /* "-E n" Error rate % for clobbering transmit frames. */
int E_rx_opt = 0; /* "-E Rn" Error rate % for clobbering receive frames. */
float e_recv_ber = 0.0; /* Receive Bit Error Rate (BER). */
int X_fx25_xmit_enable = 0; /* FX.25 transmit enable. */
+ int I_opt = -1; /* IL2P transmit, normal polarity, arg is max_fec. */
+ int i_opt = -1; /* IL2P transmit, inverted polarity, arg is max_fec. */
+
+ char x_opt_mode = ' '; /* "-x N" option for transmitting calibration tones. */
+ int x_opt_chan = 0; /* Split into 2 parts. Mode e.g. m, a, and optional channel. */
+
strlcpy(l_opt_logdir, "", sizeof(l_opt_logdir));
strlcpy(L_opt_logfile, "", sizeof(L_opt_logfile));
strlcpy(P_opt, "", sizeof(P_opt));
@@ -288,11 +299,11 @@ int main (int argc, char *argv[])
text_color_init(t_opt);
text_color_set(DW_COLOR_INFO);
//dw_printf ("Dire Wolf version %d.%d (%s) Beta Test 4\n", MAJOR_VERSION, MINOR_VERSION, __DATE__);
- //dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "G", __DATE__);
- dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION);
+ dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "E", __DATE__);
+ //dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION);
-#if defined(ENABLE_GPSD) || defined(USE_HAMLIB) || defined(USE_CM108)
+#if defined(ENABLE_GPSD) || defined(USE_HAMLIB) || defined(USE_CM108) || USE_AVAHI_CLIENT || USE_MACOS_DNSSD
dw_printf ("Includes optional support for: ");
#if defined(ENABLE_GPSD)
dw_printf (" gpsd");
@@ -302,12 +313,16 @@ int main (int argc, char *argv[])
#endif
#if defined(USE_CM108)
dw_printf (" cm108-ptt");
+#endif
+#if (USE_AVAHI_CLIENT|USE_MACOS_DNSSD)
+ dw_printf (" dns-sd");
#endif
dw_printf ("\n");
#endif
#if __WIN32__
+ //setlinebuf (stdout); setvbuf???
SetConsoleCtrlHandler ((PHANDLER_ROUTINE)cleanup_win, TRUE);
#else
setlinebuf (stdout);
@@ -357,7 +372,20 @@ int main (int argc, char *argv[])
text_color_set(DW_COLOR_INFO);
#endif
+// I've seen many references to people running this as root.
+// There is no reason to do that.
+// There is for some privileges to access the audio system, GPIO (if needed for PTT),
+// etc. but ordinary users have those abilities.
+// Giving an applications permission to do things it does not need to do
+// is a huge security risk.
+#ifndef __WIN32__
+ if (getuid() == 0 || geteuid() == 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Dire Wolf requires only privileges available to ordinary users.\n");
+ dw_printf ("Running this as root is an unnecessary security risk.\n");
+ }
+#endif
/*
* Default location of configuration file is current directory.
@@ -387,7 +415,7 @@ int main (int argc, char *argv[])
/* ':' following option character means arg is required. */
- c = getopt_long(argc, argv, "hP:B:gjJD:U:c:pxr:b:n:d:q:t:ul:L:Sa:E:T:e:X:A",
+ c = getopt_long(argc, argv, "hP:B:gjJD:U:c:px:r:b:n:d:q:t:ul:L:Sa:E:T:e:X:AI:i:",
long_options, &option_index);
if (c == -1)
break;
@@ -489,9 +517,43 @@ int main (int argc, char *argv[])
}
break;
- case 'x': /* -x for transmit calibration tones. */
-
- xmit_calibrate_option = 1;
+ case 'x': /* -x N for transmit calibration tones. */
+ /* N is composed of a channel number and/or one letter */
+ /* for the mode: mark, space, alternate, ptt-only. */
+
+ for (char *p = optarg; *p != '\0'; p++ ) {
+ switch (*p) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ x_opt_chan = x_opt_chan * 10 + *p - '0';
+ if (x_opt_mode == ' ') x_opt_mode = 'a';
+ break;
+ case 'a': x_opt_mode = *p; break; // Alternating tones
+ case 'm': x_opt_mode = *p; break; // Mark tone
+ case 's': x_opt_mode = *p; break; // Space tone
+ case 'p': x_opt_mode = *p; break; // Set PTT only
+ default:
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Invalid option '%c' for -x. Must be a, m, s, or p.\n", *p);
+ text_color_set(DW_COLOR_INFO);
+ exit (EXIT_FAILURE);
+ break;
+ }
+ }
+ if (x_opt_chan < 0 || x_opt_chan >= MAX_CHANS) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Invalid channel %d for -x. \n", x_opt_chan);
+ text_color_set(DW_COLOR_INFO);
+ exit (EXIT_FAILURE);
+ }
break;
case 'r': /* -r audio samples/sec. e.g. 44100 */
@@ -566,6 +628,8 @@ int main (int argc, char *argv[])
case 'h': d_h_opt++; break; // Hamlib verbose level.
#endif
case 'x': d_x_opt++; break; // FX.25
+ case '2': d_2_opt++; break; // IL2P
+ case 'd': aprstt_debug++; break; // APRStt (mnemonic Dtmf)
default: break;
}
}
@@ -653,6 +717,16 @@ int main (int argc, char *argv[])
X_fx25_xmit_enable = atoi(optarg);
break;
+ case 'I': // IL2P, normal polarity
+
+ I_opt = atoi(optarg);
+ break;
+
+ case 'i': // IL2P, inverted polarity
+
+ i_opt = atoi(optarg);
+ break;
+
case 'A': // -A convert AIS to APRS object
A_opt_ais_to_obj = 1;
@@ -703,7 +777,7 @@ int main (int argc, char *argv[])
if (n_opt != 0) {
audio_config.adev[0].num_channels = n_opt;
if (n_opt == 2) {
- audio_config.achan[1].medium = MEDIUM_RADIO;
+ audio_config.chan_medium[1] = MEDIUM_RADIO;
}
}
@@ -758,7 +832,7 @@ int main (int argc, char *argv[])
// Will make more precise in afsk demod init.
audio_config.achan[0].mark_freq = 2083; // Actually 2083.3 - logic 1.
audio_config.achan[0].space_freq = 1563; // Actually 1562.5 - logic 0.
- strlcpy (audio_config.achan[0].profiles, "D", sizeof(audio_config.achan[0].profiles));
+ strlcpy (audio_config.achan[0].profiles, "A", sizeof(audio_config.achan[0].profiles));
}
else {
audio_config.achan[0].modem_type = MODEM_SCRAMBLE;
@@ -853,7 +927,45 @@ int main (int argc, char *argv[])
audio_config.recv_ber = e_recv_ber;
- audio_config.fx25_xmit_enable = X_fx25_xmit_enable;
+ if (X_fx25_xmit_enable > 0) {
+ if (I_opt != -1 || i_opt != -1) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Can't mix -X with -I or -i.\n");
+ exit (EXIT_FAILURE);
+ }
+ audio_config.achan[0].fx25_strength = X_fx25_xmit_enable;
+ audio_config.achan[0].layer2_xmit = LAYER2_FX25;
+ }
+
+ if (I_opt != -1 && i_opt != -1) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Can't use both -I and -i at the same time.\n");
+ exit (EXIT_FAILURE);
+ }
+
+ if (I_opt >= 0) {
+ audio_config.achan[0].layer2_xmit = LAYER2_IL2P;
+ audio_config.achan[0].il2p_max_fec = (I_opt > 0);
+ if (audio_config.achan[0].il2p_max_fec == 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("It is highly recommended that 1, rather than 0, is used with -I for best results.\n");
+ }
+ audio_config.achan[0].il2p_invert_polarity = 0; // normal
+ }
+
+ if (i_opt >= 0) {
+ audio_config.achan[0].layer2_xmit = LAYER2_IL2P;
+ audio_config.achan[0].il2p_max_fec = (i_opt > 0);
+ if (audio_config.achan[0].il2p_max_fec == 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("It is highly recommended that 1, rather than 0, is used with -i for best results.\n");
+ }
+ audio_config.achan[0].il2p_invert_polarity = 1; // invert for transmit
+ if (audio_config.achan[0].baud == 1200) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Using -i with 1200 bps is a bad idea. Use -I instead.\n");
+ }
+ }
/*
@@ -874,16 +986,17 @@ int main (int argc, char *argv[])
}
/*
- * Initialize the demodulator(s) and HDLC decoder.
+ * Initialize the demodulator(s) and layer 2 decoder (HDLC, IL2P).
*/
multi_modem_init (&audio_config);
fx25_init (d_x_opt);
+ il2p_init (d_2_opt);
/*
* Initialize the touch tone decoder & APRStt gateway.
*/
dtmf_init (&audio_config, audio_amplitude);
- aprs_tt_init (&tt_config);
+ aprs_tt_init (&tt_config, aprstt_debug);
tt_user_init (&audio_config, &tt_config);
/*
@@ -905,30 +1018,77 @@ int main (int argc, char *argv[])
xmit_init (&audio_config, d_p_opt);
/*
- * If -x option specified, transmit alternating tones for transmitter
+ * If -x N option specified, transmit calibration tones for transmitter
* audio level adjustment, up to 1 minute then quit.
- * TODO: enhance for more than one channel.
+ * a: Alternating mark/space tones
+ * m: Mark tone (e.g. 1200Hz)
+ * s: Space tone (e.g. 2200Hz)
+ * p: Set PTT only.
+ * A leading or trailing number is the channel.
*/
- if (xmit_calibrate_option) {
-
- int max_duration = 60; /* seconds */
- int n = audio_config.achan[0].baud * max_duration;
- int chan = 0;
-
- text_color_set(DW_COLOR_INFO);
- dw_printf ("\nSending transmit calibration tones. Press control-C to terminate.\n");
-
- ptt_set (OCTYPE_PTT, chan, 1);
- while (n-- > 0) {
-
- tone_gen_put_bit (chan, n & 1);
-
- }
- ptt_set (OCTYPE_PTT, chan, 0);
- exit (0);
+ if (x_opt_mode != ' ') {
+ if (audio_config.chan_medium[x_opt_chan] == MEDIUM_RADIO) {
+ if (audio_config.achan[x_opt_chan].mark_freq
+ && audio_config.achan[x_opt_chan].space_freq) {
+ int max_duration = 60;
+ int n = audio_config.achan[x_opt_chan].baud * max_duration;
+
+ text_color_set(DW_COLOR_INFO);
+ ptt_set(OCTYPE_PTT, x_opt_chan, 1);
+
+ switch (x_opt_mode) {
+ default:
+ case 'a': // Alternating tones: -x a
+ dw_printf("\nSending alternating mark/space calibration tones (%d/%dHz) on channel %d.\nPress control-C to terminate.\n",
+ audio_config.achan[x_opt_chan].mark_freq,
+ audio_config.achan[x_opt_chan].space_freq,
+ x_opt_chan);
+ while (n-- > 0) {
+ tone_gen_put_bit(x_opt_chan, n & 1);
+ }
+ break;
+ case 'm': // "Mark" tone: -x m
+ dw_printf("\nSending mark calibration tone (%dHz) on channel %d.\nPress control-C to terminate.\n",
+ audio_config.achan[x_opt_chan].mark_freq,
+ x_opt_chan);
+ while (n-- > 0) {
+ tone_gen_put_bit(x_opt_chan, 0);
+ }
+ break;
+ case 's': // "Space" tone: -x s
+ dw_printf("\nSending space calibration tone (%dHz) on channel %d.\nPress control-C to terminate.\n",
+ audio_config.achan[x_opt_chan].space_freq,
+ x_opt_chan);
+ while (n-- > 0) {
+ tone_gen_put_bit(x_opt_chan, 1);
+ }
+ break;
+ case 'p': // Silence - set PTT only: -x p
+ dw_printf("\nSending silence (Set PTT only) on channel %d.\nPress control-C to terminate.\n", x_opt_chan);
+ SLEEP_SEC(max_duration);
+ break;
+ }
+
+ ptt_set(OCTYPE_PTT, x_opt_chan, 0);
+ text_color_set(DW_COLOR_INFO);
+ exit(EXIT_SUCCESS);
+
+ } else {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("\nMark/Space frequencies not defined for channel %d. Cannot calibrate using this modem type.\n", x_opt_chan);
+ text_color_set(DW_COLOR_INFO);
+ exit(EXIT_FAILURE);
+ }
+ } else {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("\nChannel %d is not configured as a radio channel.\n", x_opt_chan);
+ text_color_set(DW_COLOR_INFO);
+ exit(EXIT_FAILURE);
+ }
}
+
/*
* Initialize the digipeater and IGate functions.
*/
@@ -944,6 +1104,11 @@ int main (int argc, char *argv[])
server_init (&audio_config, &misc_config);
kissnet_init (&misc_config);
+#if (USE_AVAHI_CLIENT|USE_MACOS_DNSSD)
+ if (misc_config.kiss_port > 0 && misc_config.dns_sd_enabled)
+ dns_sd_announce(&misc_config);
+#endif
+
/*
* Create a pseudo terminal and KISS TNC emulator.
*/
@@ -1019,8 +1184,8 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
int h;
char display_retries[32];
- assert (chan >= 0 && chan < MAX_CHANS);
- assert (subchan >= -1 && subchan < MAX_SUBCHANS);
+ assert (chan >= 0 && chan < MAX_TOTAL_CHANS); // TOTAL for virtual channels
+ assert (subchan >= -2 && subchan < MAX_SUBCHANS);
assert (slice >= 0 && slice < MAX_SLICERS);
assert (pp != NULL); // 1.1J+
@@ -1056,8 +1221,11 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
text_color_set(DW_COLOR_DEBUG);
dw_printf ("\n");
- if (( ! q_h_opt ) && alevel.rec >= 0) { /* suppress if "-q h" option */
+// The HEARD line.
+ if (( ! q_h_opt ) && alevel.rec >= 0) { /* suppress if "-q h" option */
+// FIXME: rather than checking for ichannel, how about checking medium==radio
+ if (chan != audio_config.igate_vchannel) { // suppress if from ICHANNEL
if (h != -1 && h != AX25_SOURCE) {
dw_printf ("Digipeater ");
}
@@ -1100,6 +1268,7 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
dw_printf ("%s audio level = %s %s %s\n", heard, alevel_text, display_retries, spectrum);
}
+ }
}
/* Version 1.2: Cranking the input level way up produces 199. */
@@ -1113,6 +1282,12 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio input level is too high. Reduce so most stations are around 50.\n");
}
+// FIXME: rather than checking for ichannel, how about checking medium==radio
+ else if (alevel.rec < 5 && chan != audio_config.igate_vchannel) {
+
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Audio input level is too low. Increase so most stations are around 50.\n");
+ }
// Display non-APRS packets in a different color.
@@ -1137,6 +1312,10 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
text_color_set(DW_COLOR_REC);
dw_printf ("[%d.dtmf%s] ", chan, ts);
}
+ else if (subchan == -2) {
+ text_color_set(DW_COLOR_REC);
+ dw_printf ("[%d.is%s] ", chan, ts);
+ }
else {
if (ax25_is_aprs(pp)) {
text_color_set(DW_COLOR_REC);
@@ -1247,7 +1426,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.
// Just be quiet about errors if "-qd" is set.
- decode_aprs (&A, pp, q_d_opt);
+ decode_aprs (&A, pp, q_d_opt, 0);
if ( ! q_d_opt ) {
@@ -1325,10 +1504,10 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
flen = ax25_pack(pp, fbuf);
- server_send_rec_packet (chan, pp, fbuf, flen); // AGW net protocol
- kissnet_send_rec_packet (chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1); // KISS TCP
- kissserial_send_rec_packet (chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1); // KISS serial port
- kisspt_send_rec_packet (chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1); // KISS pseudo terminal
+ server_send_rec_packet (chan, pp, fbuf, flen); // AGW net protocol
+ kissnet_send_rec_packet (chan, KISS_CMD_DATA_FRAME, fbuf, flen, NULL, -1); // KISS TCP
+ kissserial_send_rec_packet (chan, KISS_CMD_DATA_FRAME, fbuf, flen, NULL, -1); // KISS serial port
+ kisspt_send_rec_packet (chan, KISS_CMD_DATA_FRAME, fbuf, flen, NULL, -1); // KISS pseudo terminal
if (A_opt_ais_to_obj && strlen(ais_obj_packet) != 0) {
packet_t ao_pp = ax25_from_text (ais_obj_packet, 1);
@@ -1337,26 +1516,42 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev
int ao_flen = ax25_pack(ao_pp, ao_fbuf);
server_send_rec_packet (chan, ao_pp, ao_fbuf, ao_flen);
- kissnet_send_rec_packet (chan, KISS_CMD_DATA_FRAME, ao_fbuf, ao_flen, -1);
- kissserial_send_rec_packet (chan, KISS_CMD_DATA_FRAME, ao_fbuf, ao_flen, -1);
- kisspt_send_rec_packet (chan, KISS_CMD_DATA_FRAME, ao_fbuf, ao_flen, -1);
+ kissnet_send_rec_packet (chan, KISS_CMD_DATA_FRAME, ao_fbuf, ao_flen, NULL, -1);
+ kissserial_send_rec_packet (chan, KISS_CMD_DATA_FRAME, ao_fbuf, ao_flen, NULL, -1);
+ kisspt_send_rec_packet (chan, KISS_CMD_DATA_FRAME, ao_fbuf, ao_flen, NULL, -1);
ax25_delete (ao_pp);
}
}
+/*
+ * If it is from the ICHANNEL, we are done.
+ * Don't digipeat. Don't IGate.
+ * Don't do anything with it after printing and sending to client apps.
+ */
+
+ if (chan == audio_config.igate_vchannel) {
+ return;
+ }
+
/*
- * If it came from DTMF decoder, send it to APRStt gateway.
+ * If it came from DTMF decoder (subchan == -1), send it to APRStt gateway.
* Otherwise, it is a candidate for IGate and digipeater.
*
- * TODO: It would be useful to have some way to simulate touch tone
+ * It is also useful to have some way to simulate touch tone
* sequences with BEACON sendto=R0 for testing.
*/
- if (subchan == -1) {
+ if (subchan == -1) { // from DTMF decoder
if (tt_config.gateway_enabled && info_len >= 2) {
aprs_tt_sequence (chan, (char*)(pinfo+1));
}
}
+ else if (*pinfo == 't' && info_len >= 2 && tt_config.gateway_enabled) {
+ // For testing.
+ // Would be nice to verify it was generated locally,
+ // not received over the air.
+ aprs_tt_sequence (chan, (char*)(pinfo+1));
+ }
else {
/* Send to Internet server if option is enabled. */
@@ -1469,7 +1664,9 @@ static void usage (char **argv)
dw_printf (" -P xxx Modem Profiles.\n");
dw_printf (" -A Convert AIS positions to APRS Object Reports.\n");
dw_printf (" -D n Divide audio sample rate by n for channel 0.\n");
- dw_printf (" -X n 1 to enable FX.25 transmit.\n");
+ dw_printf (" -X n 1 to enable FX.25 transmit. 16, 32, 64 for specific number of check bytes.\n");
+ dw_printf (" -I n Enable IL2P transmit. n=1 is recommended. 0 uses weaker FEC.\n");
+ dw_printf (" -i n Enable IL2P transmit, inverted polarity. n=1 is recommended. 0 uses weaker FEC.\n");
dw_printf (" -d Debug options:\n");
dw_printf (" a a = AGWPE network protocol client.\n");
dw_printf (" k k = KISS serial port or pseudo terminal client.\n");
@@ -1487,6 +1684,8 @@ static void usage (char **argv)
dw_printf (" h h = hamlib increase verbose level.\n");
#endif
dw_printf (" x x = FX.25 increase verbose level.\n");
+ dw_printf (" 2 2 = IL2P.\n");
+ dw_printf (" d d = APRStt (DTMF to APRS object translation).\n");
dw_printf (" -q Quiet (suppress output) options:\n");
dw_printf (" h h = Heard line with the audio level.\n");
dw_printf (" d d = Decoding of APRS packets.\n");
@@ -1499,6 +1698,11 @@ static void usage (char **argv)
dw_printf (" -p Enable pseudo terminal for KISS protocol.\n");
#endif
dw_printf (" -x Send Xmit level calibration tones.\n");
+ dw_printf (" a a = Alternating mark/space tones.\n");
+ dw_printf (" m m = Steady mark tone (e.g. 1200Hz).\n");
+ dw_printf (" s s = Steady space tone (e.g. 2200Hz).\n");
+ dw_printf (" p p = Silence (Set PTT only).\n");
+ dw_printf (" chan Optionally add a number to specify radio channel.\n");
dw_printf (" -u Print UTF-8 test string and exit.\n");
dw_printf (" -S Print symbol tables and exit.\n");
dw_printf (" -T fmt Time stamp format for sent and received frames.\n");
@@ -1516,6 +1720,7 @@ static void usage (char **argv)
dw_printf ("Complete documentation can be found in /usr/local/share/doc/direwolf\n");
#endif
dw_printf ("or online at https://github.com/wb2osz/direwolf/tree/master/doc\n");
+ text_color_set(DW_COLOR_INFO);
exit (EXIT_FAILURE);
}
diff --git a/src/direwolf.h b/src/direwolf.h
index efc329ba..8351e1a9 100644
--- a/src/direwolf.h
+++ b/src/direwolf.h
@@ -37,7 +37,6 @@
#endif
-
/*
* Previously, we could handle only a single audio device.
* This meant we could have only two radio channels.
@@ -65,7 +64,15 @@
* and make sure they handle undefined channels correctly.
*/
-#define MAX_CHANS ((MAX_ADEVS) * 2)
+#define MAX_RADIO_CHANS ((MAX_ADEVS) * 2)
+
+#define MAX_CHANS MAX_RADIO_CHANS // TODO: Replace all former with latter to avoid confusion with following.
+
+#define MAX_TOTAL_CHANS 16 // v1.7 allows additional virtual channels which are connected
+ // to something other than radio modems.
+ // Total maximum channels is based on the 4 bit KISS field.
+ // Someone with very unusual requirements could increase this and
+ // use only the AGW network protocol.
/*
* Maximum number of rigs.
@@ -97,7 +104,7 @@
* Each one of these can have multiple slicers, at
* different levels, to compensate for different
* amplitudes of the AFSK tones.
- * Intially used same number as subchannels but
+ * Initially used same number as subchannels but
* we could probably trim this down a little
* without impacting performance.
*/
@@ -176,6 +183,7 @@
#define DW_METERS_TO_FEET(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 3.2808399)
#define DW_FEET_TO_METERS(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.3048)
#define DW_KM_TO_MILES(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.621371192)
+#define DW_MILES_TO_KM(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 1.609344)
#define DW_KNOTS_TO_MPH(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 1.15077945)
#define DW_KNOTS_TO_METERS_PER_SEC(x) ((x) == G_UNKNOWN ? G_UNKNOWN : (x) * 0.51444444444)
@@ -278,43 +286,34 @@ char *strsep(char **stringp, const char *delim);
char *strtok_r(char *str, const char *delim, char **saveptr);
#endif
-// Don't recall why for everyone.
+// Don't recall why I added this for everyone rather than only for Windows.
char *strcasestr(const char *S, const char *FIND);
-#if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__APPLE__)
-
-// strlcpy and strlcat should be in string.h and the C library.
-
-#else // Use our own copy
-
-
-// These prevent /usr/include/gps.h from providing its own definition.
-#define HAVE_STRLCAT 1
-#define HAVE_STRLCPY 1
+// cmake determines whether strlcpy and strlcat are available
+// or if we need to supply our own.
+#define DEBUG_STRL 1 // Extra Debug version when using our own strlcpy, strlcat.
-#define DEBUG_STRL 1
-
+#ifndef HAVE_STRLCPY // Need to supply our own.
#if DEBUG_STRL
-
#define strlcpy(dst,src,siz) strlcpy_debug(dst,src,siz,__FILE__,__func__,__LINE__)
-#define strlcat(dst,src,siz) strlcat_debug(dst,src,siz,__FILE__,__func__,__LINE__)
-
size_t strlcpy_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz, const char *file, const char *func, int line);
-size_t strlcat_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz, const char *file, const char *func, int line);
-
#else
-
#define strlcpy(dst,src,siz) strlcpy_debug(dst,src,siz)
-#define strlcat(dst,src,siz) strlcat_debug(dst,src,siz)
-
size_t strlcpy_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz);
-size_t strlcat_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz);
-
#endif /* DEBUG_STRL */
+#endif
-#endif /* BSD or Apple */
+#ifndef HAVE_STRLCAT // Need to supply our own.
+#if DEBUG_STRL
+#define strlcat(dst,src,siz) strlcat_debug(dst,src,siz,__FILE__,__func__,__LINE__)
+size_t strlcat_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz, const char *file, const char *func, int line);
+#else
+#define strlcat(dst,src,siz) strlcat_debug(dst,src,siz)
+size_t strlcat_debug(char *__restrict__ dst, const char *__restrict__ src, size_t siz);
+#endif /* DEBUG_STRL */
+#endif
#endif /* ifndef DIREWOLF_H */
diff --git a/src/dlq.c b/src/dlq.c
index 2f21f6d0..0d4b4a41 100644
--- a/src/dlq.c
+++ b/src/dlq.c
@@ -130,12 +130,18 @@ void dlq_init (void)
#else
int err;
err = pthread_mutex_init (&wake_up_mutex, NULL);
+ if (err != 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("dlq_init: pthread_mutex_init err=%d", err);
+ perror ("");
+ exit (EXIT_FAILURE);
+ }
err = pthread_mutex_init (&dlq_mutex, NULL);
if (err != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("dlq_init: pthread_mutex_init err=%d", err);
perror ("");
- exit (1);
+ exit (EXIT_FAILURE);
}
#endif
@@ -233,7 +239,7 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev
dw_printf ("dlq_rec_frame (chan=%d, pp=%p, ...)\n", chan, pp);
#endif
- assert (chan >= 0 && chan < MAX_CHANS);
+ assert (chan >= 0 && chan < MAX_TOTAL_CHANS); // TOTAL to include virtual channels.
if (pp == NULL) {
text_color_set(DW_COLOR_ERROR);
@@ -253,6 +259,11 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev
/* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
+ if (pnew == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
+ }
s_new_count++;
if (s_new_count > s_delete_count + 50) {
@@ -492,6 +503,11 @@ void dlq_connect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num
/* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
+ if (pnew == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
+ }
s_new_count++;
pnew->type = DLQ_CONNECT_REQUEST;
@@ -545,6 +561,11 @@ void dlq_disconnect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int
/* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
+ if (pnew == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
+ }
s_new_count++;
pnew->type = DLQ_DISCONNECT_REQUEST;
@@ -603,6 +624,11 @@ void dlq_outstanding_frames_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LE
/* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
+ if (pnew == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
+ }
s_new_count++;
pnew->type = DLQ_OUTSTANDING_FRAMES_REQUEST;
@@ -670,6 +696,11 @@ void dlq_xmit_data_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int n
/* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
+ if (pnew == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
+ }
s_new_count++;
pnew->type = DLQ_XMIT_DATA_REQUEST;
@@ -733,6 +764,11 @@ void dlq_register_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client)
/* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
+ if (pnew == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
+ }
s_new_count++;
pnew->type = DLQ_REGISTER_CALLSIGN;
@@ -763,6 +799,11 @@ void dlq_unregister_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client
/* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
+ if (pnew == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
+ }
s_new_count++;
pnew->type = DLQ_UNREGISTER_CALLSIGN;
@@ -817,6 +858,11 @@ void dlq_channel_busy (int chan, int activity, int status)
/* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
+ if (pnew == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
+ }
s_new_count++;
pnew->type = DLQ_CHANNEL_BUSY;
@@ -840,7 +886,7 @@ void dlq_channel_busy (int chan, int activity, int status)
* Name: dlq_seize_confirm
*
* Purpose: Inform data link state machine that the transmitter is on.
- * This is in reponse to lm_seize_request.
+ * This is in response to lm_seize_request.
*
* Inputs: chan - Radio channel number.
*
@@ -865,6 +911,11 @@ void dlq_seize_confirm (int chan)
/* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
+ if (pnew == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
+ }
s_new_count++;
pnew->type = DLQ_SEIZE_CONFIRM;
@@ -910,6 +961,11 @@ void dlq_client_cleanup (int client)
/* Allocate a new queue item. */
pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1);
+ if (pnew == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
+ }
s_new_count++;
// All we care about is the client number.
@@ -1192,6 +1248,11 @@ cdata_t *cdata_new (int pid, char *data, int len)
size = ( len + 127 ) & ~0x7f;
cdata = malloc ( sizeof(cdata_t) + size );
+ if (cdata == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
+ }
cdata->magic = TXDATA_MAGIC;
cdata->next = NULL;
diff --git a/src/dns_sd_avahi.c b/src/dns_sd_avahi.c
new file mode 100644
index 00000000..63ce0b66
--- /dev/null
+++ b/src/dns_sd_avahi.c
@@ -0,0 +1,260 @@
+//
+// This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+// Copyright (C) 2020 Heikki Hannikainen, OH7LZB
+//
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+/*------------------------------------------------------------------
+ *
+ * Module: dns_sd_avahi.c
+ *
+ * Purpose: Announce the KISS over TCP service using DNS-SD via Avahi
+ *
+ * Description:
+ *
+ * Most people have typed in enough IP addresses and ports by now, and
+ * would rather just select an available TNC that is automatically
+ * discovered on the local network. Even more so on a mobile device
+ * such an Android or iOS phone or tablet.
+ *
+ * On Linux, the announcement can be made through Avahi, the mDNS
+ * framework commonly deployed on Linux systems.
+ *
+ * This is largely based on the publishing example of the Avahi library.
+ */
+
+#ifdef USE_AVAHI_CLIENT
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "dns_sd_dw.h"
+#include "dns_sd_common.h"
+#include "textcolor.h"
+
+static AvahiEntryGroup *group = NULL;
+static AvahiSimplePoll *simple_poll = NULL;
+static AvahiClient *client = NULL;
+static char *name = NULL;
+static int kiss_port = 0;
+
+pthread_t avahi_thread;
+
+static void create_services(AvahiClient *c);
+
+#define PRINT_PREFIX "DNS-SD: Avahi: "
+
+static void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata)
+{
+ assert(g == group || group == NULL);
+ group = g;
+
+ /* Called whenever the entry group state changes */
+ switch (state) {
+ case AVAHI_ENTRY_GROUP_ESTABLISHED :
+ /* The entry group has been established successfully */
+ text_color_set(DW_COLOR_INFO);
+ dw_printf(PRINT_PREFIX "Service '%s' successfully registered.\n", name);
+ break;
+ case AVAHI_ENTRY_GROUP_COLLISION: {
+ char *n;
+ /* A service name collision with a remote service
+ * happened. Let's pick a new name. */
+ n = avahi_alternative_service_name(name);
+ avahi_free(name);
+ name = n;
+ text_color_set(DW_COLOR_INFO);
+ dw_printf(PRINT_PREFIX "Service name collision, renaming service to '%s'\n", name);
+ /* And recreate the services */
+ create_services(avahi_entry_group_get_client(g));
+ break;
+ }
+ case AVAHI_ENTRY_GROUP_FAILURE:
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf(PRINT_PREFIX "Entry group failure: %s\n", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
+ /* Some kind of failure happened while we were registering our services */
+ avahi_simple_poll_quit(simple_poll);
+ break;
+ case AVAHI_ENTRY_GROUP_UNCOMMITED:
+ case AVAHI_ENTRY_GROUP_REGISTERING:
+ ;
+ }
+}
+
+static void create_services(AvahiClient *c)
+{
+ char *n;
+ int ret;
+ assert(c);
+ /* If this is the first time we're called, let's create a new
+ * entry group if necessary */
+ if (!group) {
+ if (!(group = avahi_entry_group_new(c, entry_group_callback, NULL))) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf(PRINT_PREFIX "avahi_entry_group_new() failed: %s\n", avahi_strerror(avahi_client_errno(c)));
+ goto fail;
+ }
+ } else {
+ avahi_entry_group_reset(group);
+ }
+
+ /* If the group is empty (either because it was just created, or
+ * because it was reset previously, add our entries. */
+ if (avahi_entry_group_is_empty(group)) {
+ text_color_set(DW_COLOR_INFO);
+ dw_printf(PRINT_PREFIX "Announcing KISS TCP on port %d as '%s'\n", kiss_port, name);
+
+ /* Announce with AVAHI_PROTO_INET instead of AVAHI_PROTO_UNSPEC, since Dire Wolf currently
+ * only listens on IPv4.
+ */
+
+ if ((ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_INET, 0, name, DNS_SD_SERVICE, NULL, NULL, kiss_port, NULL)) < 0) {
+ if (ret == AVAHI_ERR_COLLISION)
+ goto collision;
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf(PRINT_PREFIX "Failed to add _kiss-tnc._tcp service: %s\n", avahi_strerror(ret));
+ goto fail;
+ }
+
+ /* Tell the server to register the service */
+ if ((ret = avahi_entry_group_commit(group)) < 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf(PRINT_PREFIX "Failed to commit entry group: %s\n", avahi_strerror(ret));
+ goto fail;
+ }
+ }
+ return;
+
+collision:
+ /* A service name collision with a local service happened. Let's
+ * pick a new name */
+ n = avahi_alternative_service_name(name);
+ avahi_free(name);
+ name = n;
+ text_color_set(DW_COLOR_INFO);
+ dw_printf(PRINT_PREFIX "Service name collision, renaming service to '%s'\n", name);
+ avahi_entry_group_reset(group);
+ create_services(c);
+ return;
+
+fail:
+ avahi_simple_poll_quit(simple_poll);
+}
+
+static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata)
+{
+ assert(c);
+ /* Called whenever the client or server state changes */
+ switch (state) {
+ case AVAHI_CLIENT_S_RUNNING:
+ /* The server has startup successfully and registered its host
+ * name on the network, so it's time to create our services */
+ create_services(c);
+ break;
+ case AVAHI_CLIENT_FAILURE:
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf(PRINT_PREFIX "Client failure: %s\n", avahi_strerror(avahi_client_errno(c)));
+ avahi_simple_poll_quit(simple_poll);
+ break;
+ case AVAHI_CLIENT_S_COLLISION:
+ /* Let's drop our registered services. When the server is back
+ * in AVAHI_SERVER_RUNNING state we will register them
+ * again with the new host name. */
+ case AVAHI_CLIENT_S_REGISTERING:
+ /* The server records are now being established. This
+ * might be caused by a host name change. We need to wait
+ * for our own records to register until the host name is
+ * properly esatblished. */
+ if (group)
+ avahi_entry_group_reset(group);
+ break;
+ case AVAHI_CLIENT_CONNECTING:
+ ;
+ }
+}
+
+static void cleanup(void)
+{
+ /* Cleanup things */
+ if (client)
+ avahi_client_free(client);
+
+ if (simple_poll)
+ avahi_simple_poll_free(simple_poll);
+
+ avahi_free(name);
+}
+
+
+static void *avahi_mainloop(void *arg)
+{
+ /* Run the main loop */
+ avahi_simple_poll_loop(simple_poll);
+
+ cleanup();
+
+ return NULL;
+}
+
+void dns_sd_announce (struct misc_config_s *mc)
+{
+ text_color_set(DW_COLOR_DEBUG);
+ //kiss_port = mc->kiss_port; // now an array.
+ kiss_port = mc->kiss_port[0]; // FIXME: Quick hack until I can handle multiple TCP ports properly.
+
+ int error;
+
+ /* Allocate main loop object */
+ if (!(simple_poll = avahi_simple_poll_new())) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf(PRINT_PREFIX "Failed to create Avahi simple poll object.\n");
+ goto fail;
+ }
+
+ if (mc->dns_sd_name[0]) {
+ name = avahi_strdup(mc->dns_sd_name);
+ } else {
+ name = dns_sd_default_service_name();
+ }
+
+ /* Allocate a new client */
+ client = avahi_client_new(avahi_simple_poll_get(simple_poll), 0, client_callback, NULL, &error);
+
+ /* Check whether creating the client object succeeded */
+ if (!client) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf(PRINT_PREFIX "Failed to create Avahi client: %s\n", avahi_strerror(error));
+ goto fail;
+ }
+
+ pthread_create(&avahi_thread, NULL, &avahi_mainloop, NULL);
+
+ return;
+
+fail:
+ cleanup();
+}
+
+#endif // USE_AVAHI_CLIENT
+
diff --git a/src/dns_sd_common.c b/src/dns_sd_common.c
new file mode 100644
index 00000000..65a1cbf7
--- /dev/null
+++ b/src/dns_sd_common.c
@@ -0,0 +1,65 @@
+//
+// This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+// Copyright (C) 2020 Heikki Hannikainen, OH7LZB
+//
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+/*------------------------------------------------------------------
+ *
+ * Module: dns_sd_common.c
+ *
+ * Purpose: Announce the KISS over TCP service using DNS-SD, common functions
+ *
+ * Description:
+ *
+ * Most people have typed in enough IP addresses and ports by now, and
+ * would rather just select an available TNC that is automatically
+ * discovered on the local network. Even more so on a mobile device
+ * such an Android or iOS phone or tablet.
+ *
+ * This module contains common functions needed on Linux and MacOS.
+ */
+
+
+#include
+#include
+#include
+
+/* Get a default service name to publish. By default,
+ * "Dire Wolf on ", or just "Dire Wolf" if hostname cannot
+ * be obtained.
+ */
+char *dns_sd_default_service_name(void)
+{
+ char hostname[51];
+ char sname[64];
+
+ int i = gethostname(hostname, sizeof(hostname));
+ if (i == 0) {
+ hostname[sizeof(hostname)-1] = 0;
+
+ // on some systems, an FQDN is returned; remove domain part
+ char *dot = strchr(hostname, '.');
+ if (dot)
+ *dot = 0;
+
+ snprintf(sname, sizeof(sname), "Dire Wolf on %s", hostname);
+ return strdup(sname);
+ }
+
+ return strdup("Dire Wolf");
+}
+
diff --git a/src/dns_sd_common.h b/src/dns_sd_common.h
new file mode 100644
index 00000000..f104bf83
--- /dev/null
+++ b/src/dns_sd_common.h
@@ -0,0 +1,7 @@
+
+#if (USE_AVAHI_CLIENT|USE_MACOS_DNSSD)
+
+char *dns_sd_default_service_name(void);
+
+#endif
+
diff --git a/src/dns_sd_dw.h b/src/dns_sd_dw.h
new file mode 100644
index 00000000..79f4b868
--- /dev/null
+++ b/src/dns_sd_dw.h
@@ -0,0 +1,10 @@
+
+#if (USE_AVAHI_CLIENT|USE_MACOS_DNSSD)
+
+#include "config.h"
+
+#define DNS_SD_SERVICE "_kiss-tnc._tcp"
+
+void dns_sd_announce (struct misc_config_s *mc);
+
+#endif // USE_AVAHI_CLIENT
diff --git a/src/dns_sd_macos.c b/src/dns_sd_macos.c
new file mode 100644
index 00000000..d733a412
--- /dev/null
+++ b/src/dns_sd_macos.c
@@ -0,0 +1,89 @@
+//
+// This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+// Copyright (C) 2020 Heikki Hannikainen, OH7LZB
+//
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+/*------------------------------------------------------------------
+ *
+ * Module: dns_sd_macos.c
+ *
+ * Purpose: Announce the KISS over TCP service using MacOS dns-sd
+ *
+ * Description:
+ *
+ * Most people have typed in enough IP addresses and ports by now, and
+ * would rather just select an available TNC that is automatically
+ * discovered on the local network. Even more so on a mobile device
+ * such an Android or iOS phone or tablet.
+ *
+ * On MacOs, the announcement can be made through dns-sd.
+ */
+
+#ifdef USE_MACOS_DNSSD
+
+#include
+#include
+#include
+
+#include "dns_sd_dw.h"
+#include "dns_sd_common.h"
+#include "textcolor.h"
+
+static char *name = NULL;
+
+static void registerServiceCallBack(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode,
+ const char* name, const char* regType, const char* domain, void* context)
+{
+ if (errorCode == kDNSServiceErr_NoError) {
+ text_color_set(DW_COLOR_INFO);
+ dw_printf("DNS-SD: Successfully registered '%s'\n", name);
+ } else {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("DNS-SD: Failed to register '%s': %d\n", name, errorCode);
+ }
+}
+
+void dns_sd_announce (struct misc_config_s *mc)
+{
+ //int kiss_port = mc->kiss_port; // now an array.
+ int kiss_port = mc->kiss_port[0]; // FIXME: Quick hack until I can handle multiple TCP ports properly.
+
+ if (mc->dns_sd_name[0]) {
+ name = strdup(mc->dns_sd_name);
+ } else {
+ name = dns_sd_default_service_name();
+ }
+
+ uint16_t port_nw = htons(kiss_port);
+
+ DNSServiceRef registerRef;
+ DNSServiceErrorType err = DNSServiceRegister(
+ ®isterRef, 0, 0, name, DNS_SD_SERVICE, NULL, NULL,
+ port_nw, 0, NULL, registerServiceCallBack, NULL);
+
+ if (err == kDNSServiceErr_NoError) {
+ text_color_set(DW_COLOR_INFO);
+ dw_printf("DNS-SD: Announcing KISS TCP on port %d as '%s'\n", kiss_port, name);
+ } else {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("DNS-SD: Failed to announce '%s': %d\n", name, err);
+ }
+}
+
+#endif // USE_MACOS_DNSSD
+
+
diff --git a/src/dsp.c b/src/dsp.c
index 6ba70949..4a5f4a88 100644
--- a/src/dsp.c
+++ b/src/dsp.c
@@ -43,7 +43,6 @@
#include "dsp.h"
-//#include "fsk_demod_agc.h" /* for M_FILTER_SIZE, etc. */
#define MIN(a,b) ((a)<(b)?(a):(b))
#define MAX(a,b) ((a)>(b)?(a):(b))
@@ -127,7 +126,7 @@ float window (bp_window_t type, int size, int j)
*----------------------------------------------------------------*/
-int gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype, float lp_delay_fract)
+void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype)
{
int j;
float G;
@@ -175,54 +174,7 @@ int gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype,
lp_filter[j] = lp_filter[j] / G;
}
-
-// Calculate the signal delay.
-// If a signal at level 0 steps to level 1, this is the time that it would
-// take for the output to reach 0.5.
-//
-// Examples:
-//
-// Filter has one tap with value of 1.0.
-// Output is immediate so I would call this delay of 0.
-//
-// Filter coefficients: 0.2, 0.2, 0.2, 0.2, 0.2
-// "1" inputs Out
-// 1 0.2
-// 2 0.4
-// 3 0.6
-//
-// In this case, the output does not change immediately.
-// It takes two more samples to reach the half way point
-// so it has a delay of 2.
-
- float sum = 0;
- int delay = 0;
-
- if (lp_delay_fract == 0) lp_delay_fract = 0.5;
-
- for (j=0; j lp_delay_fract) {
- delay = j;
- break;
- }
- }
-
-#if DEBUG1
- dw_printf ("Low Pass Delay = %d samples\n", delay) ;
-#endif
-
-// Hmmm. This might have been wasted effort. The result is always half the number of taps.
-
- if (delay < 2 || delay > filter_size - 2) {
- text_color_set(DW_COLOR_ERROR);
- dw_printf ("Internal error, %s %d, delay %d for size %d\n", __func__, __LINE__, delay, filter_size);
- }
-
- return (delay);
+ return;
} /* end gen_lowpass */
@@ -369,4 +321,86 @@ void gen_ms (int fc, int sps, float *sin_table, float *cos_table, int filter_siz
} /* end gen_ms */
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name: rrc
+ *
+ * Purpose: Root Raised Cosine function.
+ * Why do they call it that?
+ * It's mostly the sinc function with cos windowing to taper off edges faster.
+ *
+ * Inputs: t - Time in units of symbol duration.
+ * i.e. The centers of two adjacent symbols would differ by 1.
+ *
+ * a - Roll off factor, between 0 and 1.
+ *
+ * Returns: Basically the sinc (sin(x)/x) function with edges decreasing faster.
+ * Should be 1 for t = 0 and 0 at all other integer values of t.
+ *
+ *----------------------------------------------------------------*/
+
+__attribute__((const))
+float rrc (float t, float a)
+{
+ float sinc, window, result;
+
+ if (t > -0.001 && t < 0.001) {
+ sinc = 1;
+ }
+ else {
+ sinc = sinf(M_PI * t) / (M_PI * t);
+ }
+
+ if (fabsf(a * t) > 0.499 && fabsf(a * t) < 0.501) {
+ window = M_PI / 4;
+ }
+ else {
+ window = cos(M_PI * a * t) / ( 1 - powf(2 * a * t, 2));
+ // This made nicer looking waveforms for generating signal.
+ //window = cos(M_PI * a * t);
+ // Do we want to let it go negative?
+ // I think this would happen when a > 0.5 / (filter width in symbol times)
+ if (window < 0) {
+ //printf ("'a' is too large for range of 't'.\n");
+ //window = 0;
+ }
+ }
+
+ result = sinc * window;
+
+#if DEBUGRRC
+ // t should vary from - to + half of filter size in symbols.
+ // Result should be 1 at t=0 and 0 at all other integer values of t.
+
+ printf ("%.3f, %.3f, %.3f, %.3f\n", t, sinc, window, result);
+#endif
+ return (result);
+}
+
+// The Root Raised Cosine (RRC) low pass filter is suppposed to minimize Intersymbol Interference (ISI).
+
+void gen_rrc_lowpass (float *pfilter, int filter_taps, float rolloff, float samples_per_symbol)
+{
+ int k;
+ float t;
+
+ for (k = 0; k < filter_taps; k++) {
+ t = (k - ((filter_taps - 1.0) / 2.0)) / samples_per_symbol;
+ pfilter[k] = rrc (t, rolloff);
+ }
+
+ // Scale it for unity gain.
+
+ t = 0;
+ for (k = 0; k < filter_taps; k++) {
+ t += pfilter[k];
+ }
+ for (k = 0; k < filter_taps; k++) {
+ pfilter[k] = pfilter[k] / t;
+ }
+}
+
/* end dsp.c */
diff --git a/src/dsp.h b/src/dsp.h
index 5d5b882d..e0dbd248 100644
--- a/src/dsp.h
+++ b/src/dsp.h
@@ -5,9 +5,13 @@
float window (bp_window_t type, int size, int j);
-int gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype, float lp_delay_fract);
+void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype);
void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_window_t wtype);
void gen_ms (int fc, int samples_per_sec, float *sin_table, float *cos_table, int filter_size, int wtype);
+
+__attribute__((const)) float rrc (float t, float a);
+
+void gen_rrc_lowpass (float *pfilter, int filter_taps, float rolloff, float samples_per_symbol);
diff --git a/src/dtmf.c b/src/dtmf.c
index 788be18b..953b0f70 100644
--- a/src/dtmf.c
+++ b/src/dtmf.c
@@ -245,7 +245,7 @@ char dtmf_sample (int c, float input)
* others in the same group multiplied by some factor.
*
* For perfect synthetic signals this needs to be in
- * the range of about 1.33 (very senstive) to 2.15 (very fussy).
+ * the range of about 1.33 (very sensitive) to 2.15 (very fussy).
*
* Too low will cause false triggers on random noise.
* Too high will won't decode less than perfect signals.
@@ -529,7 +529,7 @@ int main ()
memset (&my_audio_config, 0, sizeof(my_audio_config));
my_audio_config.adev[ACHAN2ADEV(c)].defined = 1;
my_audio_config.adev[ACHAN2ADEV(c)].samples_per_sec = 44100;
- my_audio_config.achan[c].medium = MEDIUM_RADIO;
+ my_audio_config.chan_medium[c] = MEDIUM_RADIO;
my_audio_config.achan[c].dtmf_decode = DTMF_DECODE_ON;
dtmf_init(&my_audio_config, 50);
diff --git a/src/dwgps.c b/src/dwgps.c
index 83e92444..ccf24b0b 100644
--- a/src/dwgps.c
+++ b/src/dwgps.c
@@ -90,7 +90,7 @@ static dw_mutex_t s_gps_mutex;
*
* Name: dwgps_init
*
- * Purpose: Intialize the GPS interface.
+ * Purpose: Initialize the GPS interface.
*
* Inputs: pconfig Configuration settings. This might include
* serial port name for direct connect and host
diff --git a/src/dwgpsd.c b/src/dwgpsd.c
index 70b650bd..cc2f801a 100644
--- a/src/dwgpsd.c
+++ b/src/dwgpsd.c
@@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
-// Copyright (C) 2013, 2014, 2015, 2020 John Langner, WB2OSZ
+// Copyright (C) 2013, 2014, 2015, 2020, 2022 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -20,7 +20,7 @@
/*------------------------------------------------------------------
*
- * Module: dwgps.c
+ * Module: dwgpsd.c
*
* Purpose: Interface to location data, i.e. GPS receiver.
*
@@ -60,7 +60,11 @@
// An incompatibility was introduced with version 7
// and again with 9 and again with 10.
-#if GPSD_API_MAJOR_VERSION < 5 || GPSD_API_MAJOR_VERSION > 11
+// 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
#error libgps API version might be incompatible.
#endif
@@ -94,7 +98,7 @@ static void * read_gpsd_thread (void *arg);
*
* Name: dwgpsd_init
*
- * Purpose: Intialize the GPS interface.
+ * Purpose: Initialize the GPS interface.
*
* Inputs: pconfig Configuration settings. This includes
* host name or address for network connection.
@@ -191,7 +195,7 @@ int dwgpsd_init (struct misc_config_s *pconfig, int debug)
if (strlen(pconfig->gpsd_host) == 0) {
- /* Nothing to do. Leave initial fix value of errror. */
+ /* Nothing to do. Leave initial fix value of error. */
return (0);
}
@@ -364,6 +368,10 @@ static void * read_gpsd_thread (void *arg)
default:
case MODE_NOT_SEEN:
case MODE_NO_FIX:
+ if (info.fix <= DWFIX_NOT_SEEN) {
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("GPSD: No location fix.\n");
+ }
if (info.fix >= DWFIX_2D) {
text_color_set(DW_COLOR_INFO);
dw_printf ("GPSD: Lost location fix.\n");
@@ -393,13 +401,15 @@ static void * read_gpsd_thread (void *arg)
if (/*gpsdata.stupid_status >= STATUS_FIX &&*/ gpsdata.fix.mode >= MODE_2D) {
- info.dlat = isfinite(gpsdata.fix.latitude) ? gpsdata.fix.latitude : G_UNKNOWN;
- info.dlon = isfinite(gpsdata.fix.longitude) ? gpsdata.fix.longitude : G_UNKNOWN;
+#define GOOD(x) (isfinite(x) && ! isnan(x))
+
+ info.dlat = GOOD(gpsdata.fix.latitude) ? gpsdata.fix.latitude : G_UNKNOWN;
+ info.dlon = GOOD(gpsdata.fix.longitude) ? gpsdata.fix.longitude : G_UNKNOWN;
// When stationary, track is NaN which is not finite.
- info.track = isfinite(gpsdata.fix.track) ? gpsdata.fix.track : G_UNKNOWN;
- info.speed_knots = isfinite(gpsdata.fix.speed) ? (MPS_TO_KNOTS * gpsdata.fix.speed) : G_UNKNOWN;
+ info.track = GOOD(gpsdata.fix.track) ? gpsdata.fix.track : G_UNKNOWN;
+ info.speed_knots = GOOD(gpsdata.fix.speed) ? (MPS_TO_KNOTS * gpsdata.fix.speed) : G_UNKNOWN;
if (gpsdata.fix.mode >= MODE_3D) {
- info.altitude = isfinite(gpsdata.fix.stupid_altitude) ? gpsdata.fix.stupid_altitude : G_UNKNOWN;
+ info.altitude = GOOD(gpsdata.fix.stupid_altitude) ? gpsdata.fix.stupid_altitude : G_UNKNOWN;
}
// Otherwise keep last known altitude when we downgrade from 3D to 2D fix.
// Caller knows altitude is outdated if info.fix == DWFIX_2D.
diff --git a/src/dwgpsnmea.c b/src/dwgpsnmea.c
index 840ab652..14cda77e 100644
--- a/src/dwgpsnmea.c
+++ b/src/dwgpsnmea.c
@@ -144,11 +144,9 @@ int dwgpsnmea_init (struct misc_config_s *pconfig, int debug)
/*
* Open serial port connection.
- * 4800 baud is standard for GPS.
- * Should add an option to allow changing someday.
*/
- s_gpsnmea_port_fd = serial_port_open (pconfig->gpsnmea_port, 4800);
+ s_gpsnmea_port_fd = serial_port_open (pconfig->gpsnmea_port, pconfig->gpsnmea_speed);
if (s_gpsnmea_port_fd != MYFDERROR) {
#if __WIN32__
@@ -182,12 +180,10 @@ int dwgpsnmea_init (struct misc_config_s *pconfig, int debug)
/* Return fd to share if waypoint wants same device. */
-/* Currently both are fixed speed at 4800. */
-/* If that ever becomes configurable, that needs to be compared too. */
MYFDTYPE dwgpsnmea_get_fd(char *wp_port_name, int speed)
{
- if (strcmp(s_save_configp->gpsnmea_port, wp_port_name) == 0 && speed == 4800) {
+ if (strcmp(s_save_configp->gpsnmea_port, wp_port_name) == 0 && speed == s_save_configp->gpsnmea_speed) {
return (s_gpsnmea_port_fd);
}
return (MYFDERROR);
@@ -266,9 +262,12 @@ static void * read_gpsnmea_thread (void *arg)
}
dwgps_set_data (&info);
- // TODO: doesn't exist yet - serial_port_close(fd);
+ serial_port_close(s_gpsnmea_port_fd);
s_gpsnmea_port_fd = MYFDERROR;
+ // TODO: If the open() was in this thread, we could wait a while and
+ // try to open again. That would allow recovery if the USB GPS device
+ // is unplugged and plugged in again.
break; /* terminate thread. */
}
@@ -289,73 +288,53 @@ static void * read_gpsnmea_thread (void *arg)
}
/* Process sentence. */
+// TODO: More general: Ignore the second letter rather than recognizing only GP... and GN...
if (strncmp(gps_msg, "$GPRMC", 6) == 0 ||
strncmp(gps_msg, "$GNRMC", 6) == 0) {
- f = dwgpsnmea_gprmc (gps_msg, 0, &info.dlat, &info.dlon, &info.speed_knots, &info.track);
+ // Here we just tuck away the course and speed.
+ // Fix and location will be updated by GxGGA.
- if (f == DWFIX_ERROR) {
+ double ignore_dlat;
+ double ignore_dlon;
+
+ f = dwgpsnmea_gprmc (gps_msg, 0, &ignore_dlat, &ignore_dlon, &info.speed_knots, &info.track);
- /* Parse error. Shouldn't happen. Better luck next time. */
- text_color_set(DW_COLOR_INFO);
+ if (f == DWFIX_ERROR) {
+ /* Parse error. Shouldn't happen. Better luck next time. */
+ text_color_set(DW_COLOR_ERROR);
dw_printf ("GPSNMEA: Error parsing $GPRMC sentence.\n");
dw_printf ("%s\n", gps_msg);
}
- else if (f == DWFIX_2D) {
-
- if (info.fix != DWFIX_2D && info.fix != DWFIX_3D) {
-
- text_color_set(DW_COLOR_INFO);
- dw_printf ("GPSNMEA: Location fix is now available.\n");
-
- info.fix = DWFIX_2D; // Don't know if 2D or 3D. Take minimum.
- }
- info.timestamp = time(NULL);
- if (s_debug >= 2) {
- text_color_set(DW_COLOR_DEBUG);
- dwgps_print ("GPSNMEA: ", &info);
- }
- dwgps_set_data (&info);
- }
- else {
-
- if (info.fix == DWFIX_2D || info.fix == DWFIX_3D) {
-
- text_color_set(DW_COLOR_INFO);
- dw_printf ("GPSNMEA: Lost location fix.\n");
- }
- info.fix = f; /* lost it. */
- info.timestamp = time(NULL);
- if (s_debug >= 2) {
- text_color_set(DW_COLOR_DEBUG);
- dwgps_print ("GPSNMEA: ", &info);
- }
- dwgps_set_data (&info);
- }
-
}
+
else if (strncmp(gps_msg, "$GPGGA", 6) == 0 ||
strncmp(gps_msg, "$GNGGA", 6) == 0) {
int nsat;
f = dwgpsnmea_gpgga (gps_msg, 0, &info.dlat, &info.dlon, &info.altitude, &nsat);
- /* Only switch between 2D & 3D. */
- /* Let GPRMC handle other changes in fix state and data transfer. */
-
if (f == DWFIX_ERROR) {
-
/* Parse error. Shouldn't happen. Better luck next time. */
- text_color_set(DW_COLOR_INFO);
+ text_color_set(DW_COLOR_ERROR);
dw_printf ("GPSNMEA: Error parsing $GPGGA sentence.\n");
dw_printf ("%s\n", gps_msg);
}
- else if ((f == DWFIX_3D && info.fix == DWFIX_2D) ||
- (f == DWFIX_2D && info.fix == DWFIX_3D)) {
- text_color_set(DW_COLOR_INFO);
- dw_printf ("GPSNMEA: Location fix is now %dD.\n", (int)f);
- info.fix = f;
+ else {
+ if (f != info.fix) { // Print change in location fix.
+ text_color_set(DW_COLOR_INFO);
+ if (f == DWFIX_NO_FIX) dw_printf ("GPSNMEA: Location fix has been lost.\n");
+ if (f == DWFIX_2D) dw_printf ("GPSNMEA: Location fix is now 2D.\n");
+ if (f == DWFIX_3D) dw_printf ("GPSNMEA: Location fix is now 3D.\n");
+ info.fix = f;
+ }
+ info.timestamp = time(NULL);
+ if (s_debug >= 2) {
+ text_color_set(DW_COLOR_DEBUG);
+ dwgps_print ("GPSNMEA: ", &info);
+ }
+ dwgps_set_data (&info);
}
}
}
@@ -436,7 +415,7 @@ static int remove_checksum (char *sent, int quiet)
*
* Name: dwgpsnmea_gprmc
*
- * Purpose: Parse $GPRMC sentence and extract interesing parts.
+ * Purpose: Parse $GPRMC sentence and extract interesting parts.
*
* Inputs: sentence NMEA sentence.
*
@@ -446,9 +425,11 @@ static int remove_checksum (char *sent, int quiet)
* odlon longitude
* oknots speed
* ocourse direction of travel.
- *
+ *
* Left undefined if not valid.
*
+ * Note: RMC does not contain altitude.
+ *
* Returns: DWFIX_ERROR Parse error.
* DWFIX_NO_FIX GPS is there but Position unknown. Could be temporary.
* DWFIX_2D Valid position. We don't know if it is really 2D or 3D.
@@ -585,7 +566,7 @@ dwfix_t dwgpsnmea_gprmc (char *sentence, int quiet, double *odlat, double *odlon
*
* Name: dwgpsnmea_gpgga
*
- * Purpose: Parse $GPGGA sentence and extract interesing parts.
+ * Purpose: Parse $GPGGA sentence and extract interesting parts.
*
* Inputs: sentence NMEA sentence.
*
@@ -598,10 +579,13 @@ dwfix_t dwgpsnmea_gprmc (char *sentence, int quiet, double *odlat, double *odlon
*
* Left undefined if not valid.
*
+ * Note: GGA has altitude but not course and speed so we need to use both.
+ *
* Returns: DWFIX_ERROR Parse error.
* DWFIX_NO_FIX GPS is there but Position unknown. Could be temporary.
* DWFIX_2D Valid position. We don't know if it is really 2D or 3D.
* Take more cautious value so we don't try using altitude.
+ * DWFIX_3D Valid 3D position.
*
* Examples: $GPGGA,001429.00,,,,,0,00,99.99,,,,,,*68
* $GPGGA,212407.000,4237.1505,N,07120.8602,W,0,00,,,M,,M,,*58
@@ -610,9 +594,6 @@ dwfix_t dwgpsnmea_gprmc (char *sentence, int quiet, double *odlat, double *odlon
*
*--------------------------------------------------------------------*/
-
-// TODO: in progress...
-
dwfix_t dwgpsnmea_gpgga (char *sentence, int quiet, double *odlat, double *odlon, float *oalt, int *onsat)
{
char stemp[NMEA_MAX_LEN]; /* Make copy because parsing is destructive. */
@@ -709,8 +690,7 @@ dwfix_t dwgpsnmea_gpgga (char *sentence, int quiet, double *odlat, double *odlon
return (DWFIX_ERROR);
}
-
- // TODO: num sat...
+ // TODO: num sat... Why would we care?
/*
* We can distinguish between 2D & 3D fix by presence
diff --git a/src/dwsock.h b/src/dwsock.h
index 23e63e11..986f6a29 100644
--- a/src/dwsock.h
+++ b/src/dwsock.h
@@ -11,7 +11,7 @@
int dwsock_init (void);
-int dwsock_connect (char *hostname, char *port, char *description, int allow_ipv6, int debug, char *ipaddr_str);
+int dwsock_connect (char *hostname, char *port, char *description, int allow_ipv6, int debug, char ipaddr_str[DWSOCK_IPADDR_LEN]);
/* ipaddr_str needs to be at least SOCK_IPADDR_LEN bytes */
char *dwsock_ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t StringBufSize);
diff --git a/src/encode_aprs.c b/src/encode_aprs.c
index 225cb080..a4597f74 100644
--- a/src/encode_aprs.c
+++ b/src/encode_aprs.c
@@ -81,8 +81,14 @@ typedef struct position_s {
static int set_norm_position (char symtab, char symbol, double dlat, double dlong, int ambiguity, position_t *presult)
{
+ // An over zealous compiler might complain about l*itude_to_str writing
+ // N characters plus nul to an N character field so we stick it into a
+ // larger temp then copy the desired number of bytes. (Issue 296)
- latitude_to_str (dlat, ambiguity, presult->lat);
+ char stemp[16];
+
+ latitude_to_str (dlat, ambiguity, stemp);
+ memcpy (presult->lat, stemp, sizeof(presult->lat));
if (symtab != '/' && symtab != '\\' && ! isdigit(symtab) && ! isupper(symtab)) {
text_color_set(DW_COLOR_ERROR);
@@ -90,7 +96,8 @@ static int set_norm_position (char symtab, char symbol, double dlat, double dlon
}
presult->sym_table_id = symtab;
- longitude_to_str (dlong, ambiguity, presult->lon);
+ longitude_to_str (dlong, ambiguity, stemp);
+ memcpy (presult->lon, stemp, sizeof(presult->lon));
if (symbol < '!' || symbol > '~') {
text_color_set(DW_COLOR_ERROR);
@@ -118,7 +125,7 @@ static int set_norm_position (char symtab, char symbol, double dlat, double dlon
* height - Feet.
* gain - dBi.
*
- * course - Degress, 0 - 360 (360 equiv. to 0).
+ * course - Degrees, 0 - 360 (360 equiv. to 0).
* Use G_UNKNOWN for none or unknown.
* speed - knots.
*
@@ -336,7 +343,7 @@ static int phg_data_extension (int power, int height, int gain, char *dir, char
*
* Purpose: Fill in parts of the course & speed data extension.
*
- * Inputs: course - Degress, 0 - 360 (360 equiv. to 0).
+ * Inputs: course - Degrees, 0 - 360 (360 equiv. to 0).
* Use G_UNKNOWN for none or unknown.
*
* speed - knots.
@@ -487,7 +494,7 @@ static int frequency_spec (float freq, float tone, float offset, char *presult)
* gain - dB. Not clear if it is dBi or dBd.
* dir - Directivity: N, NE, etc., omni.
*
- * course - Degress, 0 - 360 (360 equiv. to 0).
+ * course - Degrees, 0 - 360 (360 equiv. to 0).
* Use G_UNKNOWN for none or unknown.
* speed - knots. // TODO: should distinguish unknown(not revevant) vs. known zero.
*
@@ -497,7 +504,7 @@ static int frequency_spec (float freq, float tone, float offset, char *presult)
*
* comment - Additional comment text.
*
- * result_size - Ammount of space for result, provideed by
+ * result_size - Amount of space for result, provided by
* caller, to avoid buffer overflow.
*
* Outputs: presult - Stored here. Should be at least ??? bytes.
@@ -512,7 +519,7 @@ static int frequency_spec (float freq, float tone, float offset, char *presult)
* Power/height/gain/directivity or
* Course/speed.
*
- * Afer that,
+ * After that,
*
*----------------------------------------------------------------*/
@@ -629,7 +636,7 @@ int encode_position (int messaging, int compressed, double lat, double lon, int
* gain - dB. Not clear if it is dBi or dBd.
* dir - Direction: N, NE, etc., omni.
*
- * course - Degress, 0 - 360 (360 equiv. to 0).
+ * course - Degrees, 0 - 360 (360 equiv. to 0).
* Use G_UNKNOWN for none or unknown.
* speed - knots.
*
@@ -639,7 +646,7 @@ int encode_position (int messaging, int compressed, double lat, double lon, int
*
* comment - Additional comment text.
*
- * result_size - Ammount of space for result, provideed by
+ * result_size - Amount of space for result, provided by
* caller, to avoid buffer overflow.
*
* Outputs: presult - Stored here. Should be at least ??? bytes.
@@ -769,7 +776,7 @@ int encode_object (char *name, int compressed, time_t thyme, double lat, double
* Inputs: addressee - Addressed to, up to 9 characters.
* text - Text part of the message.
* id - Identifier, 0 to 5 characters.
- * result_size - Ammount of space for result, provided by
+ * result_size - Amount of space for result, provided by
* caller, to avoid buffer overflow.
*
* Outputs: presult - Stored here.
@@ -853,7 +860,7 @@ int main (int argc, char *argv[])
dw_printf ("%s\n", result);
if (strcmp(result, "!4234.61ND07126.47W&PHG7368") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; }
-/* with freq & tone. minus offset, no offset, explict simplex. */
+/* with freq & tone. minus offset, no offset, explicit simplex. */
encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&',
0, 0, 0, NULL, G_UNKNOWN, 0, 146.955, 74.4, -0.6, NULL, result, sizeof(result));
diff --git a/src/fsk_demod_agc.h b/src/fsk_demod_agc.h
deleted file mode 100644
index 95c80794..00000000
--- a/src/fsk_demod_agc.h
+++ /dev/null
@@ -1,2 +0,0 @@
-#define TUNE_MS_FILTER_SIZE 140
-#define TUNE_PRE_BAUD 1.080
diff --git a/src/fsk_demod_state.h b/src/fsk_demod_state.h
index f7b56502..bf8d23b5 100644
--- a/src/fsk_demod_state.h
+++ b/src/fsk_demod_state.h
@@ -20,6 +20,31 @@ typedef enum bp_window_e { BP_WINDOW_TRUNCATED,
BP_WINDOW_BLACKMAN,
BP_WINDOW_FLATTOP } bp_window_t;
+// Experimental low pass filter to detect DC bias or low frequency changes.
+// IIR behaves like an analog R-C filter.
+// Intuitively, it seems like FIR would be better because it is based on a finite history.
+// However, it would require MANY taps and a LOT of computation for a low frequency.
+// We can use a little trick here to keep a running average.
+// This would be equivalent to convolving with an array of all 1 values.
+// That would eliminate the need to multiply.
+// We can also eliminate the need to add them all up each time by keeping a running total.
+// 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.
+// We can also eliminate the need to shift them all down by using a circular buffer.
+
+#define CIC_LEN_MAX 4000
+
+typedef struct cic_s {
+ int len; // Number of elements used.
+ // Might want to dynamically allocate.
+ short in[CIC_LEN_MAX]; // Samples coming in.
+ int sum; // Running sum.
+ int inext; // Next position to fill.
+} cic_t;
+
+
+#define MAX_FILTER_SIZE 404 /* 401 is needed for profile A, 300 baud & 44100. Revisit someday. */
+
struct demodulator_state_s
{
@@ -39,30 +64,12 @@ struct demodulator_state_s
// Data is sampled when it overflows.
- int ms_filter_size; /* Size of mark & space filters, in audio samples. */
- /* Started off as a guess of one bit length */
- /* but about 2 bit times turned out to be better. */
- /* Currently using same size for any prefilter. */
-
-
-#define MAX_FILTER_SIZE 320 /* 304 is needed for profile C, 300 baud & 44100. */
-
-/*
- * Filter length for Mark & Space in bit times.
- * e.g. 1 means 1/1200 second for 1200 baud.
- */
- float ms_filter_len_bits;
- float lp_delay_fract;
-
/*
* Window type for the various filters.
*/
- bp_window_t pre_window;
- bp_window_t ms_window;
bp_window_t lp_window;
-
/*
* Alternate Low pass filters.
* First is arbitrary number for quick IIR.
@@ -78,16 +85,13 @@ struct demodulator_state_s
/* In practice, it turned out a little larger */
/* for profiles B, C, D. */
- float lp_filter_len_bits; /* Length in number of bit times. */
+ float lp_filter_width_sym; /* Length in number of symbol times. */
- int lp_filter_size; /* Size of Low Pass filter, in audio samples. */
- /* Previously it was always the same as the M/S */
- /* filters but in version 1.2 it's now independent. */
+#define lp_filter_len_bits lp_filter_width_sym // FIXME: temp hack
- int lp_filter_delay; /* Number of samples that the low pass filter */
- /* delays the signal. */
-
- /* New in 1.6. */
+ int lp_filter_taps; /* Size of Low Pass filter, in audio samples. */
+
+#define lp_filter_size lp_filter_taps // FIXME: temp hack
/*
@@ -111,6 +115,7 @@ struct demodulator_state_s
/*
* Phase Locked Loop (PLL) inertia.
* Larger number means less influence by signal transitions.
+ * It is more resistant to change when locked on to a signal.
*/
float pll_locked_inertia;
float pll_searching_inertia;
@@ -129,23 +134,17 @@ struct demodulator_state_s
/* lower = min(1600,1800) - 0.5 * 300 = 1450 */
/* upper = max(1600,1800) + 0.5 * 300 = 1950 */
- float pre_filter_len_bits; /* Length in number of bit times. */
+ float pre_filter_len_sym; // Length in number of symbol times.
+#define pre_filter_len_bits pre_filter_len_sym // temp until all references changed.
- int pre_filter_size; /* Size of pre filter, in audio samples. */
+ bp_window_t pre_window; // Window type for filter shaping.
- float pre_filter[MAX_FILTER_SIZE] __attribute__((aligned(16)));
-
-
-/*
- * Kernel for the mark and space detection filters.
- */
-
- float m_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
- float m_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+ int pre_filter_taps; // Calculated number of filter taps.
+#define pre_filter_size pre_filter_taps // temp until all references changed.
- float s_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
- float s_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+ float pre_filter[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+ float raw_cb[MAX_FILTER_SIZE] __attribute__((aligned(16))); // audio in, need better name.
/*
* The rest are continuously updated.
@@ -154,11 +153,6 @@ struct demodulator_state_s
unsigned int lo_phase; /* Local oscillator for PSK. */
-/*
- * Most recent raw audio samples, before/after prefiltering.
- */
- float raw_cb[MAX_FILTER_SIZE] __attribute__((aligned(16)));
-
/*
* Use half of the AGC code to get a measure of input audio amplitude.
* These use "quick" attack and "sluggish" decay while the
@@ -170,24 +164,14 @@ struct demodulator_state_s
float alevel_mark_peak;
float alevel_space_peak;
-/*
- * Input to the mark/space detector.
- * Could be prefiltered or raw audio.
- */
- float ms_in_cb[MAX_FILTER_SIZE] __attribute__((aligned(16)));
-
/*
* Outputs from the mark and space amplitude detection,
* used as inputs to the FIR lowpass filters.
* Kernel for the lowpass filters.
*/
- float m_amp_cb[MAX_FILTER_SIZE] __attribute__((aligned(16)));
- float s_amp_cb[MAX_FILTER_SIZE] __attribute__((aligned(16)));
-
float lp_filter[MAX_FILTER_SIZE] __attribute__((aligned(16)));
-
float m_peak, s_peak;
float m_valley, s_valley;
float m_amp_prev, s_amp_prev;
@@ -266,6 +250,122 @@ struct demodulator_state_s
union {
+//////////////////////////////////////////////////////////////////////////////////
+// //
+// AFSK only - new method in 1.7 //
+// //
+//////////////////////////////////////////////////////////////////////////////////
+
+
+ struct afsk_only_s {
+
+ unsigned int m_osc_phase; // Phase for Mark local oscillator.
+ unsigned int m_osc_delta; // How much to change for each audio sample.
+
+ unsigned int s_osc_phase; // Phase for Space local oscillator.
+ unsigned int s_osc_delta; // How much to change for each audio sample.
+
+ unsigned int c_osc_phase; // Phase for Center frequency local oscillator.
+ unsigned int c_osc_delta; // How much to change for each audio sample.
+
+ // Need two mixers for profile "A".
+
+ float m_I_raw[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+ float m_Q_raw[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+
+ float s_I_raw[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+ float s_Q_raw[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+
+ // Only need one mixer for profile "B". Reuse the same storage?
+
+//#define c_I_raw m_I_raw
+//#define c_Q_raw m_Q_raw
+ float c_I_raw[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+ float c_Q_raw[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+
+ int use_rrc; // Use RRC rather than generic low pass.
+
+ float rrc_width_sym; /* Width of RRC filter in number of symbols. */
+
+ float rrc_rolloff; /* Rolloff factor for RRC. Between 0 and 1. */
+
+ float prev_phase; // To see phase shift between samples for FM demod.
+
+ float normalize_rpsam; // Normalize to -1 to +1 for expected tones.
+
+ } afsk;
+
+//////////////////////////////////////////////////////////////////////////////////
+// //
+// Baseband only, AKA G3RUH //
+// //
+//////////////////////////////////////////////////////////////////////////////////
+
+// TODO: Continue experiments with root raised cosine filter.
+// Either switch to that or take out all the related stuff.
+
+ struct bb_only_s {
+
+ float rrc_width_sym; /* Width of RRC filter in number of symbols. */
+
+ float rrc_rolloff; /* Rolloff factor for RRC. Between 0 and 1. */
+
+ int rrc_filter_taps; // Number of elements used in the next two.
+
+// FIXME: TODO: reevaluate max size needed.
+
+ float audio_in[MAX_FILTER_SIZE] __attribute__((aligned(16))); // Audio samples in.
+
+
+ float lp_filter[MAX_FILTER_SIZE] __attribute__((aligned(16))); // Low pass filter.
+
+ // New in 1.7 - Polyphase filter to reduce CPU requirements.
+
+ float lp_polyphase_1[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+ float lp_polyphase_2[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+ float lp_polyphase_3[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+ float lp_polyphase_4[MAX_FILTER_SIZE] __attribute__((aligned(16)));
+
+ float lp_1_iir_param; // very low pass filters to get DC offset.
+ float lp_1_out;
+
+ float lp_2_iir_param;
+ float lp_2_out;
+
+ float agc_1_fast_attack; // Signal envelope detection.
+ float agc_1_slow_decay;
+ float agc_1_peak;
+ float agc_1_valley;
+
+ float agc_2_fast_attack;
+ float agc_2_slow_decay;
+ float agc_2_peak;
+ float agc_2_valley;
+
+ float agc_3_fast_attack;
+ float agc_3_slow_decay;
+ float agc_3_peak;
+ float agc_3_valley;
+
+ // CIC low pass filters to detect DC bias or low frequency changes.
+ // IIR behaves like an analog R-C filter.
+ // Intuitively, it seems like FIR would be better because it is based on a finite history.
+ // However, it would require MANY taps and a LOT of computation for a low frequency.
+ // We can use a little trick here to keep a running average.
+ // This would be equivalent to convolving with an array of all 1 values.
+ // That would eliminate the need to multiply.
+ // We can also eliminate the need to add them all up each time by keeping a running total.
+ // 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.
+ // 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.
+
+ cic_t cic_center1;
+ cic_t cic_above;
+ cic_t cic_below;
+
+ } bb;
+
//////////////////////////////////////////////////////////////////////////////////
// //
// PSK only. //
diff --git a/src/fx25_init.c b/src/fx25_init.c
index 9031a6de..2844fb93 100644
--- a/src/fx25_init.c
+++ b/src/fx25_init.c
@@ -410,19 +410,25 @@ struct rs *INIT_RS(unsigned int symsize,unsigned int gfpoly,unsigned fcr,unsigne
return NULL; /* Can't have more roots than symbol values! */
rs = (struct rs *)calloc(1,sizeof(struct rs));
+ if (rs == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
+ }
rs->mm = symsize;
rs->nn = (1<alpha_to = (DTYPE *)malloc(sizeof(DTYPE)*(rs->nn+1));
+ rs->alpha_to = (DTYPE *)calloc((rs->nn+1),sizeof(DTYPE));
if(rs->alpha_to == NULL){
- free(rs);
- return NULL;
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
}
- rs->index_of = (DTYPE *)malloc(sizeof(DTYPE)*(rs->nn+1));
+ rs->index_of = (DTYPE *)calloc((rs->nn+1),sizeof(DTYPE));
if(rs->index_of == NULL){
- free(rs->alpha_to);
- free(rs);
- return NULL;
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
}
/* Generate Galois field lookup tables */
@@ -446,14 +452,13 @@ struct rs *INIT_RS(unsigned int symsize,unsigned int gfpoly,unsigned fcr,unsigne
}
/* Form RS code generator polynomial from its roots */
- rs->genpoly = (DTYPE *)malloc(sizeof(DTYPE)*(nroots+1));
+ rs->genpoly = (DTYPE *)calloc((nroots+1),sizeof(DTYPE));
if(rs->genpoly == NULL){
- free(rs->alpha_to);
- free(rs->index_of);
- free(rs);
- return NULL;
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
}
- rs->fcr = fcr;
+ rs->fcr = fcr;
rs->prim = prim;
rs->nroots = nroots;
@@ -482,7 +487,7 @@ struct rs *INIT_RS(unsigned int symsize,unsigned int gfpoly,unsigned fcr,unsigne
}
// diagnostic prints
-/*
+#if 0
printf("Alpha To:\n\r");
for (i=0; i < sizeof(DTYPE)*(rs->nn+1); i++)
printf("0x%2x,", rs->alpha_to[i]);
@@ -497,7 +502,7 @@ struct rs *INIT_RS(unsigned int symsize,unsigned int gfpoly,unsigned fcr,unsigne
for (i = 0; i <= nroots; i++)
printf("0x%2x,", rs->genpoly[i]);
printf("\n\r");
-*/
+#endif
return rs;
}
diff --git a/src/fx25_rec.c b/src/fx25_rec.c
index 3ab78518..9cb5c4d9 100644
--- a/src/fx25_rec.c
+++ b/src/fx25_rec.c
@@ -258,7 +258,7 @@ int fx25_rec_busy (int chan)
{
assert (chan >= 0 && chan < MAX_CHANS);
- // This could be a litle faster if we knew number of
+ // This could be a little faster if we knew number of
// subchannels and slicers but it is probably insignificant.
for (int i = 0; i < MAX_SUBCHANS; i++) {
diff --git a/src/gen_packets.c b/src/gen_packets.c
index b0977906..ba58abbd 100644
--- a/src/gen_packets.c
+++ b/src/gen_packets.c
@@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
-// Copyright (C) 2011, 2013, 2014, 2015, 2016, 2019 John Langner, WB2OSZ
+// Copyright (C) 2011, 2013, 2014, 2015, 2016, 2019, 2021 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -76,6 +76,7 @@
#include "morse.h"
#include "dtmf.h"
#include "fx25.h"
+#include "il2p.h"
/* Own random number generator so we can get */
@@ -123,6 +124,7 @@ static void send_packet (char *str)
return;
}
flen = ax25_pack (pp, fbuf);
+ (void)flen;
for (c=0; c 0) {
+ if (I_opt != -1 || i_opt != -1) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Can't mix -X with -I or -i.\n");
+ exit (EXIT_FAILURE);
+ }
+ modem.achan[0].fx25_strength = X_opt;
+ modem.achan[0].layer2_xmit = LAYER2_FX25;
+ }
+
+ if (I_opt != -1 && i_opt != -1) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Can't use both -I and -i at the same time.\n");
+ exit (EXIT_FAILURE);
+ }
+
+ if (I_opt >= 0) {
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("Using IL2P normal polarity.\n");
+ modem.achan[0].layer2_xmit = LAYER2_IL2P;
+ modem.achan[0].il2p_max_fec = (I_opt > 0);
+ modem.achan[0].il2p_invert_polarity = 0; // normal
+ }
+
+ if (i_opt >= 0) {
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("Using IL2P inverted polarity.\n");
+ modem.achan[0].layer2_xmit = LAYER2_IL2P;
+ modem.achan[0].il2p_max_fec = (i_opt > 0);
+ modem.achan[0].il2p_invert_polarity = 1; // invert for transmit
+ if (modem.achan[0].baud == 1200) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Using -i with 1200 bps is a bad idea. Use -I instead.\n");
+ }
+ }
+
+
/*
* Open the output file.
*/
@@ -536,6 +586,7 @@ int main(int argc, char **argv)
// Just use the default of minimal information.
fx25_init (1);
+ il2p_init (0); // There are no "-d" options so far but it could be handy here.
assert (modem.adev[0].bits_per_sample == 8 || modem.adev[0].bits_per_sample == 16);
assert (modem.adev[0].num_channels == 1 || modem.adev[0].num_channels == 2);
@@ -669,7 +720,9 @@ static void usage (char **argv)
dw_printf (" -g Scrambled baseband rather than AFSK.\n");
dw_printf (" -j 2400 bps QPSK compatible with direwolf <= 1.5.\n");
dw_printf (" -J 2400 bps QPSK compatible with MFJ-2400.\n");
- dw_printf (" -X n Generate FX.25 frames. Specify number of check bytes: 16, 32, or 64.\n");
+ dw_printf (" -X n 1 to enable FX.25 transmit. 16, 32, 64 for specific number of check bytes.\n");
+ dw_printf (" -I n Enable IL2P transmit. n=1 is recommended. 0 uses weaker FEC.\n");
+ dw_printf (" -i n Enable IL2P transmit, inverted polarity. n=1 is recommended. 0 uses weaker FEC.\n");
dw_printf (" -m Mark frequency. Default is %d.\n", DEFAULT_MARK_FREQ);
dw_printf (" -s Space frequency. Default is %d.\n", DEFAULT_SPACE_FREQ);
dw_printf (" -r Audio sample Rate. Default is %d.\n", DEFAULT_SAMPLES_PER_SEC);
diff --git a/src/gen_tone.c b/src/gen_tone.c
index 68f72bc0..3317aa32 100644
--- a/src/gen_tone.c
+++ b/src/gen_tone.c
@@ -164,7 +164,7 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets)
for (chan = 0; chan < MAX_CHANS; chan++) {
- if (audio_config_p->achan[chan].medium == MEDIUM_RADIO) {
+ if (audio_config_p->chan_medium[chan] == MEDIUM_RADIO) {
int a = ACHAN2ADEV(chan);
@@ -295,7 +295,7 @@ void tone_gen_put_bit (int chan, int dat)
assert (save_audio_config_p != NULL);
- if (save_audio_config_p->achan[chan].medium != MEDIUM_RADIO) {
+ if (save_audio_config_p->chan_medium[chan] != MEDIUM_RADIO) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid channel %d for tone generation.\n", chan);
return;
@@ -360,7 +360,10 @@ void tone_gen_put_bit (int chan, int dat)
bit_count[chan] = 0;
}
- if (save_audio_config_p->achan[chan].modem_type == MODEM_SCRAMBLE) {
+ // Would be logical to have MODEM_BASEBAND for IL2P rather than checking here. But...
+ // That would mean putting in at least 3 places and testing all rather than just one.
+ if (save_audio_config_p->achan[chan].modem_type == MODEM_SCRAMBLE &&
+ save_audio_config_p->achan[chan].layer2_xmit != LAYER2_IL2P) {
int x;
x = (dat ^ (lfsr[chan] >> 16) ^ (lfsr[chan] >> 11)) & 1;
@@ -380,7 +383,14 @@ void tone_gen_put_bit (int chan, int dat)
text_color_set(DW_COLOR_DEBUG);
dw_printf ("tone_gen_put_bit %d AFSK\n", __LINE__);
#endif
- tone_phase[chan] += dat ? f2_change_per_sample[chan] : f1_change_per_sample[chan];
+
+ // v1.7 reversed.
+ // Previously a data '1' selected the second (usually higher) tone.
+ // It never really mattered before because we were using NRZI.
+ // With the addition of IL2P, we need to be more careful.
+ // A data '1' should be the mark tone.
+
+ 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;
diff --git a/src/grm_sym.h b/src/grm_sym.h
index 11fb4fad..7a15041b 100644
--- a/src/grm_sym.h
+++ b/src/grm_sym.h
@@ -433,9 +433,9 @@ static const symbol_type_t grm_alternate_symtab[SYMTAB_SIZE] = {
sym_park, // ; 27 Park/Picnic area
sym_default, // < 28 ADVISORY (one WX flag)
sym_rbcn, // = 29 APRStt Touchtone (DTMF users)
- sym_car, // > 30 OVERLAYED CAR
+ sym_car, // > 30 OVERLAID CAR
sym_default, // ? 31 INFO Kiosk (Blue box with ?)
- sym_default, // @ 32 HURICANE/Trop-Storm
+ sym_default, // @ 32 HURRICANE/Trop-Storm
sym_default, // A 33 overlayBOX DTMF & RFID & XO
sym_default, // B 34 Blwng Snow (& future codes)
sym_coast_guard, // C 35 Coast Guard
@@ -445,7 +445,7 @@ static const symbol_type_t grm_alternate_symtab[SYMTAB_SIZE] = {
sym_default, // G 39 Snow Shwr (& future ovrlys)
sym_default, // H 40 Haze (& Overlay Hazards)
sym_default, // I 41 Rain Shower
- sym_default, // J 42 Lightening (& future ovrlys)
+ sym_default, // J 42 Lightning (& future ovrlys)
sym_rbcn, // K 43 Kenwood HT (W)
sym_light, // L 44 Lighthouse
sym_default, // M 45 MARS (A=Army,N=Navy,F=AF)
@@ -488,12 +488,12 @@ static const symbol_type_t grm_alternate_symtab[SYMTAB_SIZE] = {
sym_restrooms, // r 82 Restrooms
sym_default, // s 83 OVERLAY SHIP/boat (top view)
sym_default, // t 84 Tornado
- sym_car, // u 85 OVERLAYED TRUCK
- sym_car, // v 86 OVERLAYED Van
+ sym_car, // u 85 OVERLAID TRUCK
+ sym_car, // v 86 OVERLAID Van
sym_default, // w 87 Flooding
sym_wreck, // x 88 Wreck or Obstruction ->X<-
sym_default, // y 89 Skywarn
- sym_default, // z 90 OVERLAYED Shelter
+ sym_default, // z 90 OVERLAID Shelter
sym_default, // { 91 Fog (& future ovrly codes)
sym_default, // | 92 TNC Stream Switch
sym_default, // } 93
diff --git a/src/hdlc_rec.c b/src/hdlc_rec.c
index 6b395be6..d87a1b50 100644
--- a/src/hdlc_rec.c
+++ b/src/hdlc_rec.c
@@ -46,6 +46,7 @@
#include "demod_9600.h" /* for descramble() */
#include "ptt.h"
#include "fx25.h"
+#include "il2p.h"
//#define TEST 1 /* Define for unit testing. */
@@ -151,7 +152,7 @@ void hdlc_rec_init (struct audio_s *pa)
for (ch = 0; ch < MAX_CHANS; ch++)
{
- if (pa->achan[ch].medium == MEDIUM_RADIO) {
+ if (pa->chan_medium[ch] == MEDIUM_RADIO) {
num_subchan[ch] = pa->achan[ch].num_subchan;
@@ -496,6 +497,7 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled,
if (g_audio_p->achan[chan].modem_type != MODEM_AIS) {
fx25_rec_bit (chan, subchan, slice, dbit);
+ il2p_rec_bit (chan, subchan, slice, raw); // Note: skip NRZI.
}
/*
@@ -759,7 +761,7 @@ void dcd_change (int chan, int subchan, int slice, int state)
*
* Name: hdlc_rec_data_detect_any
*
- * Purpose: Determine if the radio channel is curently busy
+ * Purpose: Determine if the radio channel is currently busy
* with packet data.
* This version doesn't care about voice or other sounds.
* This is used by the transmit logic to transmit only
@@ -774,7 +776,7 @@ void dcd_change (int chan, int subchan, int slice, int state)
* Description: We have two different versions here.
*
* hdlc_rec_data_detect_any sees if ANY of the decoders
- * for this channel are receving a signal. This is
+ * for this channel are receiving a signal. This is
* used to determine whether the channel is clear and
* we can transmit. This would apply to the 300 baud
* HF SSB case where we have multiple decoders running
diff --git a/src/hdlc_rec2.c b/src/hdlc_rec2.c
index e23aaee7..b817018f 100644
--- a/src/hdlc_rec2.c
+++ b/src/hdlc_rec2.c
@@ -273,7 +273,7 @@ void hdlc_rec2_block (rrbb_t block)
}
/*
- * Not successful with frame in orginal form.
+ * Not successful with frame in original form.
* See if we can "fix" it.
*/
if (try_to_fix_quick_now (block, chan, subchan, slice, alevel)) {
diff --git a/src/hdlc_send.c b/src/hdlc_send.c
index 2d170f67..0818d0dc 100644
--- a/src/hdlc_send.c
+++ b/src/hdlc_send.c
@@ -2,7 +2,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
-// Copyright (C) 2011, 2013, 2014, 2019 John Langner, WB2OSZ
+// Copyright (C) 2011, 2013, 2014, 2019, 2021 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -27,37 +27,37 @@
#include "gen_tone.h"
#include "textcolor.h"
#include "fcs_calc.h"
+#include "ax25_pad.h"
#include "fx25.h"
+#include "il2p.h"
-static void send_control (int, int);
-static void send_data (int, int);
-static void send_bit (int, int);
+static void send_byte_msb_first (int chan, int x, int polarity);
+static void send_control_nrzi (int, int);
+static void send_data_nrzi (int, int);
+static void send_bit_nrzi (int, int);
-static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "hdlc_send_frame" or "hdlc_send_flags"
-
+static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "hdlc_send_frame" or "hdlc_send_flags"
/*-------------------------------------------------------------
*
- * Name: hdlc_send
+ * Name: layer2_send_frame
*
- * Purpose: Convert HDLC frames to a stream of bits.
+ * Purpose: Convert frames to a stream of bits.
+ * Originally this was for AX.25 only, hence the file name.
+ * Over time, FX.25 and IL2P were shoehorned in.
*
* Inputs: chan - Audio channel number, 0 = first.
*
- * fbuf - Frame buffer address.
- *
- * flen - Frame length, not including the FCS.
+ * pp - Packet object.
*
* bad_fcs - Append an invalid FCS for testing purposes.
* Applies only to regular AX.25.
*
- * fx25_xmit_enable - Just like the name says.
- *
* Outputs: Bits are shipped out by calling tone_gen_put_bit().
*
* Returns: Number of bits sent including "flags" and the
@@ -65,12 +65,12 @@ static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "hdl
* The required time can be calculated by dividing this
* number by the transmit rate of bits/sec.
*
- * Description: Convert to stream of bits including:
+ * Description: For AX.25, send:
* start flag
* bit stuffed data
* calculated FCS
* end flag
- * NRZI encoding
+ * NRZI encoding for all but the "flags."
*
*
* Assumptions: It is assumed that the tone_gen module has been
@@ -81,23 +81,40 @@ static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "hdl
static int ax25_only_hdlc_send_frame (int chan, unsigned char *fbuf, int flen, int bad_fcs);
-// New in 1.6: Option to encapsulate in FX.25.
-int hdlc_send_frame (int chan, unsigned char *fbuf, int flen, int bad_fcs, int fx25_xmit_enable)
+int layer2_send_frame (int chan, packet_t pp, int bad_fcs, struct audio_s *audio_config_p)
{
- if (fx25_xmit_enable) {
- int n = fx25_send_frame (chan, fbuf, flen, fx25_xmit_enable);
+ if (audio_config_p->achan[chan].layer2_xmit == LAYER2_IL2P) {
+
+ int n = il2p_send_frame (chan, pp, audio_config_p->achan[chan].il2p_max_fec,
+ audio_config_p->achan[chan].il2p_invert_polarity);
+ if (n > 0) {
+ return (n);
+ }
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("Unable to send IL2p frame. Falling back to regular AX.25.\n");
+ // Not sure if we should fall back to AX.25 or not here.
+ }
+ else if (audio_config_p->achan[chan].layer2_xmit == LAYER2_FX25) {
+ unsigned char fbuf[AX25_MAX_PACKET_LEN+2];
+ int flen = ax25_pack (pp, fbuf);
+ int n = fx25_send_frame (chan, fbuf, flen, audio_config_p->achan[chan].fx25_strength);
if (n > 0) {
return (n);
}
text_color_set(DW_COLOR_ERROR);
dw_printf ("Unable to send FX.25. Falling back to regular AX.25.\n");
+ // Definitely need to fall back to AX.25 here because
+ // the FX.25 frame length is so limited.
}
+ unsigned char fbuf[AX25_MAX_PACKET_LEN+2];
+ int flen = ax25_pack (pp, fbuf);
return (ax25_only_hdlc_send_frame (chan, fbuf, flen, bad_fcs));
}
+
static int ax25_only_hdlc_send_frame (int chan, unsigned char *fbuf, int flen, int bad_fcs)
{
int j, fcs;
@@ -105,33 +122,31 @@ static int ax25_only_hdlc_send_frame (int chan, unsigned char *fbuf, int flen, i
number_of_bits_sent[chan] = 0;
-
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("hdlc_send_frame ( chan = %d, fbuf = %p, flen = %d, bad_fcs = %d)\n", chan, fbuf, flen, bad_fcs);
fflush (stdout);
#endif
-
- send_control (chan, 0x7e); /* Start frame */
+ send_control_nrzi (chan, 0x7e); /* Start frame */
for (j=0; j> 8) & 0xff);
+ send_data_nrzi (chan, (~fcs) & 0xff);
+ send_data_nrzi (chan, ((~fcs) >> 8) & 0xff);
}
else {
- send_data (chan, fcs & 0xff);
- send_data (chan, (fcs >> 8) & 0xff);
+ send_data_nrzi (chan, fcs & 0xff);
+ send_data_nrzi (chan, (fcs >> 8) & 0xff);
}
- send_control (chan, 0x7e); /* End frame */
+ send_control_nrzi (chan, 0x7e); /* End frame */
return (number_of_bits_sent[chan]);
}
@@ -139,22 +154,25 @@ static int ax25_only_hdlc_send_frame (int chan, unsigned char *fbuf, int flen, i
/*-------------------------------------------------------------
*
- * Name: hdlc_send_flags
+ * Name: layer2_preamble_postamble
*
- * Purpose: Send HDLC flags before and after the frame.
+ * Purpose: Send filler pattern before and after the frame.
+ * For HDLC it is 01111110, for IL2P 01010101.
*
* Inputs: chan - Audio channel number, 0 = first.
*
- * nflags - Number of flag patterns to send.
+ * nbytes - Number of bytes to send.
*
* finish - True for end of transmission.
* This causes the last audio buffer to be flushed.
*
+ * audio_config_p - Configuration for audio and modems.
+ *
* Outputs: Bits are shipped out by calling tone_gen_put_bit().
*
* Returns: Number of bits sent.
* There is no bit-stuffing so we would expect this to
- * be 8 * nflags.
+ * be 8 * nbytes.
* The required time can be calculated by dividing this
* number by the transmit rate of bits/sec.
*
@@ -164,25 +182,30 @@ static int ax25_only_hdlc_send_frame (int chan, unsigned char *fbuf, int flen, i
*
*--------------------------------------------------------------*/
-int hdlc_send_flags (int chan, int nflags, int finish)
+int layer2_preamble_postamble (int chan, int nbytes, int finish, struct audio_s *audio_config_p)
{
int j;
-
number_of_bits_sent[chan] = 0;
-
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("hdlc_send_flags ( chan = %d, nflags = %d, finish = %d )\n", chan, nflags, finish);
fflush (stdout);
#endif
- /* The AX.25 spec states that when the transmitter is on but not sending data */
- /* it should send a continuous stream of "flags." */
+ // When the transmitter is on but not sending data, it should be sending
+ // a stream of a filler pattern.
+ // For AX.25, it is the 01111110 "flag" pattern with NRZI and no bit stuffing.
+ // For IL2P, it is 01010101 without NRZI.
- for (j=0; jachan[chan].layer2_xmit == LAYER2_IL2P) {
+ send_byte_msb_first (chan, IL2P_PREAMBLE, audio_config_p->achan[chan].il2p_invert_polarity);
+ }
+ else {
+ send_control_nrzi (chan, 0x7e);
+ }
}
/* Push out the final partial buffer! */
@@ -196,33 +219,54 @@ int hdlc_send_flags (int chan, int nflags, int finish)
+// The next one is only for IL2P. No NRZI.
+// MSB first, opposite of AX.25.
+
+static void send_byte_msb_first (int chan, int x, int polarity)
+{
+ int i;
+
+ for (i=0; i<8; i++) {
+ int dbit = (x & 0x80) != 0;
+ tone_gen_put_bit (chan, (dbit ^ polarity) & 1);
+ x <<= 1;
+ number_of_bits_sent[chan]++;
+ }
+}
+
+
+// The following are only for HDLC.
+// All bits are sent NRZI.
+// Data (non flags) use bit stuffing.
+
+
static int stuff[MAX_CHANS]; // Count number of "1" bits to keep track of when we
// need to break up a long run by "bit stuffing."
// Needs to be array because we could be transmitting
// on multiple channels at the same time.
-static void send_control (int chan, int x)
+static void send_control_nrzi (int chan, int x)
{
int i;
for (i=0; i<8; i++) {
- send_bit (chan, x & 1);
+ send_bit_nrzi (chan, x & 1);
x >>= 1;
}
stuff[chan] = 0;
}
-static void send_data (int chan, int x)
+static void send_data_nrzi (int chan, int x)
{
int i;
for (i=0; i<8; i++) {
- send_bit (chan, x & 1);
+ send_bit_nrzi (chan, x & 1);
if (x & 1) {
stuff[chan]++;
if (stuff[chan] == 5) {
- send_bit (chan, 0);
+ send_bit_nrzi (chan, 0);
stuff[chan] = 0;
}
} else {
@@ -238,7 +282,7 @@ static void send_data (int chan, int x)
* data 0 bit -> invert signal.
*/
-static void send_bit (int chan, int b)
+static void send_bit_nrzi (int chan, int b)
{
static int output[MAX_CHANS];
diff --git a/src/hdlc_send.h b/src/hdlc_send.h
index 4ebbcc2e..4f8a1053 100644
--- a/src/hdlc_send.h
+++ b/src/hdlc_send.h
@@ -1,9 +1,16 @@
/* hdlc_send.h */
-int hdlc_send_frame (int chan, unsigned char *fbuf, int flen, int bad_fcs, int fx25_xmit_enable);
+// In version 1.7 an extra layer of abstraction was added here.
+// Rather than calling hdlc_send_frame, we now use another function
+// which sends AX.25, FX.25, or IL2P depending on
-int hdlc_send_flags (int chan, int flags, int finish);
+#include "ax25_pad.h"
+#include "audio.h"
+
+int layer2_send_frame (int chan, packet_t pp, int bad_fcs, struct audio_s *audio_config_p);
+
+int layer2_preamble_postamble (int chan, int flags, int finish, struct audio_s *audio_config_p);
/* end hdlc_send.h */
diff --git a/src/igate.c b/src/igate.c
index 37cd34fa..bb63e367 100644
--- a/src/igate.c
+++ b/src/igate.c
@@ -100,6 +100,7 @@
#include "version.h"
#include "digipeater.h"
#include "tq.h"
+#include "dlq.h"
#include "igate.h"
#include "latlong.h"
#include "pfilter.h"
@@ -107,6 +108,7 @@
#include "mheard.h"
+
#if __WIN32__
static unsigned __stdcall connnect_thread (void *arg);
static unsigned __stdcall igate_recv_thread (void *arg);
@@ -1469,7 +1471,7 @@ static void * igate_recv_thread (void *arg)
*
* Future: might have ability to configure multiple transmit
* channels, each with own client side filtering and via path.
- * Loop here over all configured channels.
+ * If so, loop here over all configured channels.
*/
text_color_set(DW_COLOR_REC);
dw_printf ("\n[ig>tx] "); // formerly just [ig]
@@ -1504,6 +1506,60 @@ static void * igate_recv_thread (void *arg)
if (to_chan >= 0) {
maybe_xmit_packet_from_igate ((char*)message, to_chan);
}
+
+
+/*
+ * New in 1.7: If ICHANNEL was specified, send packet to client app as specified channel.
+ */
+ if (save_audio_config_p->igate_vchannel >= 0) {
+
+ int ichan = save_audio_config_p->igate_vchannel;
+
+ // Try to parse it into a packet object.
+ // 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.
+
+ // Possible problem: Up to 8 digipeaters are allowed in radio format.
+ // There is a potential of finding a larger number here.
+
+ packet_t pp3 = ax25_from_text((char*)message, 0); // 0 means not strict
+ 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;
+ memset (&alevel, 0, sizeof(alevel));
+ alevel.mark = -2; // FIXME: Do we want some other special case?
+ alevel.space = -2;
+
+ int subchan = -2; // FIXME: -1 is special case for APRStt.
+ // See what happens with -2 and follow up on this.
+ // Do we need something else here?
+ int slice = 0;
+ int is_fx25 = 0;
+ char spectrum[] = "APRS-IS";
+ dlq_rec_frame (ichan, subchan, slice, pp3, alevel, is_fx25, RETRY_NONE, spectrum);
+ }
+ else {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("ICHANNEL %d: Could not parse message from APRS-IS server.\n", ichan);
+ dw_printf ("%s\n", message);
+ }
+ } // end ICHANNEL option
}
} /* while (1) */
@@ -1538,9 +1594,14 @@ static void * igate_recv_thread (void *arg)
* Duplicate removal will drop the original if there is no
* corresponding digipeated version.
*
+ *
+ * This was an idea that came up in one of the discussion forums.
+ * I rushed in without thinking about it very much.
+ *
* In retrospect, I don't think this was such a good idea.
* It would be of value only if there is no other IGate nearby
* that would report on the original transmission.
+ * I wonder if anyone would notice if this silently disappeared.
*
*--------------------------------------------------------------------*/
@@ -2211,7 +2272,7 @@ static int rx_to_ig_allow (packet_t pp)
*
* Future:
* Should the digipeater function avoid transmitting something if it
- * was recently transmitted by the IGate funtion?
+ * was recently transmitted by the IGate function?
* This code is pretty much the same as dedupe.c. Maybe it could all
* be combined into one. Need to ponder this some more.
*
diff --git a/src/il2p.h b/src/il2p.h
new file mode 100644
index 00000000..d18e2d05
--- /dev/null
+++ b/src/il2p.h
@@ -0,0 +1,145 @@
+
+
+#ifndef IL2P_H
+#define IL2P_H 1
+
+
+#define IL2P_PREAMBLE 0x55
+
+#define IL2P_SYNC_WORD 0xF15E48
+
+#define IL2P_SYNC_WORD_SIZE 3
+#define IL2P_HEADER_SIZE 13 // Does not include 2 parity.
+#define IL2P_HEADER_PARITY 2
+
+#define IL2P_MAX_PAYLOAD_SIZE 1023
+#define IL2P_MAX_PAYLOAD_BLOCKS 5
+#define IL2P_MAX_PARITY_SYMBOLS 16 // For payload only.
+#define IL2P_MAX_ENCODED_PAYLOAD_SIZE (IL2P_MAX_PAYLOAD_SIZE + IL2P_MAX_PAYLOAD_BLOCKS * IL2P_MAX_PARITY_SYMBOLS)
+
+#define IL2P_MAX_PACKET_SIZE (IL2P_SYNC_WORD_SIZE + IL2P_HEADER_SIZE + IL2P_HEADER_PARITY + IL2P_MAX_ENCODED_PAYLOAD_SIZE)
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// il2p_init.c
+//
+///////////////////////////////////////////////////////////////////////////////
+
+
+// Init must be called at start of application.
+
+extern void il2p_init (int debug);
+
+#include "fx25.h" // For Reed Solomon stuff. e.g. struct rs
+ // Maybe rearrange someday because RS now used another place.
+
+extern struct rs *il2p_find_rs(int nparity); // Internal later?
+
+extern void il2p_encode_rs (unsigned char *tx_data, int data_size, int num_parity, unsigned char *parity_out);
+
+extern int il2p_decode_rs (unsigned char *rec_block, int data_size, int num_parity, unsigned char *out);
+
+extern int il2p_get_debug(void);
+extern void il2p_set_debug(int debug);
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// il2p_rec.c
+//
+///////////////////////////////////////////////////////////////////////////////
+
+// Receives a bit stream from demodulator.
+
+extern void il2p_rec_bit (int chan, int subchan, int slice, int dbit);
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// il2p_send.c
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "ax25_pad.h" // For packet object.
+
+// Send bit stream to modulator.
+
+int il2p_send_frame (int chan, packet_t pp, int max_fec, int polarity);
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// il2p_codec.c
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "ax25_pad.h"
+
+extern int il2p_encode_frame (packet_t pp, int max_fec, unsigned char *iout);
+
+packet_t il2p_decode_frame (unsigned char *irec);
+
+packet_t il2p_decode_header_payload (unsigned char* uhdr, unsigned char *epayload, int *symbols_corrected);
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// il2p_header.c
+//
+///////////////////////////////////////////////////////////////////////////////
+
+
+extern int il2p_type_1_header (packet_t pp, int max_fec, unsigned char *hdr);
+
+extern packet_t il2p_decode_header_type_1 (unsigned char *hdr, int num_sym_changed);
+
+
+extern int il2p_type_0_header (packet_t pp, int max_fec, unsigned char *hdr);
+
+extern int il2p_clarify_header(unsigned char *rec_hdr, unsigned char *corrected_descrambled_hdr);
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// il2p_scramble.c
+//
+///////////////////////////////////////////////////////////////////////////////
+
+extern void il2p_scramble_block (unsigned char *in, unsigned char *out, int len);
+
+extern void il2p_descramble_block (unsigned char *in, unsigned char *out, int len);
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// il2p_payload.c
+//
+///////////////////////////////////////////////////////////////////////////////
+
+
+typedef struct {
+ int payload_byte_count; // Total size, 0 thru 1023
+ int payload_block_count;
+ int small_block_size;
+ int large_block_size;
+ int large_block_count;
+ int small_block_count;
+ int parity_symbols_per_block; // 2, 4, 6, 8, 16
+} il2p_payload_properties_t;
+
+extern int il2p_payload_compute (il2p_payload_properties_t *p, int payload_size, int max_fec);
+
+extern int il2p_encode_payload (unsigned char *payload, int payload_size, int max_fec, unsigned char *enc);
+
+extern int il2p_decode_payload (unsigned char *received, int payload_size, int max_fec, unsigned char *payload_out, int *symbols_corrected);
+
+extern int il2p_get_header_attributes (unsigned char *hdr, int *hdr_type, int *max_fec);
+
+#endif
diff --git a/src/il2p_codec.c b/src/il2p_codec.c
new file mode 100644
index 00000000..a20aed65
--- /dev/null
+++ b/src/il2p_codec.c
@@ -0,0 +1,263 @@
+//
+// This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+// Copyright (C) 2021 John Langner, WB2OSZ
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+
+#include "direwolf.h"
+
+#include
+#include
+#include
+#include
+
+#include "il2p.h"
+#include "textcolor.h"
+#include "demod.h"
+
+
+/*-------------------------------------------------------------
+ *
+ * File: il2p_codec.c
+ *
+ * Purpose: Convert IL2P encoded format from and to direwolf internal packet format.
+ *
+ *--------------------------------------------------------------*/
+
+
+/*-------------------------------------------------------------
+ *
+ * Name: il2p_encode_frame
+ *
+ * Purpose: Convert AX.25 frame to IL2P encoding.
+ *
+ * Inputs: chan - Audio channel number, 0 = first.
+ *
+ * pp - Packet object pointer.
+ *
+ * max_fec - 1 to send maximum FEC size rather than automatic.
+ *
+ * Outputs: iout - Encoded result, excluding the 3 byte sync word.
+ * Caller should provide IL2P_MAX_PACKET_SIZE bytes.
+ *
+ * Returns: Number of bytes for transmission.
+ * -1 is returned for failure.
+ *
+ * Description: Encode into IL2P format.
+ *
+ * Errors: If something goes wrong, return -1.
+ *
+ * Most likely reason is that the frame is too large.
+ * IL2P has a max payload size of 1023 bytes.
+ * For a type 1 header, this is the maximum AX.25 Information part size.
+ * For a type 0 header, this is the entire AX.25 frame.
+ *
+ *--------------------------------------------------------------*/
+
+int il2p_encode_frame (packet_t pp, int max_fec, unsigned char *iout)
+{
+
+// Can a type 1 header be used?
+
+ unsigned char hdr[IL2P_HEADER_SIZE + IL2P_HEADER_PARITY];
+ int e;
+ int out_len = 0;
+
+ e = il2p_type_1_header (pp, max_fec, hdr);
+ if (e >= 0) {
+ il2p_scramble_block (hdr, iout, IL2P_HEADER_SIZE);
+ il2p_encode_rs (iout, IL2P_HEADER_SIZE, IL2P_HEADER_PARITY, iout+IL2P_HEADER_SIZE);
+ out_len = IL2P_HEADER_SIZE + IL2P_HEADER_PARITY;
+
+ if (e == 0) {
+ // Success. No info part.
+ return (out_len);
+ }
+
+ // Payload is AX.25 info part.
+ unsigned char *pinfo;
+ int info_len;
+ info_len = ax25_get_info (pp, &pinfo);
+
+ int k = il2p_encode_payload (pinfo, info_len, max_fec, iout+out_len);
+ if (k > 0) {
+ out_len += k;
+ // Success. Info part was <= 1023 bytes.
+ return (out_len);
+ }
+
+ // Something went wrong with the payload encoding.
+ return (-1);
+ }
+ else if (e == -1) {
+
+// Could not use type 1 header for some reason.
+// e.g. More than 2 addresses, extended (mod 128) sequence numbers, etc.
+
+ e = il2p_type_0_header (pp, max_fec, hdr);
+ if (e > 0) {
+
+ il2p_scramble_block (hdr, iout, IL2P_HEADER_SIZE);
+ il2p_encode_rs (iout, IL2P_HEADER_SIZE, IL2P_HEADER_PARITY, iout+IL2P_HEADER_SIZE);
+ out_len = IL2P_HEADER_SIZE + IL2P_HEADER_PARITY;
+
+ // Payload is entire AX.25 frame.
+
+ unsigned char *frame_data_ptr = ax25_get_frame_data_ptr (pp);
+ int frame_len = ax25_get_frame_len (pp);
+ int k = il2p_encode_payload (frame_data_ptr, frame_len, max_fec, iout+out_len);
+ if (k > 0) {
+ out_len += k;
+ // Success. Entire AX.25 frame <= 1023 bytes.
+ return (out_len);
+ }
+ // Something went wrong with the payload encoding.
+ return (-1);
+ }
+ else if (e == 0) {
+ // Impossible condition. Type 0 header must have payload.
+ return (-1);
+ }
+ else {
+ // AX.25 frame is too large.
+ return (-1);
+ }
+ }
+
+ // AX.25 Information part is too large.
+ return (-1);
+}
+
+
+
+/*-------------------------------------------------------------
+ *
+ * Name: il2p_decode_frame
+ *
+ * Purpose: Convert IL2P encoding to AX.25 frame.
+ * This is only used during testing, with a whole encoded frame.
+ * During reception, the header would have FEC and descrambling
+ * applied first so we would know how much to collect for the payload.
+ *
+ * Inputs: irec - Received IL2P frame excluding the 3 byte sync word.
+ *
+ * Future Out: Number of symbols corrected.
+ *
+ * Returns: Packet pointer or NULL for error.
+ *
+ *--------------------------------------------------------------*/
+
+packet_t il2p_decode_frame (unsigned char *irec)
+{
+ unsigned char uhdr[IL2P_HEADER_SIZE]; // After FEC and descrambling.
+ int e = il2p_clarify_header (irec, uhdr);
+
+ // TODO?: for symmetry we might want to clarify the payload before combining.
+
+ return (il2p_decode_header_payload(uhdr, irec + IL2P_HEADER_SIZE + IL2P_HEADER_PARITY, &e));
+}
+
+
+/*-------------------------------------------------------------
+ *
+ * Name: il2p_decode_header_payload
+ *
+ * Purpose: Convert IL2P encoding to AX.25 frame
+ *
+ * Inputs: uhdr - Received header after FEC and descrambling.
+ * epayload - Encoded payload.
+ *
+ * In/Out: symbols_corrected - Symbols (bytes) corrected in the header.
+ * Should be 0 or 1 because it has 2 parity symbols.
+ * Here we add number of corrections for the payload.
+ *
+ * Returns: Packet pointer or NULL for error.
+ *
+ *--------------------------------------------------------------*/
+
+packet_t il2p_decode_header_payload (unsigned char* uhdr, unsigned char *epayload, int *symbols_corrected)
+{
+ int hdr_type;
+ int max_fec;
+ int payload_len = il2p_get_header_attributes (uhdr, &hdr_type, &max_fec);
+
+ packet_t pp = NULL;
+
+ if (hdr_type == 1) {
+
+// Header type 1. Any payload is the AX.25 Information part.
+
+ pp = il2p_decode_header_type_1 (uhdr, *symbols_corrected);
+ if (pp == NULL) {
+ // Failed for some reason.
+ return (NULL);
+ }
+
+ if (payload_len > 0) {
+ // This is the AX.25 Information part.
+
+ unsigned char extracted[IL2P_MAX_PAYLOAD_SIZE];
+ int e = il2p_decode_payload (epayload, payload_len, max_fec, extracted, symbols_corrected);
+
+ // It would be possible to have a good header but too many errors in the payload.
+
+ if (e <= 0) {
+ ax25_delete (pp);
+ pp = NULL;
+ return (pp);
+ }
+
+ if (e != payload_len) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("IL2P Internal Error: %s(): hdr_type=%d, max_fec=%d, payload_len=%d, e=%d.\n", __func__, hdr_type, max_fec, payload_len, e);
+ }
+
+ ax25_set_info (pp, extracted, payload_len);
+ }
+ return (pp);
+ }
+ else {
+
+// Header type 0. The payload is the entire AX.25 frame.
+
+ unsigned char extracted[IL2P_MAX_PAYLOAD_SIZE];
+ int e = il2p_decode_payload (epayload, payload_len, max_fec, extracted, symbols_corrected);
+
+ if (e <= 0) { // Payload was not received correctly.
+ return (NULL);
+ }
+
+ if (e != payload_len) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("IL2P Internal Error: %s(): hdr_type=%d, e=%d, payload_len=%d\n", __func__, hdr_type, e, payload_len);
+ return (NULL);
+ }
+
+ alevel_t alevel;
+ memset (&alevel, 0, sizeof(alevel));
+ //alevel = demod_get_audio_level (chan, subchan); // What TODO? We don't know channel here.
+ // I think alevel gets filled in somewhere later making
+ // this redundant.
+
+ pp = ax25_from_frame (extracted, payload_len, alevel);
+ return (pp);
+ }
+
+} // end il2p_decode_header_payload
+
+// end il2p_codec.c
+
+
diff --git a/src/il2p_header.c b/src/il2p_header.c
new file mode 100644
index 00000000..9a1e9ea4
--- /dev/null
+++ b/src/il2p_header.c
@@ -0,0 +1,673 @@
+//
+// This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+// Copyright (C) 2021 John Langner, WB2OSZ
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+
+#include "direwolf.h"
+
+#include
+#include
+#include
+#include
+
+#include "textcolor.h"
+#include "ax25_pad.h"
+#include "ax25_pad2.h"
+#include "il2p.h"
+
+
+
+/*--------------------------------------------------------------------------------
+ *
+ * File: il2p_header.c
+ *
+ * Purpose: Functions to deal with the IL2P header.
+ *
+ * Reference: http://tarpn.net/t/il2p/il2p-specification0-4.pdf
+ *
+ *--------------------------------------------------------------------------------*/
+
+
+
+// Convert ASCII to/from DEC SIXBIT as defined here:
+// https://en.wikipedia.org/wiki/Six-bit_character_code#DEC_six-bit_code
+
+static inline int ascii_to_sixbit (int a)
+{
+ if (a >= ' ' && a <= '_') return (a - ' ');
+ return (31); // '?' for any invalid.
+}
+
+static inline int sixbit_to_ascii (int s)
+{
+ return (s + ' ');
+}
+
+// Functions for setting the various header fields.
+// It is assumed that it was zeroed first so only the '1' bits are set.
+
+static void set_field (unsigned char *hdr, int bit_num, int lsb_index, int width, int value)
+{
+ while (width > 0 && value != 0) {
+ assert (lsb_index >= 0 && lsb_index <= 11);
+ if (value & 1) {
+ hdr[lsb_index] |= 1 << bit_num;
+ }
+ value >>= 1;
+ lsb_index--;
+ width--;
+ }
+ assert (value == 0);
+}
+
+#define SET_UI(hdr,val) set_field(hdr, 6, 0, 1, val)
+
+#define SET_PID(hdr,val) set_field(hdr, 6, 4, 4, val)
+
+#define SET_CONTROL(hdr,val) set_field(hdr, 6, 11, 7, val)
+
+
+#define SET_FEC_LEVEL(hdr,val) set_field(hdr, 7, 0, 1, val)
+
+#define SET_HDR_TYPE(hdr,val) set_field(hdr, 7, 1, 1, val)
+
+#define SET_PAYLOAD_BYTE_COUNT(hdr,val) set_field(hdr, 7, 11, 10, val)
+
+
+// Extracting the fields.
+
+static int get_field (unsigned char *hdr, int bit_num, int lsb_index, int width)
+{
+ int result = 0;
+ lsb_index -= width - 1;
+ while (width > 0) {
+ result <<= 1;
+ assert (lsb_index >= 0 && lsb_index <= 11);
+ if (hdr[lsb_index] & (1 << bit_num)) {
+ result |= 1;
+ }
+ lsb_index++;
+ width--;
+ }
+ return (result);
+}
+
+#define GET_UI(hdr) get_field(hdr, 6, 0, 1)
+
+#define GET_PID(hdr) get_field(hdr, 6, 4, 4)
+
+#define GET_CONTROL(hdr) get_field(hdr, 6, 11, 7)
+
+
+#define GET_FEC_LEVEL(hdr) get_field(hdr, 7, 0, 1)
+
+#define GET_HDR_TYPE(hdr) get_field(hdr, 7, 1, 1)
+
+#define GET_PAYLOAD_BYTE_COUNT(hdr) get_field(hdr, 7, 11, 10)
+
+
+
+// AX.25 'I' and 'UI' frames have a protocol ID which determines how the
+// information part should be interpreted.
+// Here we squeeze the most common cases down to 4 bits.
+// Return -1 if translation is not possible. Fall back to type 0 header in this case.
+
+static int encode_pid (packet_t pp)
+{
+ int pid = ax25_get_pid(pp);
+
+ if ((pid & 0x30) == 0x20) return (0x2); // AX.25 Layer 3
+ if ((pid & 0x30) == 0x10) return (0x2); // AX.25 Layer 3
+ if (pid == 0x01) return (0x3); // ISO 8208 / CCIT X.25 PLP
+ if (pid == 0x06) return (0x4); // Compressed TCP/IP
+ if (pid == 0x07) return (0x5); // Uncompressed TCP/IP
+ if (pid == 0x08) return (0x6); // Segmentation fragmen
+ if (pid == 0xcc) return (0xb); // ARPA Internet Protocol
+ if (pid == 0xcd) return (0xc); // ARPA Address Resolution
+ if (pid == 0xce) return (0xd); // FlexNet
+ if (pid == 0xcf) return (0xe); // TheNET
+ if (pid == 0xf0) return (0xf); // No L3
+ return (-1);
+}
+
+// Convert IL2P 4 bit PID to AX.25 8 bit PID.
+
+
+static int decode_pid (int pid)
+{
+ static const unsigned char axpid[16] = {
+ 0xf0, // Should not happen. 0 is for 'S' frames.
+ 0xf0, // Should not happen. 1 is for 'U' frames (but not UI).
+ 0x20, // AX.25 Layer 3
+ 0x01, // ISO 8208 / CCIT X.25 PLP
+ 0x06, // Compressed TCP/IP
+ 0x07, // Uncompressed TCP/IP
+ 0x08, // Segmentation fragment
+ 0xf0, // Future
+ 0xf0, // Future
+ 0xf0, // Future
+ 0xf0, // Future
+ 0xcc, // ARPA Internet Protocol
+ 0xcd, // ARPA Address Resolution
+ 0xce, // FlexNet
+ 0xcf, // TheNET
+ 0xf0 }; // No L3
+
+ assert (pid >= 0 && pid <= 15);
+ return (axpid[pid]);
+}
+
+
+
+/*--------------------------------------------------------------------------------
+ *
+ * Function: il2p_type_1_header
+ *
+ * Purpose: Attempt to create type 1 header from packet object.
+ *
+ * Inputs: pp - Packet object.
+ *
+ * max_fec - 1 to use maximum FEC symbols , 0 for automatic.
+ *
+ * Outputs: hdr - IL2P header with no scrambling or parity symbols.
+ * Must be large enough to hold IL2P_HEADER_SIZE unsigned bytes.
+ *
+ * Returns: Number of bytes for information part or -1 for failure.
+ * In case of failure, fall back to type 0 transparent encapsulation.
+ *
+ * Description: Type 1 Headers do not support AX.25 repeater callsign addressing,
+ * Modulo-128 extended mode window sequence numbers, nor any callsign
+ * characters that cannot translate to DEC SIXBIT.
+ * If these cases are encountered during IL2P packet encoding,
+ * the encoder switches to Type 0 Transparent Encapsulation.
+ * SABME can't be handled by type 1.
+ *
+ *--------------------------------------------------------------------------------*/
+
+int il2p_type_1_header (packet_t pp, int max_fec, unsigned char *hdr)
+{
+ memset (hdr, 0, IL2P_HEADER_SIZE);
+
+ if (ax25_get_num_addr(pp) != 2) {
+ // Only two addresses are allowed for type 1 header.
+ return (-1);
+ }
+
+ // Check does not apply for 'U' frames but put in one place rather than two.
+
+ if (ax25_get_modulo(pp) == 128) return(-1);
+
+// Destination and source addresses go into low bits 0-5 for bytes 0-11.
+
+ char dst_addr[AX25_MAX_ADDR_LEN];
+ char src_addr[AX25_MAX_ADDR_LEN];
+
+ ax25_get_addr_no_ssid (pp, AX25_DESTINATION, dst_addr);
+ int dst_ssid = ax25_get_ssid (pp, AX25_DESTINATION);
+
+ ax25_get_addr_no_ssid (pp, AX25_SOURCE, src_addr);
+ int src_ssid = ax25_get_ssid (pp, AX25_SOURCE);
+
+ unsigned char *a = (unsigned char *)dst_addr;
+ for (int i = 0; *a != '\0'; i++, a++) {
+ if (*a < ' ' || *a > '_') {
+ // Shouldn't happen but follow the rule.
+ return (-1);
+ }
+ hdr[i] = ascii_to_sixbit(*a);
+ }
+
+ a = (unsigned char *)src_addr;
+ for (int i = 6; *a != '\0'; i++, a++) {
+ if (*a < ' ' || *a > '_') {
+ // Shouldn't happen but follow the rule.
+ return (-1);
+ }
+ hdr[i] = ascii_to_sixbit(*a);
+ }
+
+ // Byte 12 has DEST SSID in upper nybble and SRC SSID in lower nybble and
+ hdr[12] = (dst_ssid << 4) | src_ssid;
+
+ ax25_frame_type_t frame_type;
+ cmdres_t cr; // command or response.
+ char description[64];
+ int pf; // Poll/Final.
+ int nr, ns; // Sequence numbers.
+
+ frame_type = ax25_frame_type (pp, &cr, description, &pf, &nr, &ns);
+
+ //dw_printf ("%s(): %s-%d>%s-%d: %s\n", __func__, src_addr, src_ssid, dst_addr, dst_ssid, description);
+
+ switch (frame_type) {
+
+ case frame_type_S_RR: // Receive Ready - System Ready To Receive
+ case frame_type_S_RNR: // Receive Not Ready - TNC Buffer Full
+ case frame_type_S_REJ: // Reject Frame - Out of Sequence or Duplicate
+ case frame_type_S_SREJ: // Selective Reject - Request single frame repeat
+
+ // S frames (RR, RNR, REJ, SREJ), mod 8, have control N(R) P/F S S 0 1
+ // These are mapped into P/F N(R) C S S
+ // Bit 6 is not mentioned in documentation but it is used for P/F for the other frame types.
+ // C is copied from the C bit in the destination addr.
+ // C from source is not used here. Reception assumes it is the opposite.
+ // PID is set to 0, meaning none, for S frames.
+
+ SET_UI(hdr, 0);
+ SET_PID(hdr, 0);
+ SET_CONTROL(hdr, (pf<<6) | (nr<<3) | (((cr == cr_cmd) | (cr == cr_11))<<2));
+
+ // This gets OR'ed into the above.
+ switch (frame_type) {
+ case frame_type_S_RR: SET_CONTROL(hdr, 0); break;
+ case frame_type_S_RNR: SET_CONTROL(hdr, 1); break;
+ case frame_type_S_REJ: SET_CONTROL(hdr, 2); break;
+ case frame_type_S_SREJ: SET_CONTROL(hdr, 3); break;
+ default: break;
+ }
+
+ break;
+
+ case frame_type_U_SABM: // Set Async Balanced Mode
+ case frame_type_U_DISC: // Disconnect
+ case frame_type_U_DM: // Disconnect Mode
+ case frame_type_U_UA: // Unnumbered Acknowledge
+ case frame_type_U_FRMR: // Frame Reject
+ case frame_type_U_UI: // Unnumbered Information
+ case frame_type_U_XID: // Exchange Identification
+ case frame_type_U_TEST: // Test
+
+ // The encoding allows only 3 bits for frame type and SABME got left out.
+ // Control format: P/F opcode[3] C n/a n/a
+ // The grayed out n/a bits are observed as 00 in the example.
+ // The header UI field must also be set for UI frames.
+ // PID is set to 1 for all U frames other than UI.
+
+ if (frame_type == frame_type_U_UI) {
+ SET_UI(hdr, 1); // I guess this is how we distinguish 'I' and 'UI'
+ // on the receiving end.
+ int pid = encode_pid(pp);
+ if (pid < 0) return (-1);
+ SET_PID(hdr, pid);
+ }
+ else {
+ SET_PID(hdr, 1); // 1 for 'U' other than 'UI'.
+ }
+
+ // Each of the destination and source addresses has a "C" bit.
+ // They should normally have the opposite setting.
+ // IL2P has only a single bit to represent 4 possbilities.
+ //
+ // dst src il2p meaning
+ // --- --- ---- -------
+ // 0 0 0 Not valid (earlier protocol version)
+ // 1 0 1 Command (v2)
+ // 0 1 0 Response (v2)
+ // 1 1 1 Not valid (earlier protocol version)
+ //
+ // APRS does not mention how to set these bits and all 4 combinations
+ // are seen in the wild. Apparently these are ignored on receive and no
+ // one cares. Here we copy from the C bit in the destination address.
+ // It should be noted that the case of both C bits being the same can't
+ // be represented so the il2p encode/decode bit not produce exactly the
+ // same bits. We see this in the second example in the protocol spec.
+ // The original UI frame has both C bits of 0 so it is received as a response.
+
+ SET_CONTROL(hdr, (pf<<6) | (((cr == cr_cmd) | (cr == cr_11))<<2));
+
+ // This gets OR'ed into the above.
+ switch (frame_type) {
+ case frame_type_U_SABM: SET_CONTROL(hdr, 0<<3); break;
+ case frame_type_U_DISC: SET_CONTROL(hdr, 1<<3); break;
+ case frame_type_U_DM: SET_CONTROL(hdr, 2<<3); break;
+ case frame_type_U_UA: SET_CONTROL(hdr, 3<<3); break;
+ case frame_type_U_FRMR: SET_CONTROL(hdr, 4<<3); break;
+ case frame_type_U_UI: SET_CONTROL(hdr, 5<<3); break;
+ case frame_type_U_XID: SET_CONTROL(hdr, 6<<3); break;
+ case frame_type_U_TEST: SET_CONTROL(hdr, 7<<3); break;
+ default: break;
+ }
+ break;
+
+ case frame_type_I: // Information
+
+ // I frames (mod 8 only)
+ // encoded control: P/F N(R) N(S)
+
+ SET_UI(hdr, 0);
+
+ int pid2 = encode_pid(pp);
+ if (pid2 < 0) return (-1);
+ SET_PID(hdr, pid2);
+
+ SET_CONTROL(hdr, (pf<<6) | (nr<<3) | ns);
+ break;
+
+ case frame_type_U_SABME: // Set Async Balanced Mode, Extended
+ case frame_type_U: // other Unnumbered, not used by AX.25.
+ case frame_not_AX25: // Could not get control byte from frame.
+ default:
+
+ // Fall back to the header type 0 for these.
+ return (-1);
+ }
+
+// Common for all header type 1.
+
+ // Bit 7 has [FEC Level:1], [HDR Type:1], [Payload byte Count:10]
+
+ SET_FEC_LEVEL(hdr, max_fec);
+ SET_HDR_TYPE(hdr, 1);
+
+ unsigned char *pinfo;
+ int info_len;
+
+ info_len = ax25_get_info (pp, &pinfo);
+ if (info_len < 0 || info_len > IL2P_MAX_PAYLOAD_SIZE) {
+ return (-2);
+ }
+
+ SET_PAYLOAD_BYTE_COUNT(hdr, info_len);
+ return (info_len);
+}
+
+
+// This should create a packet from the IL2P header.
+// The information part will not be filled in.
+
+static void trim (char *stuff)
+{
+ char *p = stuff + strlen(stuff) - 1;
+ while (strlen(stuff) > 0 && (*p == ' ')) {
+ *p = '\0';
+ p--;
+ }
+}
+
+
+
+/*--------------------------------------------------------------------------------
+ *
+ * Function: il2p_decode_header_type_1
+ *
+ * Purpose: Attempt to convert type 1 header to a packet object.
+ *
+ * Inputs: hdr - IL2P header with no scrambling or parity symbols.
+ *
+ * num_sym_changed - Number of symbols changed by FEC in the header.
+ * Should be 0 or 1.
+ *
+ * Returns: Packet Object or NULL for failure.
+ *
+ * Description: A later step will process the payload for the information part.
+ *
+ *--------------------------------------------------------------------------------*/
+
+packet_t il2p_decode_header_type_1 (unsigned char *hdr, int num_sym_changed)
+{
+
+ if (GET_HDR_TYPE(hdr) != 1 ) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("IL2P Internal error. Should not be here: %s, when header type is 0.\n", __func__);
+ return (NULL);
+ }
+
+// First get the addresses including SSID.
+
+ char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
+ int num_addr = 2;
+ memset (addrs, 0, 2*AX25_MAX_ADDR_LEN);
+
+// The IL2P header uses 2 parity symbols which means a single corrupted symbol (byte)
+// can always be corrected.
+// 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.
+// 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.
+// 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?
+// My current thinking is that it should be silently ignored if the header has been
+// modified (correctee or more likely, made worse in this cases).
+// If no changes were made, something weird is happening. We should mention it for
+// troubleshooting rather than sweeping it under the rug.
+
+// The same thing has been observed with the payload, under very high error conditions,
+// and max_fec==0. Here I don't see a good solution. AX.25 information can contain
+// "binary" data so I'm not sure what sort of sanity check could be added.
+// This was not observed with max_fec==1. If we make that the default, same as Nino TNC,
+// it would be extremely extremely unlikely unless someone explicitly selects weaker FEC.
+
+// TODO: We could do something similar for header type 0.
+// The address fields should be all binary zero values.
+// Someone overly ambitious might check the addresses found in the first payload block.
+
+ for (int i = 0; i <= 5; i++) {
+ addrs[AX25_DESTINATION][i] = sixbit_to_ascii(hdr[i] & 0x3f);
+ }
+ trim (addrs[AX25_DESTINATION]);
+ for (int i = 0; i < strlen(addrs[AX25_DESTINATION]); i++) {
+ if (! isupper(addrs[AX25_DESTINATION][i]) && ! isdigit(addrs[AX25_DESTINATION][i])) {
+ if (num_sym_changed == 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("IL2P: Invalid character '%c' in destination address '%s'\n", addrs[AX25_DESTINATION][i], addrs[AX25_DESTINATION]);
+ }
+ return (NULL);
+ }
+ }
+ snprintf (addrs[AX25_DESTINATION]+strlen(addrs[AX25_DESTINATION]), 4, "-%d", (hdr[12] >> 4) &0xf);
+
+ for (int i = 0; i <= 5; i++) {
+ addrs[AX25_SOURCE][i] = sixbit_to_ascii(hdr[i+6] & 0x3f);
+ }
+ trim (addrs[AX25_SOURCE]);
+ for (int i = 0; i < strlen(addrs[AX25_SOURCE]); i++) {
+ if (! isupper(addrs[AX25_SOURCE][i]) && ! isdigit(addrs[AX25_SOURCE][i])) {
+ if (num_sym_changed == 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("IL2P: Invalid character '%c' in source address '%s'\n", addrs[AX25_SOURCE][i], addrs[AX25_SOURCE]);
+ }
+ return (NULL);
+ }
+ }
+ snprintf (addrs[AX25_SOURCE]+strlen(addrs[AX25_SOURCE]), 4, "-%d", hdr[12] &0xf);
+
+// The PID field gives us the general type.
+// 0 = 'S' frame.
+// 1 = 'U' frame other than UI.
+// others are either 'UI' or 'I' depending on the UI field.
+
+ int pid = GET_PID(hdr);
+ int ui = GET_UI(hdr);
+
+ if (pid == 0) {
+
+// 'S' frame.
+// The control field contains: P/F N(R) C S S
+
+ int control = GET_CONTROL(hdr);
+ cmdres_t cr = (control & 0x04) ? cr_cmd : cr_res;
+ ax25_frame_type_t ftype;
+ switch (control & 0x03) {
+ case 0: ftype = frame_type_S_RR; break;
+ case 1: ftype = frame_type_S_RNR; break;
+ case 2: ftype = frame_type_S_REJ; break;
+ default: ftype = frame_type_S_SREJ; break;
+ }
+ int modulo = 8;
+ int nr = (control >> 3) & 0x07;
+ int pf = (control >> 6) & 0x01;
+ unsigned char *pinfo = NULL; // Any info for SREJ will be added later.
+ int info_len = 0;
+ return (ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf, pinfo, info_len));
+ }
+ else if (pid == 1) {
+
+// 'U' frame other than 'UI'.
+// The control field contains: P/F OPCODE{3) C x x
+
+ int control = GET_CONTROL(hdr);
+ cmdres_t cr = (control & 0x04) ? cr_cmd : cr_res;
+ int axpid = 0; // unused for U other than UI.
+ ax25_frame_type_t ftype;
+ switch ((control >> 3) & 0x7) {
+ case 0: ftype = frame_type_U_SABM; break;
+ case 1: ftype = frame_type_U_DISC; break;
+ case 2: ftype = frame_type_U_DM; break;
+ case 3: ftype = frame_type_U_UA; break;
+ case 4: ftype = frame_type_U_FRMR; break;
+ case 5: ftype = frame_type_U_UI; axpid = 0xf0; break; // Should not happen with IL2P pid == 1.
+ case 6: ftype = frame_type_U_XID; break;
+ default: ftype = frame_type_U_TEST; break;
+ }
+ int pf = (control >> 6) & 0x01;
+ unsigned char *pinfo = NULL; // Any info for UI, XID, TEST will be added later.
+ int info_len = 0;
+ return (ax25_u_frame (addrs, num_addr, cr, ftype, pf, axpid, pinfo, info_len));
+ }
+ else if (ui) {
+
+// 'UI' frame.
+// The control field contains: P/F OPCODE{3) C x x
+
+ int control = GET_CONTROL(hdr);
+ cmdres_t cr = (control & 0x04) ? cr_cmd : cr_res;
+ ax25_frame_type_t ftype = frame_type_U_UI;
+ int pf = (control >> 6) & 0x01;
+ int axpid = decode_pid(GET_PID(hdr));
+ unsigned char *pinfo = NULL; // Any info for UI, XID, TEST will be added later.
+ int info_len = 0;
+ return (ax25_u_frame (addrs, num_addr, cr, ftype, pf, axpid, pinfo, info_len));
+ }
+ else {
+
+// 'I' frame.
+// The control field contains: P/F N(R) N(S)
+
+ int control = GET_CONTROL(hdr);
+ cmdres_t cr = cr_cmd; // Always command.
+ int pf = (control >> 6) & 0x01;
+ int nr = (control >> 3) & 0x7;
+ int ns = control & 0x7;
+ int modulo = 8;
+ int axpid = decode_pid(GET_PID(hdr));
+ unsigned char *pinfo = NULL; // Any info for UI, XID, TEST will be added later.
+ int info_len = 0;
+ return (ax25_i_frame (addrs, num_addr, cr, modulo, nr, ns, pf, axpid, pinfo, info_len));
+ }
+ return (NULL); // unreachable but avoid warning.
+
+} // end
+
+
+/*--------------------------------------------------------------------------------
+ *
+ * Function: il2p_type_0_header
+ *
+ * Purpose: Attempt to create type 0 header from packet object.
+ *
+ * Inputs: pp - Packet object.
+ *
+ * max_fec - 1 to use maximum FEC symbols, 0 for automatic.
+ *
+ * Outputs: hdr - IL2P header with no scrambling or parity symbols.
+ * Must be large enough to hold IL2P_HEADER_SIZE unsigned bytes.
+ *
+ * Returns: Number of bytes for information part or -1 for failure.
+ * In case of failure, fall back to type 0 transparent encapsulation.
+ *
+ * Description: The type 0 header is used when it is not one of the restricted cases
+ * covered by the type 1 header.
+ * The AX.25 frame is put in the payload.
+ * This will cover: more than one address, mod 128 sequences, etc.
+ *
+ *--------------------------------------------------------------------------------*/
+
+int il2p_type_0_header (packet_t pp, int max_fec, unsigned char *hdr)
+{
+ memset (hdr, 0, IL2P_HEADER_SIZE);
+
+ // Bit 7 has [FEC Level:1], [HDR Type:1], [Payload byte Count:10]
+
+ SET_FEC_LEVEL(hdr, max_fec);
+ SET_HDR_TYPE(hdr, 0);
+
+ int frame_len = ax25_get_frame_len (pp);
+
+ if (frame_len < 14 || frame_len > IL2P_MAX_PAYLOAD_SIZE) {
+ return (-2);
+ }
+
+ SET_PAYLOAD_BYTE_COUNT(hdr, frame_len);
+ return (frame_len);
+}
+
+
+/***********************************************************************************
+ *
+ * Name: il2p_get_header_attributes
+ *
+ * Purpose: Extract a few attributes from an IL2p header.
+ *
+ * Inputs: hdr - IL2P header structure.
+ *
+ * Outputs: hdr_type - 0 or 1.
+ *
+ * max_fec - 0 for automatic or 1 for fixed maximum size.
+ *
+ * Returns: Payload byte count. (actual payload size, not the larger encoded format)
+ *
+ ***********************************************************************************/
+
+
+int il2p_get_header_attributes (unsigned char *hdr, int *hdr_type, int *max_fec)
+{
+ *hdr_type = GET_HDR_TYPE(hdr);
+ *max_fec = GET_FEC_LEVEL(hdr);
+ return(GET_PAYLOAD_BYTE_COUNT(hdr));
+}
+
+
+/***********************************************************************************
+ *
+ * Name: il2p_clarify_header
+ *
+ * Purpose: Convert received header to usable form.
+ * This involves RS FEC then descrambling.
+ *
+ * Inputs: rec_hdr - Header as received over the radio.
+ *
+ * Outputs: corrected_descrambled_hdr - After RS FEC and unscrambling.
+ *
+ * Returns: Number of symbols that were corrected:
+ * 0 = No errors
+ * 1 = Single symbol corrected.
+ * <0 = Unable to obtain good header.
+ *
+ ***********************************************************************************/
+
+int il2p_clarify_header(unsigned char *rec_hdr, unsigned char *corrected_descrambled_hdr)
+{
+ unsigned char corrected[IL2P_HEADER_SIZE+IL2P_HEADER_PARITY];
+
+ int e = il2p_decode_rs (rec_hdr, IL2P_HEADER_SIZE, IL2P_HEADER_PARITY, corrected);
+
+ il2p_descramble_block (corrected, corrected_descrambled_hdr, IL2P_HEADER_SIZE);
+
+ return (e);
+}
+
+// end il2p_header.c
\ No newline at end of file
diff --git a/src/il2p_init.c b/src/il2p_init.c
new file mode 100644
index 00000000..533a2213
--- /dev/null
+++ b/src/il2p_init.c
@@ -0,0 +1,226 @@
+//
+// This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+// Copyright (C) 2021 John Langner, WB2OSZ
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+
+#include "direwolf.h"
+
+#include
+#include
+#include
+#include
+
+#include "textcolor.h"
+#include "fx25.h" // For Reed Solomon stuff.
+#include "il2p.h"
+
+// Interesting related stuff:
+// https://www.kernel.org/doc/html/v4.15/core-api/librs.html
+// https://berthub.eu/articles/posts/reed-solomon-for-programmers/
+
+
+#define MAX_NROOTS 16
+
+#define NTAB 5
+
+static struct {
+ int symsize; // Symbol size, bits (1-8). Always 8 for this application.
+ int genpoly; // Field generator polynomial coefficients.
+ int fcs; // First root of RS code generator polynomial, index form.
+ // FX.25 uses 1 but IL2P uses 0.
+ int prim; // Primitive element to generate polynomial roots.
+ int nroots; // RS code generator polynomial degree (number of roots).
+ // Same as number of check bytes added.
+ struct rs *rs; // Pointer to RS codec control block. Filled in at init time.
+} Tab[NTAB] = {
+ {8, 0x11d, 0, 1, 2, NULL }, // 2 parity
+ {8, 0x11d, 0, 1, 4, NULL }, // 4 parity
+ {8, 0x11d, 0, 1, 6, NULL }, // 6 parity
+ {8, 0x11d, 0, 1, 8, NULL }, // 8 parity
+ {8, 0x11d, 0, 1, 16, NULL }, // 16 parity
+};
+
+
+
+static int g_il2p_debug = 0;
+
+
+/*-------------------------------------------------------------
+ *
+ * Name: il2p_init
+ *
+ * Purpose: This must be called at application start up time.
+ * It sets up tables for the Reed-Solomon functions.
+ *
+ * Inputs: debug - Enable debug output.
+ *
+ *--------------------------------------------------------------*/
+
+void il2p_init (int il2p_debug)
+{
+ g_il2p_debug = il2p_debug;
+
+ for (int i = 0 ; i < NTAB ; i++) {
+ assert (Tab[i].nroots <= MAX_NROOTS);
+ Tab[i].rs = INIT_RS(Tab[i].symsize, Tab[i].genpoly, Tab[i].fcs, Tab[i].prim, Tab[i].nroots);
+ if (Tab[i].rs == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf("IL2P internal error: init_rs_char failed!\n");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+} // end il2p_init
+
+
+int il2p_get_debug(void)
+{
+ return (g_il2p_debug);
+}
+void il2p_set_debug(int debug)
+{
+ g_il2p_debug = debug;
+}
+
+
+// Find RS codec control block for specified number of parity symbols.
+
+struct rs *il2p_find_rs(int nparity)
+{
+ for (int n = 0; n < NTAB; n++) {
+ if (Tab[n].nroots == nparity) {
+ return (Tab[n].rs);
+ }
+ }
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("IL2P INTERNAL ERROR: il2p_find_rs: control block not found for nparity = %d.\n", nparity);
+ return (Tab[0].rs);
+}
+
+
+/*-------------------------------------------------------------
+ *
+ * Name: void il2p_encode_rs
+ *
+ * Purpose: Add parity symbols to a block of data.
+ *
+ * Inputs: tx_data Header or other data to transmit.
+ * data_size Number of data bytes in above.
+ * num_parity Number of parity symbols to add.
+ * Maximum of IL2P_MAX_PARITY_SYMBOLS.
+ *
+ * Outputs: parity_out Specified number of parity symbols
+ *
+ * Restriction: data_size + num_parity <= 255 which is the RS block size.
+ * The caller must ensure this.
+ *
+ *--------------------------------------------------------------*/
+
+void il2p_encode_rs (unsigned char *tx_data, int data_size, int num_parity, unsigned char *parity_out)
+{
+ assert (data_size >= 1);
+ assert (num_parity == 2 || num_parity == 4 || num_parity == 6 || num_parity == 8 || num_parity == 16);
+ assert (data_size + num_parity <= 255);
+
+ unsigned char rs_block[FX25_BLOCK_SIZE];
+ memset (rs_block, 0, sizeof(rs_block));
+ memcpy (rs_block + sizeof(rs_block) - data_size - num_parity, tx_data, data_size);
+ ENCODE_RS (il2p_find_rs(num_parity), rs_block, parity_out);
+}
+
+/*-------------------------------------------------------------
+ *
+ * Name: void il2p_decode_rs
+ *
+ * Purpose: Check and attempt to fix block with FEC.
+ *
+ * Inputs: rec_block Received block composed of data and parity.
+ * Total size is sum of following two parameters.
+ * data_size Number of data bytes in above.
+ * num_parity Number of parity symbols (bytes) in above.
+ *
+ * Outputs: out Original with possible corrections applied.
+ * data_size bytes.
+ *
+ * Returns: -1 for unrecoverable.
+ * >= 0 for success. Number of symbols corrected.
+ *
+ *--------------------------------------------------------------*/
+
+int il2p_decode_rs (unsigned char *rec_block, int data_size, int num_parity, unsigned char *out)
+{
+
+ // Use zero padding in front if data size is too small.
+
+ int n = data_size + num_parity; // total size in.
+
+ unsigned char rs_block[FX25_BLOCK_SIZE];
+
+ // We could probably do this more efficiently by skipping the
+ // processing of the bytes known to be zero. Good enough for now.
+
+ memset (rs_block, 0, sizeof(rs_block) - n);
+ memcpy (rs_block + sizeof(rs_block) - n, rec_block, n);
+
+ if (il2p_get_debug() >= 3) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("============================== il2p_decode_rs ==============================\n");
+ dw_printf ("%d filler zeros, %d data, %d parity\n", (int)(sizeof(rs_block) - n), data_size, num_parity);
+ fx_hex_dump (rs_block, sizeof (rs_block));
+ }
+
+ int derrlocs[FX25_MAX_CHECK]; // Half would probably be OK.
+
+ int derrors = DECODE_RS(il2p_find_rs(num_parity), rs_block, derrlocs, 0);
+ memcpy (out, rs_block + sizeof(rs_block) - n, data_size);
+
+ if (il2p_get_debug() >= 3) {
+ if (derrors == 0) {
+ dw_printf ("No errors reported for RS block.\n");
+ }
+ else if (derrors > 0) {
+ dw_printf ("%d errors fixed in positions:\n", derrors);
+ for (int j = 0; j < derrors; j++) {
+ dw_printf (" %3d (0x%02x)\n", derrlocs[j] , derrlocs[j]);
+ }
+ fx_hex_dump (rs_block, sizeof (rs_block));
+ }
+ }
+
+ // It is possible to have a situation where too many errors are
+ // present but the algorithm could get a good code block by "fixing"
+ // one of the padding bytes that should be 0.
+
+ for (int i = 0; i < derrors; i++) {
+ if (derrlocs[i] < sizeof(rs_block) - n) {
+ if (il2p_get_debug() >= 3) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("RS DECODE ERROR! Padding position %d should be 0 but it was set to %02x.\n", derrlocs[i], rs_block[derrlocs[i]]);
+ }
+ derrors = -1;
+ break;
+ }
+ }
+
+ if (il2p_get_debug() >= 3) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("============================== il2p_decode_rs returns %d ==============================\n", derrors);
+ }
+ return (derrors);
+}
+
+// end il2p_init.c
\ No newline at end of file
diff --git a/src/il2p_payload.c b/src/il2p_payload.c
new file mode 100644
index 00000000..67c79a98
--- /dev/null
+++ b/src/il2p_payload.c
@@ -0,0 +1,298 @@
+//
+// This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+// Copyright (C) 2021 John Langner, WB2OSZ
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+
+#include "direwolf.h"
+
+#include
+#include
+#include
+#include
+
+#include "textcolor.h"
+#include "il2p.h"
+
+
+/*--------------------------------------------------------------------------------
+ *
+ * File: il2p_payload.c
+ *
+ * Purpose: Functions dealing with the payload.
+ *
+ *--------------------------------------------------------------------------------*/
+
+
+/*--------------------------------------------------------------------------------
+ *
+ * Function: il2p_payload_compute
+ *
+ * Purpose: Compute number and sizes of data blocks based on total size.
+ *
+ * Inputs: payload_size 0 to 1023. (IL2P_MAX_PAYLOAD_SIZE)
+ * max_fec true for 16 parity symbols, false for automatic.
+ *
+ * Outputs: *p Payload block sizes and counts.
+ * Number of parity symbols per block.
+ *
+ * Returns: Number of bytes in the encoded format.
+ * Could be 0 for no payload blocks.
+ * -1 for error (i.e. invalid unencoded size: <0 or >1023)
+ *
+ *--------------------------------------------------------------------------------*/
+
+int il2p_payload_compute (il2p_payload_properties_t *p, int payload_size, int max_fec)
+{
+ memset (p, 0, sizeof(il2p_payload_properties_t));
+
+ if (payload_size < 0 || payload_size > IL2P_MAX_PAYLOAD_SIZE) {
+ return (-1);
+ }
+ if (payload_size == 0) {
+ return (0);
+ }
+
+ if (max_fec) {
+ p->payload_byte_count = payload_size;
+ p->payload_block_count = (p->payload_byte_count + 238) / 239;
+ p->small_block_size = p->payload_byte_count / p->payload_block_count;
+ p->large_block_size = p->small_block_size + 1;
+ p->large_block_count = p->payload_byte_count - (p->payload_block_count * p->small_block_size);
+ p->small_block_count = p->payload_block_count - p->large_block_count;
+ p->parity_symbols_per_block = 16;
+ }
+ else {
+ p->payload_byte_count = payload_size;
+ p->payload_block_count = (p->payload_byte_count + 246) / 247;
+ p->small_block_size = p->payload_byte_count / p->payload_block_count;
+ p->large_block_size = p->small_block_size + 1;
+ p->large_block_count = p->payload_byte_count - (p->payload_block_count * p->small_block_size);
+ p->small_block_count = p->payload_block_count - p->large_block_count;
+ //p->parity_symbols_per_block = (p->small_block_size / 32) + 2; // Looks like error in documentation
+
+ // It would work if the number of parity symbols was based on large block size.
+
+ if (p->small_block_size <= 61) p->parity_symbols_per_block = 2;
+ else if (p->small_block_size <= 123) p->parity_symbols_per_block = 4;
+ else if (p->small_block_size <= 185) p->parity_symbols_per_block = 6;
+ else if (p->small_block_size <= 247) p->parity_symbols_per_block = 8;
+ else {
+ // Should not happen. But just in case...
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("IL2P parity symbol per payload block error. small_block_size = %d\n", p->small_block_size);
+ return (-1);
+ }
+ }
+
+ // Return the total size for the encoded format.
+
+ return (p->small_block_count * (p->small_block_size + p->parity_symbols_per_block) +
+ p->large_block_count * (p->large_block_size + p->parity_symbols_per_block));
+
+} // end il2p_payload_compute
+
+
+
+/*--------------------------------------------------------------------------------
+ *
+ * Function: il2p_encode_payload
+ *
+ * Purpose: Split payload into multiple blocks such that each set
+ * of data and parity symbols fit into a 255 byte RS block.
+ *
+ * Inputs: *payload Array of bytes.
+ * payload_size 0 to 1023. (IL2P_MAX_PAYLOAD_SIZE)
+ * max_fec true for 16 parity symbols, false for automatic.
+ *
+ * Outputs: *enc Encoded payload for transmission.
+ * Up to IL2P_MAX_ENCODED_SIZE bytes.
+ *
+ * Returns: -1 for error (i.e. invalid size)
+ * 0 for no blocks. (i.e. size zero)
+ * Number of bytes generated. Maximum IL2P_MAX_ENCODED_SIZE.
+ *
+ * Note: I interpreted the protocol spec as saying the LFSR state is retained
+ * between data blocks. During interoperability testing, I found that
+ * was not the case. It is reset for each data block.
+ *
+ *--------------------------------------------------------------------------------*/
+
+
+int il2p_encode_payload (unsigned char *payload, int payload_size, int max_fec, unsigned char *enc)
+{
+ if (payload_size > IL2P_MAX_PAYLOAD_SIZE) return (-1);
+ if (payload_size == 0) return (0);
+
+// Determine number of blocks and sizes.
+
+ il2p_payload_properties_t ipp;
+ int e;
+ e = il2p_payload_compute (&ipp, payload_size, max_fec);
+ if (e <= 0) {
+ return (e);
+ }
+
+ unsigned char *pin = payload;
+ unsigned char *pout = enc;
+ int encoded_length = 0;
+ unsigned char scram[256];
+ unsigned char parity[IL2P_MAX_PARITY_SYMBOLS];
+
+// First the large blocks.
+
+ for (int b = 0; b < ipp.large_block_count; b++) {
+
+ il2p_scramble_block (pin, scram, ipp.large_block_size);
+ memcpy (pout, scram, ipp.large_block_size);
+ pin += ipp.large_block_size;
+ pout += ipp.large_block_size;
+ encoded_length += ipp.large_block_size;
+ il2p_encode_rs (scram, ipp.large_block_size, ipp.parity_symbols_per_block, parity);
+ memcpy (pout, parity, ipp.parity_symbols_per_block);
+ pout += ipp.parity_symbols_per_block;
+ encoded_length += ipp.parity_symbols_per_block;
+ }
+
+// Then the small blocks.
+
+ for (int b = 0; b < ipp.small_block_count; b++) {
+
+ il2p_scramble_block (pin, scram, ipp.small_block_size);
+ memcpy (pout, scram, ipp.small_block_size);
+ pin += ipp.small_block_size;
+ pout += ipp.small_block_size;
+ encoded_length += ipp.small_block_size;
+ il2p_encode_rs (scram, ipp.small_block_size, ipp.parity_symbols_per_block, parity);
+ memcpy (pout, parity, ipp.parity_symbols_per_block);
+ pout += ipp.parity_symbols_per_block;
+ encoded_length += ipp.parity_symbols_per_block;
+ }
+
+ return (encoded_length);
+
+} // end il2p_encode_payload
+
+
+/*--------------------------------------------------------------------------------
+ *
+ * Function: il2p_decode_payload
+ *
+ * Purpose: Extract original data from encoded payload.
+ *
+ * Inputs: received Array of bytes. Size is unknown but in practice it
+ * must not exceeed IL2P_MAX_ENCODED_SIZE.
+ * payload_size 0 to 1023. (IL2P_MAX_PAYLOAD_SIZE)
+ * Expected result size based on header.
+ * max_fec true for 16 parity symbols, false for automatic.
+ *
+ * Outputs: payload_out Recovered payload.
+ *
+ * In/Out: symbols_corrected Number of symbols corrected.
+ *
+ *
+ * Returns: Number of bytes extracted. Should be same as payload_size going in.
+ * -3 for unexpected internal inconsistency.
+ * -2 for unable to recover from signal corruption.
+ * -1 for invalid size.
+ * 0 for no blocks. (i.e. size zero)
+ *
+ * Description: Each block is scrambled separately but the LSFR state is carried
+ * from the first payload block to the next.
+ *
+ *--------------------------------------------------------------------------------*/
+
+int il2p_decode_payload (unsigned char *received, int payload_size, int max_fec, unsigned char *payload_out, int *symbols_corrected)
+{
+// Determine number of blocks and sizes.
+
+ il2p_payload_properties_t ipp;
+ int e;
+ e = il2p_payload_compute (&ipp, payload_size, max_fec);
+ if (e <= 0) {
+ return (e);
+ }
+
+ unsigned char *pin = received;
+ unsigned char *pout = payload_out;
+ int decoded_length = 0;
+ int failed = 0;
+
+// First the large blocks.
+
+ for (int b = 0; b < ipp.large_block_count; b++) {
+ unsigned char corrected_block[255];
+ int e = il2p_decode_rs (pin, ipp.large_block_size, ipp.parity_symbols_per_block, corrected_block);
+
+ // dw_printf ("%s:%d: large block decode_rs returned status = %d\n", __FILE__, __LINE__, e);
+
+ if (e < 0) failed = 1;
+ *symbols_corrected += e;
+
+ il2p_descramble_block (corrected_block, pout, ipp.large_block_size);
+
+ if (il2p_get_debug() >= 2) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("Descrambled large payload block, %d bytes:\n", ipp.large_block_size);
+ fx_hex_dump(pout, ipp.large_block_size);
+ }
+
+ pin += ipp.large_block_size + ipp.parity_symbols_per_block;
+ pout += ipp.large_block_size;
+ decoded_length += ipp.large_block_size;
+ }
+
+// Then the small blocks.
+
+ for (int b = 0; b < ipp.small_block_count; b++) {
+ unsigned char corrected_block[255];
+ int e = il2p_decode_rs (pin, ipp.small_block_size, ipp.parity_symbols_per_block, corrected_block);
+
+ // dw_printf ("%s:%d: small block decode_rs returned status = %d\n", __FILE__, __LINE__, e);
+
+ if (e < 0) failed = 1;
+ *symbols_corrected += e;
+
+ il2p_descramble_block (corrected_block, pout, ipp.small_block_size);
+
+ if (il2p_get_debug() >= 2) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("Descrambled small payload block, %d bytes:\n", ipp.small_block_size);
+ fx_hex_dump(pout, ipp.small_block_size);
+ }
+
+ pin += ipp.small_block_size + ipp.parity_symbols_per_block;
+ pout += ipp.small_block_size;
+ decoded_length += ipp.small_block_size;
+ }
+
+ if (failed) {
+ //dw_printf ("%s:%d: failed = %0x\n", __FILE__, __LINE__, failed);
+ return (-2);
+ }
+
+ if (decoded_length != payload_size) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("IL2P Internal error: decoded_length = %d, payload_size = %d\n", decoded_length, payload_size);
+ return (-3);
+ }
+
+ return (decoded_length);
+
+} // end il2p_decode_payload
+
+// end il2p_payload.c
+
diff --git a/src/il2p_rec.c b/src/il2p_rec.c
new file mode 100644
index 00000000..5ad457a0
--- /dev/null
+++ b/src/il2p_rec.c
@@ -0,0 +1,276 @@
+//
+// This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+// Copyright (C) 2021 John Langner, WB2OSZ
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+
+
+/********************************************************************************
+ *
+ * File: il2p_rec.c
+ *
+ * Purpose: Extract IL2P frames from a stream of bits and process them.
+ *
+ * References: http://tarpn.net/t/il2p/il2p-specification0-4.pdf
+ *
+ *******************************************************************************/
+
+#include "direwolf.h"
+
+#include
+#include
+#include
+#include
+
+#include "textcolor.h"
+#include "il2p.h"
+#include "multi_modem.h"
+#include "demod.h"
+
+
+struct il2p_context_s {
+
+ enum { IL2P_SEARCHING=0, IL2P_HEADER, IL2P_PAYLOAD, IL2P_DECODE } state;
+
+ unsigned int acc; // Accumulate most recent 24 bits for sync word matching.
+ // Lower 8 bits are also used for accumulating bytes for
+ // the header and payload.
+
+ int bc; // Bit counter so we know when a complete byte has been accumulated.
+
+ int polarity; // 1 if opposite of expected polarity.
+
+ unsigned char shdr[IL2P_HEADER_SIZE+IL2P_HEADER_PARITY];
+ // Scrambled header as received over the radio. Includes parity.
+ int hc; // Number if bytes placed in above.
+
+ unsigned char uhdr[IL2P_HEADER_SIZE]; // Header after FEC and unscrambling.
+
+ int eplen; // Encoded payload length. This is not the nuumber from
+ // from the header but rather the number of encoded bytes to gather.
+
+ unsigned char spayload[IL2P_MAX_ENCODED_PAYLOAD_SIZE];
+ // Scrambled and encoded payload as received over the radio.
+ int pc; // Number of bytes placed in above.
+
+ int corrected; // Number of symbols corrected by RS FEC.
+};
+
+static struct il2p_context_s *il2p_context[MAX_CHANS][MAX_SUBCHANS][MAX_SLICERS];
+
+
+
+/***********************************************************************************
+ *
+ * Name: il2p_rec_bit
+ *
+ * Purpose: Extract FX.25 packets from a stream of bits.
+ *
+ * Inputs: chan - Channel number.
+ *
+ * subchan - This allows multiple demodulators per channel.
+ *
+ * slice - Allows multiple slicers per demodulator (subchannel).
+ *
+ * dbit - One bit from the received data stream.
+ *
+ * Description: This is called once for each received bit.
+ * For each valid packet, process_rec_frame() is called for further processing.
+ * It can gather multiple candidates from different parallel demodulators
+ * ("subchannels") and slicers, then decide which one is the best.
+ *
+ ***********************************************************************************/
+
+void il2p_rec_bit (int chan, int subchan, int slice, int dbit)
+{
+
+// Allocate context blocks only as needed.
+
+ struct il2p_context_s *F = il2p_context[chan][subchan][slice];
+ if (F == NULL) {
+ assert (chan >= 0 && chan < MAX_CHANS);
+ assert (subchan >= 0 && subchan < MAX_SUBCHANS);
+ assert (slice >= 0 && slice < MAX_SLICERS);
+ F = il2p_context[chan][subchan][slice] = (struct il2p_context_s *)malloc(sizeof (struct il2p_context_s));
+ assert (F != NULL);
+ memset (F, 0, sizeof(struct il2p_context_s));
+ }
+
+// Accumulate most recent 24 bits received. Most recent is LSB.
+
+ F->acc = ((F->acc << 1) | (dbit & 1)) & 0x00ffffff;
+
+// State machine to look for sync word then gather appropriate number of header and payload bytes.
+
+ switch (F->state) {
+
+ case IL2P_SEARCHING: // Searching for the sync word.
+
+ if (__builtin_popcount (F->acc ^ IL2P_SYNC_WORD) <= 1) { // allow single bit mismatch
+ //text_color_set (DW_COLOR_INFO);
+ //dw_printf ("IL2P header has normal polarity\n");
+ F->polarity = 0;
+ F->state = IL2P_HEADER;
+ F->bc = 0;
+ F->hc = 0;
+ }
+ else if (__builtin_popcount ((~F->acc & 0x00ffffff) ^ IL2P_SYNC_WORD) <= 1) {
+ text_color_set (DW_COLOR_INFO);
+ // FIXME - this pops up occasionally with random noise. Find better way to convey information.
+ // This also happens for each slicer - to noisy.
+ //dw_printf ("IL2P header has reverse polarity\n");
+ F->polarity = 1;
+ F->state = IL2P_HEADER;
+ F->bc = 0;
+ F->hc = 0;
+ }
+ break;
+
+ case IL2P_HEADER: // Gathering the header.
+
+ F->bc++;
+ if (F->bc == 8) { // full byte has been collected.
+ F->bc = 0;
+ if ( ! F->polarity) {
+ F->shdr[F->hc++] = F->acc & 0xff;
+ }
+ else {
+ F->shdr[F->hc++] = (~ F->acc) & 0xff;
+ }
+ if (F->hc == IL2P_HEADER_SIZE+IL2P_HEADER_PARITY) { // Have all of header
+
+ if (il2p_get_debug() >= 1) {
+ text_color_set (DW_COLOR_DEBUG);
+ dw_printf ("IL2P header as received [%d.%d.%d]:\n", chan, subchan, slice);
+ fx_hex_dump (F->shdr, IL2P_HEADER_SIZE+IL2P_HEADER_PARITY);
+ }
+
+ // Fix any errors and descramble.
+ F->corrected = il2p_clarify_header(F->shdr, F->uhdr);
+
+ if (F->corrected >= 0) { // Good header.
+ // How much payload is expected?
+ il2p_payload_properties_t plprop;
+ int hdr_type, max_fec;
+ int len = il2p_get_header_attributes (F->uhdr, &hdr_type, &max_fec);
+
+ F->eplen = il2p_payload_compute (&plprop, len, max_fec);
+
+ if (il2p_get_debug() >= 1) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("IL2P header after correcting %d symbols and unscrambling [%d.%d.%d]:\n", F->corrected, chan, subchan, slice);
+ fx_hex_dump (F->uhdr, IL2P_HEADER_SIZE);
+ dw_printf ("Header type %d, max fec = %d\n", hdr_type, max_fec);
+ dw_printf ("Need to collect %d encoded bytes for %d byte payload.\n", F->eplen, len);
+ dw_printf ("%d small blocks of %d and %d large blocks of %d. %d parity symbols per block\n",
+ plprop.small_block_count, plprop.small_block_size,
+ plprop.large_block_count, plprop.large_block_size, plprop.parity_symbols_per_block);
+ }
+
+ if (F->eplen >= 1) { // Need to gather payload.
+ F->pc = 0;
+ F->state = IL2P_PAYLOAD;
+ }
+ else if (F->eplen == 0) { // No payload.
+ F->pc = 0;
+ F->state = IL2P_DECODE;
+ }
+ else { // Error.
+
+ if (il2p_get_debug() >= 1) {
+ text_color_set (DW_COLOR_ERROR);
+ dw_printf ("IL2P header INVALID.\n");
+ }
+
+ F->state = IL2P_SEARCHING;
+ }
+ } // good header after FEC.
+ else {
+ F->state = IL2P_SEARCHING; // Header failed FEC check.
+ }
+ } // entire header has been collected.
+ } // full byte collected.
+ break;
+
+ case IL2P_PAYLOAD: // Gathering the payload, if any.
+
+ F->bc++;
+ if (F->bc == 8) { // full byte has been collected.
+ F->bc = 0;
+ if ( ! F->polarity) {
+ F->spayload[F->pc++] = F->acc & 0xff;
+ }
+ else {
+ F->spayload[F->pc++] = (~ F->acc) & 0xff;
+ }
+ if (F->pc == F->eplen) {
+
+ // TODO?: for symmetry it seems like we should clarify the payload before combining.
+
+ F->state = IL2P_DECODE;
+ }
+ }
+ break;
+
+ case IL2P_DECODE:
+ // We get here after a good header and any payload has been collected.
+ // Processing is delayed by one bit but I think it makes the logic cleaner.
+ // During unit testing be sure to send an extra bit to flush it out at the end.
+
+ // in uhdr[IL2P_HEADER_SIZE]; // Header after FEC and descrambling.
+
+ // TODO?: for symmetry, we might decode the payload here and later build the frame.
+
+ {
+ packet_t pp = il2p_decode_header_payload (F->uhdr, F->spayload, &(F->corrected));
+
+ if (il2p_get_debug() >= 1) {
+ if (pp != NULL) {
+ ax25_hex_dump (pp);
+ }
+ else {
+ // Most likely too many FEC errors.
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FAILED to construct frame in %s.\n", __func__);
+ }
+ }
+
+ if (pp != NULL) {
+ alevel_t alevel = demod_get_audio_level (chan, subchan);
+ retry_t retries = F->corrected;
+ int is_fx25 = 1; // FIXME: distinguish fx.25 and IL2P.
+ // Currently this just means that a FEC mode was used.
+
+ // TODO: Could we put last 3 arguments in packet object rather than passing around separately?
+
+ multi_modem_process_rec_packet (chan, subchan, slice, pp, alevel, retries, is_fx25);
+ }
+ } // end block for local variables.
+
+ if (il2p_get_debug() >= 1) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("-----\n");
+ }
+
+ F->state = IL2P_SEARCHING;
+ break;
+
+ } // end of switch
+
+} // end il2p_rec_bit
+
+
+// end il2p_rec.c
diff --git a/src/il2p_scramble.c b/src/il2p_scramble.c
new file mode 100644
index 00000000..6219cddb
--- /dev/null
+++ b/src/il2p_scramble.c
@@ -0,0 +1,157 @@
+//
+// This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+// Copyright (C) 2021 John Langner, WB2OSZ
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+
+/*--------------------------------------------------------------------------------
+ *
+ * File: il2p_scramble.c
+ *
+ * Purpose: Scramble / descramble data as specified in the IL2P protocol specification.
+ *
+ *--------------------------------------------------------------------------------*/
+
+
+#include "direwolf.h"
+
+#include
+#include
+#include
+#include
+
+#include "il2p.h"
+
+
+// Scramble bits for il2p transmit.
+
+// Note that there is a delay of 5 until the first bit comes out.
+// So we need to need to ignore the first 5 out and stick in
+// an extra 5 filler bits to flush at the end.
+
+#define INIT_TX_LSFR 0x00f
+
+static inline int scramble_bit (int in, int *state)
+{
+ int out = ((*state >> 4) ^ *state) & 1;
+ *state = ( (((in ^ *state) & 1) << 9) | (*state ^ ((*state & 1) << 4)) ) >> 1;
+ return (out);
+}
+
+
+// Undo data scrambling for il2p receive.
+
+#define INIT_RX_LSFR 0x1f0
+
+static inline int descramble_bit (int in, int *state)
+{
+ int out = (in ^ *state) & 1;
+ *state = ((*state >> 1) | ((in & 1) << 8)) ^ ((in & 1) << 3);
+ return (out);
+}
+
+
+/*--------------------------------------------------------------------------------
+ *
+ * Function: il2p_scramble_block
+ *
+ * Purpose: Scramble a block before adding RS parity.
+ *
+ * Inputs: in Array of bytes.
+ * len Number of bytes both in and out.
+ *
+ * Outputs: out Array of bytes.
+ *
+ *--------------------------------------------------------------------------------*/
+
+void il2p_scramble_block (unsigned char *in, unsigned char *out, int len)
+{
+ int tx_lfsr_state = INIT_TX_LSFR;
+
+ memset (out, 0, len);
+
+ int skipping = 1; // Discard the first 5 out.
+ int ob = 0; // Index to output byte.
+ int om = 0x80; // Output bit mask;
+ for (int ib = 0; ib < len; ib++) {
+ for (int im = 0x80; im != 0; im >>= 1) {
+ int s = scramble_bit((in[ib] & im) != 0, &tx_lfsr_state);
+ if (ib == 0 && im == 0x04) skipping = 0;
+ if ( ! skipping) {
+ if (s) {
+ out[ob] |= om;
+ }
+ om >>= 1;
+ if (om == 0) {
+ om = 0x80;
+ ob++;
+ }
+ }
+ }
+ }
+ // Flush it.
+
+ // This is a relic from when I thought the state would need to
+ // be passed along for the next block.
+ // Preserve the LSFR state from before flushing.
+ // This might be needed as the initial state for later payload blocks.
+ int x = tx_lfsr_state;
+ for (int n = 0; n < 5; n++) {
+ int s = scramble_bit(0, &x);
+ if (s) {
+ out[ob] |= om;
+ }
+ om >>=1;
+ if (om == 0) {
+ om = 0x80;
+ ob++;
+ }
+ }
+
+} // end il2p_scramble_block
+
+
+
+/*--------------------------------------------------------------------------------
+ *
+ * Function: il2p_descramble_block
+ *
+ * Purpose: Descramble a block after removing RS parity.
+ *
+ * Inputs: in Array of bytes.
+ * len Number of bytes both in and out.
+ *
+ * Outputs: out Array of bytes.
+ *
+ *--------------------------------------------------------------------------------*/
+
+void il2p_descramble_block (unsigned char *in, unsigned char *out, int len)
+{
+ int rx_lfsr_state = INIT_RX_LSFR;
+
+ memset (out, 0, len);
+
+ for (int b = 0; b < len; b++) {
+ for (int m = 0x80; m != 0; m >>= 1) {
+ int d = descramble_bit((in[b] & m) != 0, &rx_lfsr_state);
+ if (d) {
+ out[b] |= m;
+ }
+ }
+ }
+}
+
+// end il2p_scramble.c
diff --git a/src/il2p_send.c b/src/il2p_send.c
new file mode 100644
index 00000000..3c4554e0
--- /dev/null
+++ b/src/il2p_send.c
@@ -0,0 +1,147 @@
+//
+// This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+// Copyright (C) 2021 John Langner, WB2OSZ
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+
+#include "direwolf.h"
+
+#include
+#include
+#include
+#include
+
+#include "il2p.h"
+#include "textcolor.h"
+#include "audio.h"
+#include "gen_tone.h"
+
+
+static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "il2p_send_frame"
+
+static void send_bytes (int chan, unsigned char *b, int count, int polarity);
+static void send_bit (int chan, int b, int polarity);
+
+
+
+/*-------------------------------------------------------------
+ *
+ * Name: il2p_send_frame
+ *
+ * Purpose: Convert frames to a stream of bits in IL2P format.
+ *
+ * Inputs: chan - Audio channel number, 0 = first.
+ *
+ * pp - Pointer to packet object.
+ *
+ * max_fec - 1 to force 16 parity symbols for each payload block.
+ * 0 for automatic depending on block size.
+ *
+ * polarity - 0 for normal. 1 to invert signal.
+ * 2 special case for testing - introduce some errors to test FEC.
+ *
+ * Outputs: Bits are shipped out by calling tone_gen_put_bit().
+ *
+ * Returns: Number of bits sent including
+ * - Preamble (01010101...)
+ * - 3 byte Sync Word.
+ * - 15 bytes for Header.
+ * - Optional payload.
+ * The required time can be calculated by dividing this
+ * number by the transmit rate of bits/sec.
+ * -1 is returned for failure.
+ *
+ * Description: Generate an IL2P encoded frame.
+ *
+ * Assumptions: It is assumed that the tone_gen module has been
+ * properly initialized so that bits sent with
+ * tone_gen_put_bit() are processed correctly.
+ *
+ * Errors: Return -1 for error. Probably frame too large.
+ *
+ * Note: Inconsistency here. ax25 version has just a byte array
+ * and length going in. Here we need the full packet object.
+ *
+ *--------------------------------------------------------------*/
+
+int il2p_send_frame (int chan, packet_t pp, int max_fec, int polarity)
+{
+ unsigned char encoded[IL2P_MAX_PACKET_SIZE];
+
+ encoded[0] = ( IL2P_SYNC_WORD >> 16 ) & 0xff;
+ encoded[1] = ( IL2P_SYNC_WORD >> 8 ) & 0xff;
+ encoded[2] = ( IL2P_SYNC_WORD ) & 0xff;
+
+ int elen = il2p_encode_frame (pp, max_fec, encoded + IL2P_SYNC_WORD_SIZE);
+ if (elen <= 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("IL2P: Unable to encode frame into IL2P.\n");
+ return (-1);
+ }
+
+ elen += IL2P_SYNC_WORD_SIZE;
+
+ number_of_bits_sent[chan] = 0;
+
+ if (il2p_get_debug() >= 1) {
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("IL2P frame, max_fec = %d, %d encoded bytes total\n", max_fec, elen);
+ fx_hex_dump (encoded, elen);
+ }
+
+ // Clobber some bytes for testing.
+ if (polarity >= 2) {
+ for (int j = 10; j < elen; j+=100) {
+ encoded[j] = ~ encoded[j];
+ }
+ }
+
+ // Send bits to modulator.
+
+ static unsigned char preamble = IL2P_PREAMBLE;
+
+ send_bytes (chan, &preamble, 1, polarity);
+ send_bytes (chan, encoded, elen, polarity);
+
+ return (number_of_bits_sent[chan]);
+}
+
+
+static void send_bytes (int chan, unsigned char *b, int count, int polarity)
+{
+ for (int j = 0; j < count; j++) {
+ unsigned int x = b[j];
+ for (int k = 0; k < 8; k++) {
+ send_bit (chan, (x & 0x80) != 0, polarity);
+ x <<= 1;
+ }
+ }
+}
+
+// NRZI would be applied for AX.25 but IL2P does not use it.
+// However we do have an option to invert the signal.
+// The direwolf receive implementation will automatically compensate
+// for either polarity but other implementations might not.
+
+static void send_bit (int chan, int b, int polarity)
+{
+ tone_gen_put_bit (chan, (b ^ polarity) & 1);
+ number_of_bits_sent[chan]++;
+}
+
+
+
+// end il2p_send.c
\ No newline at end of file
diff --git a/src/il2p_test.c b/src/il2p_test.c
new file mode 100644
index 00000000..c983daff
--- /dev/null
+++ b/src/il2p_test.c
@@ -0,0 +1,977 @@
+//
+// This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+// Copyright (C) 2021 John Langner, WB2OSZ
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+
+#include "direwolf.h"
+
+#include
+#include
+#include
+#include
+
+#include "textcolor.h"
+#include "il2p.h"
+#include "ax25_pad.h"
+#include "ax25_pad2.h"
+#include "multi_modem.h"
+
+
+static void test_scramble(void);
+static void test_rs(void);
+static void test_payload(void);
+static void test_example_headers(void);
+static void all_frame_types(void);
+static void test_serdes(void);
+static void decode_bitstream(void);
+
+/*-------------------------------------------------------------
+ *
+ * Name: il2p_test.c
+ *
+ * Purpose: Unit tests for IL2P protocol functions.
+ *
+ * Errors: Die if anything goes wrong.
+ *
+ *--------------------------------------------------------------*/
+
+
+int main ()
+{
+ int enable_color = 1;
+ text_color_init (enable_color);
+
+ int enable_debug_out = 0;
+ il2p_init(enable_debug_out);
+
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("Begin IL2P unit tests.\n");
+
+// These start simple and later complex cases build upon earlier successes.
+
+// Test scramble and descramble.
+
+ test_scramble();
+
+// Test Reed Solomon error correction.
+
+ test_rs();
+
+// Test payload functions.
+
+ test_payload();
+
+// Try encoding the example headers in the protocol spec.
+
+ test_example_headers();
+
+// Convert all of the AX.25 frame types to IL2P and back again.
+
+ all_frame_types();
+
+// Use same serialize / deserialize functions used on the air.
+
+ test_serdes ();
+
+// Decode bitstream from demodulator if data file is available.
+// TODO: Very large info parts. Appropriate error if too long.
+// TODO: More than 2 addresses.
+
+ decode_bitstream();
+
+ text_color_set(DW_COLOR_REC);
+ dw_printf ("\n----------\n\n");
+ dw_printf ("\nSUCCESS!\n");
+
+ return (EXIT_SUCCESS);
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+//
+// Test scrambling and descrambling.
+//
+/////////////////////////////////////////////////////////////////////////////////////////////
+
+static void test_scramble(void)
+{
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("Test scrambling...\n");
+
+// First an example from the protocol specification to make sure I'm compatible.
+
+ static unsigned char scramin1[] = { 0x63, 0xf1, 0x40, 0x40, 0x40, 0x00, 0x6b, 0x2b, 0x54, 0x28, 0x25, 0x2a, 0x0f };
+ static unsigned char scramout1[] = { 0x6a, 0xea, 0x9c, 0xc2, 0x01, 0x11, 0xfc, 0x14, 0x1f, 0xda, 0x6e, 0xf2, 0x53 };
+ unsigned char scramout[sizeof(scramin1)];
+
+ il2p_scramble_block (scramin1, scramout, sizeof(scramin1));
+ assert (memcmp(scramout, scramout, sizeof(scramout1)) == 0);
+
+} // end test_scramble.
+
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+//
+// Test Reed Solomon encode/decode examples found in the protocol spec.
+// The data part is scrambled but that does not matter here because.
+// We are only concerned abound adding the parity and verifying.
+//
+/////////////////////////////////////////////////////////////////////////////////////////////
+
+
+static void test_rs()
+{
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("Test Reed Solomon functions...\n");
+
+ static unsigned char example_s[] = { 0x26, 0x57, 0x4d, 0x57, 0xf1, 0x96, 0xcc, 0x85, 0x42, 0xe7, 0x24, 0xf7, 0x2e,
+ 0x8a, 0x97 };
+ unsigned char parity_out[2];
+ il2p_encode_rs (example_s, 13, 2, parity_out);
+ //dw_printf ("DEBUG RS encode %02x %02x\n", parity_out[0], parity_out[1]);
+ assert (memcmp(parity_out, example_s + 13, 2) == 0);
+
+
+ static unsigned char example_u[] = { 0x6a, 0xea, 0x9c, 0xc2, 0x01, 0x11, 0xfc, 0x14, 0x1f, 0xda, 0x6e, 0xf2, 0x53,
+ 0x91, 0xbd };
+ il2p_encode_rs (example_u, 13, 2, parity_out);
+ //dw_printf ("DEBUG RS encode %02x %02x\n", parity_out[0], parity_out[1]);
+ assert (memcmp(parity_out, example_u + 13, 2) == 0);
+
+ // See if we can go the other way.
+
+ unsigned char received[15];
+ unsigned char corrected[15];
+ int e;
+
+ e = il2p_decode_rs (example_s, 13, 2, corrected);
+ assert (e == 0);
+ assert (memcmp(example_s, corrected, 13) == 0);
+
+ memcpy (received, example_s, 15);
+ received[0] = '?';
+ e = il2p_decode_rs (received, 13, 2, corrected);
+ assert (e == 1);
+ assert (memcmp(example_s, corrected, 13) == 0);
+
+ e = il2p_decode_rs (example_u, 13, 2, corrected);
+ assert (e == 0);
+ assert (memcmp(example_u, corrected, 13) == 0);
+
+ memcpy (received, example_u, 15);
+ received[12] = '?';
+ e = il2p_decode_rs (received, 13, 2, corrected);
+ assert (e == 1);
+ assert (memcmp(example_u, corrected, 13) == 0);
+
+ received[1] = '?';
+ received[2] = '?';
+ e = il2p_decode_rs (received, 13, 2, corrected);
+ assert (e == -1);
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+//
+// Test payload functions.
+//
+/////////////////////////////////////////////////////////////////////////////////////////////
+
+static void test_payload(void)
+{
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("Test payload functions...\n");
+
+ il2p_payload_properties_t ipp;
+ int e;
+
+// Examples in specification.
+
+ e = il2p_payload_compute (&ipp, 100, 0);
+ assert (ipp.small_block_size == 100);
+ assert (ipp.large_block_size == 101);
+ assert (ipp.large_block_count == 0);
+ assert (ipp.small_block_count == 1);
+ assert (ipp.parity_symbols_per_block == 4);
+
+ e = il2p_payload_compute (&ipp, 236, 0);
+ assert (ipp.small_block_size == 236);
+ assert (ipp.large_block_size == 237);
+ assert (ipp.large_block_count == 0);
+ assert (ipp.small_block_count == 1);
+ assert (ipp.parity_symbols_per_block == 8);
+
+ e = il2p_payload_compute (&ipp, 512, 0);
+ assert (ipp.small_block_size == 170);
+ assert (ipp.large_block_size == 171);
+ assert (ipp.large_block_count == 2);
+ assert (ipp.small_block_count == 1);
+ assert (ipp.parity_symbols_per_block == 6);
+
+ e = il2p_payload_compute (&ipp, 1023, 0);
+ assert (ipp.small_block_size == 204);
+ assert (ipp.large_block_size == 205);
+ assert (ipp.large_block_count == 3);
+ assert (ipp.small_block_count == 2);
+ assert (ipp.parity_symbols_per_block == 8);
+
+// Now try all possible sizes for Baseline FEC Parity.
+
+ for (int n = 1; n <= IL2P_MAX_PAYLOAD_SIZE; n++) {
+ e = il2p_payload_compute (&ipp, n, 0);
+ //dw_printf ("bytecount=%d, smallsize=%d, largesize=%d, largecount=%d, smallcount=%d\n", n,
+ // ipp.small_block_size, ipp.large_block_size,
+ // ipp.large_block_count, ipp.small_block_count);
+ //fflush (stdout);
+
+ assert (ipp.payload_block_count >= 1 && ipp.payload_block_count <= IL2P_MAX_PAYLOAD_BLOCKS);
+ assert (ipp.payload_block_count == ipp.small_block_count + ipp.large_block_count);
+ assert (ipp.small_block_count * ipp.small_block_size +
+ ipp.large_block_count * ipp.large_block_size == n);
+ assert (ipp.parity_symbols_per_block == 2 ||
+ ipp.parity_symbols_per_block == 4 ||
+ ipp.parity_symbols_per_block == 6 ||
+ ipp.parity_symbols_per_block == 8);
+
+ // Data and parity must fit in RS block size of 255.
+ // Size test does not apply if block count is 0.
+ assert (ipp.small_block_count == 0 || ipp.small_block_size + ipp.parity_symbols_per_block <= 255);
+ assert (ipp.large_block_count == 0 || ipp.large_block_size + ipp.parity_symbols_per_block <= 255);
+ }
+
+// All sizes for MAX FEC.
+
+ for (int n = 1; n <= IL2P_MAX_PAYLOAD_SIZE; n++) {
+ e = il2p_payload_compute (&ipp, n, 1); // 1 for max fec.
+ //dw_printf ("bytecount=%d, smallsize=%d, largesize=%d, largecount=%d, smallcount=%d\n", n,
+ // ipp.small_block_size, ipp.large_block_size,
+ // ipp.large_block_count, ipp.small_block_count);
+ //fflush (stdout);
+
+ assert (ipp.payload_block_count >= 1 && ipp.payload_block_count <= IL2P_MAX_PAYLOAD_BLOCKS);
+ assert (ipp.payload_block_count == ipp.small_block_count + ipp.large_block_count);
+ assert (ipp.small_block_count * ipp.small_block_size +
+ ipp.large_block_count * ipp.large_block_size == n);
+ assert (ipp.parity_symbols_per_block == 16);
+
+ // Data and parity must fit in RS block size of 255.
+ // Size test does not apply if block count is 0.
+ assert (ipp.small_block_count == 0 || ipp.small_block_size + ipp.parity_symbols_per_block <= 255);
+ assert (ipp.large_block_count == 0 || ipp.large_block_size + ipp.parity_symbols_per_block <= 255);
+ }
+
+// Now let's try encoding payloads and extracting original again.
+// This will also provide exercise for scrambling and Reed Solomon under more conditions.
+
+ unsigned char original_payload[IL2P_MAX_PAYLOAD_SIZE];
+ for (int n = 0; n < IL2P_MAX_PAYLOAD_SIZE; n++) {
+ original_payload[n] = n & 0xff;
+ }
+ for (int max_fec = 0; max_fec <= 1; max_fec++) {
+ for (int payload_length = 1; payload_length <= IL2P_MAX_PAYLOAD_SIZE; payload_length++) {
+ //dw_printf ("\n--------- max_fec = %d, payload_length = %d\n", max_fec, payload_length);
+ unsigned char encoded[IL2P_MAX_ENCODED_PAYLOAD_SIZE];
+ int k = il2p_encode_payload (original_payload, payload_length, max_fec, encoded);
+
+ //dw_printf ("payload length %d %s -> %d\n", payload_length, max_fec ? "M" : "", k);
+ assert (k > payload_length && k <= IL2P_MAX_ENCODED_PAYLOAD_SIZE);
+
+ // Now extract.
+
+ unsigned char extracted[IL2P_MAX_PAYLOAD_SIZE];
+ int symbols_corrected = 0;
+ int e = il2p_decode_payload (encoded, payload_length, max_fec, extracted, &symbols_corrected);
+ //dw_printf ("e = %d, payload_length = %d\n", e, payload_length);
+ assert (e == payload_length);
+
+ // if (memcmp (original_payload, extracted, payload_length) != 0) {
+ // dw_printf ("********** Received message not as expected. **********\n");
+ // fx_hex_dump(extracted, payload_length);
+ // }
+ assert (memcmp (original_payload, extracted, payload_length) == 0);
+ }
+ }
+ (void)e;
+} // end test_payload
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+//
+// Test header examples found in protocol specification.
+//
+/////////////////////////////////////////////////////////////////////////////////////////////
+
+static void test_example_headers()
+{
+
+//----------- Example 1: AX.25 S-Frame --------------
+
+// This frame sample only includes a 15 byte header, without PID field.
+// Destination Callsign: ?KA2DEW-2
+// Source Callsign: ?KK4HEJ-7
+// N(R): 5
+// P/F: 1
+// C: 1
+// Control Opcode: 00 (Receive Ready)
+//
+// AX.25 data:
+// 96 82 64 88 8a ae e4 96 96 68 90 8a 94 6f b1
+//
+// IL2P Data Prior to Scrambling and RS Encoding:
+// 2b a1 12 24 25 77 6b 2b 54 68 25 2a 27
+//
+// IL2P Data After Scrambling and RS Encoding:
+// 26 57 4d 57 f1 96 cc 85 42 e7 24 f7 2e 8a 97
+
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("Example 1: AX.25 S-Frame...\n");
+
+ static unsigned char example1[] = {0x96, 0x82, 0x64, 0x88, 0x8a, 0xae, 0xe4, 0x96, 0x96, 0x68, 0x90, 0x8a, 0x94, 0x6f, 0xb1};
+ static unsigned char header1[] = {0x2b, 0xa1, 0x12, 0x24, 0x25, 0x77, 0x6b, 0x2b, 0x54, 0x68, 0x25, 0x2a, 0x27 };
+ unsigned char header[IL2P_HEADER_SIZE];
+ unsigned char sresult[32];
+ memset (header, 0, sizeof(header));
+ memset (sresult, 0, sizeof(sresult));
+ unsigned char check[2];
+ alevel_t alevel;
+ memset(&alevel, 0, sizeof(alevel));
+
+ packet_t pp = ax25_from_frame (example1, sizeof(example1), alevel);
+ assert (pp != NULL);
+ int e;
+ e = il2p_type_1_header (pp, 0, header);
+ assert (e == 0);
+ ax25_delete(pp);
+
+ //dw_printf ("Example 1 header:\n");
+ //for (int i = 0 ; i < sizeof(header); i++) {
+ // dw_printf (" %02x", header[i]);
+ //}
+ ///dw_printf ("\n");
+
+ assert (memcmp(header, header1, sizeof(header)) == 0);
+
+ il2p_scramble_block (header, sresult, 13);
+ //dw_printf ("Expect scrambled 26 57 4d 57 f1 96 cc 85 42 e7 24 f7 2e\n");
+ //for (int i = 0 ; i < sizeof(sresult); i++) {
+ // dw_printf (" %02x", sresult[i]);
+ //}
+ //dw_printf ("\n");
+
+ il2p_encode_rs (sresult, 13, 2, check);
+
+ //dw_printf ("expect checksum = 8a 97\n");
+ //dw_printf ("check = ");
+ //for (int i = 0 ; i < sizeof(check); i++) {
+ // dw_printf (" %02x", check[i]);
+ //}
+ //dw_printf ("\n");
+ assert (check[0] == 0x8a);
+ assert (check[1] == 0x97);
+
+// Can we go from IL2P back to AX.25?
+
+ pp = il2p_decode_header_type_1 (header, 0);
+ assert (pp != NULL);
+
+ char dst_addr[AX25_MAX_ADDR_LEN];
+ char src_addr[AX25_MAX_ADDR_LEN];
+
+ ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dst_addr);
+ ax25_get_addr_with_ssid (pp, AX25_SOURCE, src_addr);
+
+ ax25_frame_type_t frame_type;
+ cmdres_t cr; // command or response.
+ char description[64];
+ int pf; // Poll/Final.
+ int nr, ns; // Sequence numbers.
+
+ frame_type = ax25_frame_type (pp, &cr, description, &pf, &nr, &ns);
+ (void)frame_type;
+#if 1
+ dw_printf ("%s(): %s>%s: %s\n", __func__, src_addr, dst_addr, description);
+#endif
+// TODO: compare binary.
+ ax25_delete (pp);
+
+ dw_printf ("Example 1 header OK\n");
+
+
+// -------------- Example 2 - UI frame, no info part ------------------
+
+// This is an AX.25 Unnumbered Information frame, such as APRS.
+// Destination Callsign: ?CQ -0
+// Source Callsign: ?KK4HEJ-15
+// P/F: 0
+// C: 0
+// Control Opcode: 3 Unnumbered Information
+// PID: 0xF0 No L3
+//
+// AX.25 Data:
+// 86 a2 40 40 40 40 60 96 96 68 90 8a 94 7f 03 f0
+//
+// IL2P Data Prior to Scrambling and RS Encoding:
+// 63 f1 40 40 40 00 6b 2b 54 28 25 2a 0f
+//
+// IL2P Data After Scrambling and RS Encoding:
+// 6a ea 9c c2 01 11 fc 14 1f da 6e f2 53 91 bd
+
+
+ //dw_printf ("---------- example 2 ------------\n");
+ static unsigned char example2[] = { 0x86, 0xa2, 0x40, 0x40, 0x40, 0x40, 0x60, 0x96, 0x96, 0x68, 0x90, 0x8a, 0x94, 0x7f, 0x03, 0xf0 };
+ static unsigned char header2[] = { 0x63, 0xf1, 0x40, 0x40, 0x40, 0x00, 0x6b, 0x2b, 0x54, 0x28, 0x25, 0x2a, 0x0f };
+ memset (header, 0, sizeof(header));
+ memset (sresult, 0, sizeof(sresult));
+ memset(&alevel, 0, sizeof(alevel));
+
+ pp = ax25_from_frame (example2, sizeof(example2), alevel);
+ assert (pp != NULL);
+ e = il2p_type_1_header (pp, 0, header);
+ assert (e == 0);
+ ax25_delete(pp);
+
+ //dw_printf ("Example 2 header:\n");
+ //for (int i = 0 ; i < sizeof(header); i++) {
+ // dw_printf (" %02x", header[i]);
+ //}
+ //dw_printf ("\n");
+
+ assert (memcmp(header, header2, sizeof(header2)) == 0);
+
+ il2p_scramble_block (header, sresult, 13);
+ //dw_printf ("Expect scrambled 6a ea 9c c2 01 11 fc 14 1f da 6e f2 53\n");
+ //for (int i = 0 ; i < sizeof(sresult); i++) {
+ // dw_printf (" %02x", sresult[i]);
+ //}
+ //dw_printf ("\n");
+
+ il2p_encode_rs (sresult, 13, 2, check);
+
+ //dw_printf ("expect checksum = 91 bd\n");
+ //dw_printf ("check = ");
+ //for (int i = 0 ; i < sizeof(check); i++) {
+ // dw_printf (" %02x", check[i]);
+ //}
+ //dw_printf ("\n");
+ assert (check[0] == 0x91);
+ assert (check[1] == 0xbd);
+
+// Can we go from IL2P back to AX.25?
+
+ pp = il2p_decode_header_type_1 (header, 0);
+ assert (pp != NULL);
+
+ ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dst_addr);
+ ax25_get_addr_with_ssid (pp, AX25_SOURCE, src_addr);
+
+ frame_type = ax25_frame_type (pp, &cr, description, &pf, &nr, &ns);
+ (void)frame_type;
+#if 1
+ dw_printf ("%s(): %s>%s: %s\n", __func__, src_addr, dst_addr, description);
+#endif
+// TODO: compare binary.
+
+ ax25_delete (pp);
+// TODO: more examples
+
+ dw_printf ("Example 2 header OK\n");
+
+
+// -------------- Example 3 - I Frame ------------------
+
+// This is an AX.25 I-Frame with 9 bytes of information after the 16 byte header.
+//
+// Destination Callsign: ?KA2DEW-2
+// Source Callsign: ?KK4HEJ-2
+// P/F: 1
+// C: 1
+// N(R): 5
+// N(S): 4
+// AX.25 PID: 0xCF TheNET
+// IL2P Payload Byte Count: 9
+//
+// AX.25 Data:
+// 96 82 64 88 8a ae e4 96 96 68 90 8a 94 65 b8 cf 30 31 32 33 34 35 36 37 38
+//
+// IL2P Scrambled and Encoded Data:
+// 26 13 6d 02 8c fe fb e8 aa 94 2d 6a 34 43 35 3c 69 9f 0c 75 5a 38 a1 7f f3 fc
+
+
+ //dw_printf ("---------- example 3 ------------\n");
+ static unsigned char example3[] = { 0x96, 0x82, 0x64, 0x88, 0x8a, 0xae, 0xe4, 0x96, 0x96, 0x68, 0x90, 0x8a, 0x94, 0x65, 0xb8, 0xcf, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38 };
+ static unsigned char header3[] = { 0x2b, 0xe1, 0x52, 0x64, 0x25, 0x77, 0x6b, 0x2b, 0xd4, 0x68, 0x25, 0xaa, 0x22 };
+ static unsigned char complete3[] = { 0x26, 0x13, 0x6d, 0x02, 0x8c, 0xfe, 0xfb, 0xe8, 0xaa, 0x94, 0x2d, 0x6a, 0x34, 0x43, 0x35, 0x3c, 0x69, 0x9f, 0x0c, 0x75, 0x5a, 0x38, 0xa1, 0x7f, 0xf3, 0xfc };
+ memset (header, 0, sizeof(header));
+ memset (sresult, 0, sizeof(sresult));
+ memset(&alevel, 0, sizeof(alevel));
+
+ pp = ax25_from_frame (example3, sizeof(example3), alevel);
+ assert (pp != NULL);
+ e = il2p_type_1_header (pp, 0, header);
+ assert (e == 9);
+ ax25_delete(pp);
+
+ //dw_printf ("Example 3 header:\n");
+ //for (int i = 0 ; i < sizeof(header); i++) {
+ // dw_printf (" %02x", header[i]);
+ //}
+ //dw_printf ("\n");
+
+ assert (memcmp(header, header3, sizeof(header)) == 0);
+
+ il2p_scramble_block (header, sresult, 13);
+ //dw_printf ("Expect scrambled 26 13 6d 02 8c fe fb e8 aa 94 2d 6a 34\n");
+ //for (int i = 0 ; i < sizeof(sresult); i++) {
+ // dw_printf (" %02x", sresult[i]);
+ //}
+ //dw_printf ("\n");
+
+ il2p_encode_rs (sresult, 13, 2, check);
+
+ //dw_printf ("expect checksum = 43 35\n");
+ //dw_printf ("check = ");
+ //for (int i = 0 ; i < sizeof(check); i++) {
+ // dw_printf (" %02x", check[i]);
+ //}
+ //dw_printf ("\n");
+
+ assert (check[0] == 0x43);
+ assert (check[1] == 0x35);
+
+ // That was only the header. We will get to the info part in a later test.
+
+// Can we go from IL2P back to AX.25?
+
+ pp = il2p_decode_header_type_1 (header, 0);
+ assert (pp != NULL);
+
+ ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dst_addr);
+ ax25_get_addr_with_ssid (pp, AX25_SOURCE, src_addr);
+
+ frame_type = ax25_frame_type (pp, &cr, description, &pf, &nr, &ns);
+ (void)frame_type;
+#if 1
+ dw_printf ("%s(): %s>%s: %s\n", __func__, src_addr, dst_addr, description);
+#endif
+// TODO: compare binary.
+
+ ax25_delete (pp);
+ dw_printf ("Example 3 header OK\n");
+
+// Example 3 again, this time the Information part is included.
+
+ pp = ax25_from_frame (example3, sizeof(example3), alevel);
+ assert (pp != NULL);
+
+ int max_fec = 0;
+ unsigned char iout[IL2P_MAX_PACKET_SIZE];
+ e = il2p_encode_frame (pp, max_fec, iout);
+
+ //dw_printf ("expected for example 3:\n");
+ //fx_hex_dump(complete3, sizeof(complete3));
+ //dw_printf ("actual result for example 3:\n");
+ //fx_hex_dump(iout, e);
+ // Does it match the example in the protocol spec?
+ assert (e == sizeof(complete3));
+ assert (memcmp(iout, complete3, sizeof(complete3)) == 0);
+ ax25_delete (pp);
+
+ dw_printf ("Example 3 with info OK\n");
+
+} // end test_example_headers
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+//
+// Test all of the frame types.
+//
+// Encode to IL2P format, decode, and verify that the result is the same as the original.
+//
+/////////////////////////////////////////////////////////////////////////////////////////////
+
+
+static void enc_dec_compare (packet_t pp1)
+{
+ for (int max_fec = 0; max_fec <= 1; max_fec++) {
+
+ unsigned char encoded[IL2P_MAX_PACKET_SIZE];
+ int enc_len;
+ enc_len = il2p_encode_frame (pp1, max_fec, encoded);
+ assert (enc_len >= 0);
+
+ packet_t pp2;
+ pp2 = il2p_decode_frame (encoded);
+ assert (pp2 != NULL);
+
+// Is it the same after encoding to IL2P and then decoding?
+
+ int len1 = ax25_get_frame_len (pp1);
+ unsigned char *data1 = ax25_get_frame_data_ptr (pp1);
+
+ int len2 = ax25_get_frame_len (pp2);
+ unsigned char *data2 = ax25_get_frame_data_ptr (pp2);
+
+ if (len1 != len2 || memcmp(data1, data2, len1) != 0) {
+
+ dw_printf ("\nEncode/Decode Error. Original:\n");
+ ax25_hex_dump (pp1);
+
+ dw_printf ("IL2P encoded as:\n");
+ fx_hex_dump(encoded, enc_len);
+
+ dw_printf ("Got turned into this:\n");
+ ax25_hex_dump (pp2);
+ }
+
+ assert (len1 == len2 && memcmp(data1, data2, len1) == 0);
+
+ ax25_delete (pp2);
+ }
+}
+
+static void all_frame_types(void)
+{
+ char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN];
+ int num_addr = 2;
+ cmdres_t cr;
+ ax25_frame_type_t ftype;
+ int pf = 0;
+ int pid = 0xf0;
+ int modulo;
+ int nr, ns;
+ unsigned char *pinfo = NULL;
+ int info_len = 0;
+ packet_t pp;
+
+ strcpy (addrs[0], "W2UB");
+ strcpy (addrs[1], "WB2OSZ-12");
+ num_addr = 2;
+
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("Testing all frame types.\n");
+
+/* U frame */
+
+ dw_printf ("\nU frames...\n");
+
+ for (ftype = frame_type_U_SABME; ftype <= frame_type_U_TEST; ftype++) {
+
+ for (pf = 0; pf <= 1; pf++) {
+
+ int cmin = 0, cmax = 1;
+
+ switch (ftype) {
+ // 0 = response, 1 = command
+ case frame_type_U_SABME: cmin = 1; cmax = 1; break;
+ case frame_type_U_SABM: cmin = 1; cmax = 1; break;
+ case frame_type_U_DISC: cmin = 1; cmax = 1; break;
+ case frame_type_U_DM: cmin = 0; cmax = 0; break;
+ case frame_type_U_UA: cmin = 0; cmax = 0; break;
+ case frame_type_U_FRMR: cmin = 0; cmax = 0; break;
+ case frame_type_U_UI: cmin = 0; cmax = 1; break;
+ case frame_type_U_XID: cmin = 0; cmax = 1; break;
+ case frame_type_U_TEST: cmin = 0; cmax = 1; break;
+ default: break; // avoid compiler warning.
+ }
+
+ for (cr = cmin; cr <= cmax; cr++) {
+
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("\nConstruct U frame, cr=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid);
+
+ pp = ax25_u_frame (addrs, num_addr, cr, ftype, pf, pid, pinfo, info_len);
+ ax25_hex_dump (pp);
+ enc_dec_compare (pp);
+ ax25_delete (pp);
+ }
+ }
+ }
+
+
+/* S frame */
+
+ //strcpy (addrs[2], "DIGI1-1");
+ //num_addr = 3;
+
+ dw_printf ("\nS frames...\n");
+
+ for (ftype = frame_type_S_RR; ftype <= frame_type_S_SREJ; ftype++) {
+
+ for (pf = 0; pf <= 1; pf++) {
+
+ modulo = 8;
+ nr = modulo / 2 + 1;
+
+ // SREJ can only be response.
+
+ for (cr = 0; cr <= (int)(ftype!=frame_type_S_SREJ); cr++) {
+
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("\nConstruct S frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid);
+
+ pp = ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf, NULL, 0);
+
+ ax25_hex_dump (pp);
+ enc_dec_compare (pp);
+ ax25_delete (pp);
+ }
+
+ modulo = 128;
+ nr = modulo / 2 + 1;
+
+ for (cr = 0; cr <= (int)(ftype!=frame_type_S_SREJ); cr++) {
+
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("\nConstruct S frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid);
+
+ pp = ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf, NULL, 0);
+
+ ax25_hex_dump (pp);
+ enc_dec_compare (pp);
+ ax25_delete (pp);
+ }
+ }
+ }
+
+/* SREJ is only S frame which can have information part. */
+
+ static unsigned char srej_info[] = { 1<<1, 2<<1, 3<<1, 4<<1 };
+
+ ftype = frame_type_S_SREJ;
+ for (pf = 0; pf <= 1; pf++) {
+
+ modulo = 128;
+ nr = 127;
+ cr = cr_res;
+
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("\nConstruct Multi-SREJ S frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid);
+
+ pp = ax25_s_frame (addrs, num_addr, cr, ftype, modulo, nr, pf, srej_info, (int)(sizeof(srej_info)));
+
+ ax25_hex_dump (pp);
+ enc_dec_compare (pp);
+ ax25_delete (pp);
+ }
+
+
+/* I frame */
+
+ dw_printf ("\nI frames...\n");
+
+ pinfo = (unsigned char*)"The rain in Spain stays mainly on the plain.";
+ info_len = strlen((char*)pinfo);
+
+ for (pf = 0; pf <= 1; pf++) {
+
+ modulo = 8;
+ nr = 0x55 & (modulo - 1);
+ ns = 0xaa & (modulo - 1);
+
+ for (cr = 1; cr <= 1; cr++) { // can only be command
+
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("\nConstruct I frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid);
+
+ pp = ax25_i_frame (addrs, num_addr, cr, modulo, nr, ns, pf, pid, pinfo, info_len);
+
+ ax25_hex_dump (pp);
+ enc_dec_compare (pp);
+ ax25_delete (pp);
+ }
+
+ modulo = 128;
+ nr = 0x55 & (modulo - 1);
+ ns = 0xaa & (modulo - 1);
+
+ for (cr = 1; cr <= 1; cr++) {
+
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("\nConstruct I frame, cmd=%d, ftype=%d, pid=0x%02x\n", cr, ftype, pid);
+
+ pp = ax25_i_frame (addrs, num_addr, cr, modulo, nr, ns, pf, pid, pinfo, info_len);
+
+ ax25_hex_dump (pp);
+ enc_dec_compare (pp);
+ ax25_delete (pp);
+ }
+ }
+
+} // end all_frame_types
+
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+//
+// Test bitstream tapped off from demodulator.
+//
+// 5 frames were sent to Nino TNC and a recording was made.
+// This was demodulated and the resulting bit stream saved to a file.
+//
+// No automatic test here - must be done manually with audio recording.
+//
+/////////////////////////////////////////////////////////////////////////////////////////////
+
+static int decoding_bitstream = 0;
+
+static void decode_bitstream(void)
+{
+ dw_printf("-----\nReading il2p-bitstream.txt if available...\n");
+
+ FILE *fp = fopen ("il2p-bitstream.txt", "r");
+ if (fp == NULL) {
+ dw_printf ("Bitstream test file not available.\n");
+ return;
+ }
+
+ decoding_bitstream = 1;
+ int save_previous = il2p_get_debug();
+ il2p_set_debug (1);
+
+ int ch;
+ while ( (ch = fgetc(fp)) != EOF) {
+
+ if (ch == '0' || ch == '1') {
+ il2p_rec_bit (0, 0, 0, ch - '0');
+ }
+ }
+ fclose(fp);
+ il2p_set_debug (save_previous);
+ decoding_bitstream = 0;
+
+} // end decode_bitstream
+
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+//
+// Test serialize / deserialize.
+//
+// This uses same functions used on the air.
+//
+/////////////////////////////////////////////////////////////////////////////////////////////
+
+static char addrs2[] = "AA1AAA-1>ZZ9ZZZ-9";
+static char addrs3[] = "AA1AAA-1>ZZ9ZZZ-9,DIGI*";
+static char text[] =
+ "'... As I was saying, that seems to be done right - though I haven't time to look it over thoroughly just now - and that shows that there are three hundred and sixty-four days when you might get un-birthday presents -'"
+ "\n"
+ "'Certainly,' said Alice."
+ "\n"
+ "'And only one for birthday presents, you know. There's glory for you!'"
+ "\n"
+ "'I don't know what you mean by \"glory\",' Alice said."
+ "\n"
+ "Humpty Dumpty smiled contemptuously. 'Of course you don't - till I tell you. I meant \"there's a nice knock-down argument for you!\"'"
+ "\n"
+ "'But \"glory\" doesn't mean \"a nice knock-down argument\",' Alice objected."
+ "\n"
+ "'When I use a word,' Humpty Dumpty said, in rather a scornful tone, 'it means just what I choose it to mean - neither more nor less.'"
+ "\n"
+ "'The question is,' said Alice, 'whether you can make words mean so many different things.'"
+ "\n"
+ "'The question is,' said Humpty Dumpty, 'which is to be master - that's all.'"
+ "\n" ;
+
+
+static int rec_count = -1; // disable deserialized packet test.
+static int polarity = 0;
+
+static void test_serdes (void)
+{
+ text_color_set(DW_COLOR_INFO);
+ dw_printf ("\nTest serialize / deserialize...\n");
+ rec_count = 0;
+
+ int max_fec = 1;
+
+ // try combinations of header type, max_fec, polarity, errors.
+
+ for (int hdr_type = 0; hdr_type <= 1; hdr_type++) {
+ char packet[1024];
+ snprintf (packet, sizeof(packet), "%s:%s", hdr_type ? addrs2 : addrs3, text);
+ packet_t pp = ax25_from_text (packet, 1);
+ assert (pp != NULL);
+
+ int chan = 0;
+
+
+ for (max_fec = 0; max_fec <= 1; max_fec++) {
+ for (polarity = 0; polarity <= 2; polarity++) { // 2 means throw in some errors.
+ int num_bits_sent = il2p_send_frame (chan, pp, max_fec, polarity);
+ dw_printf ("%d bits sent.\n", num_bits_sent);
+
+ // Need extra bit at end to flush out state machine.
+ il2p_rec_bit (0, 0, 0, 0);
+ }
+ }
+ ax25_delete(pp);
+ }
+
+ dw_printf ("Serdes receive count = %d\n", rec_count);
+ assert (rec_count == 12);
+ rec_count = -1; // disable deserialized packet test.
+}
+
+
+// Serializing calls this which then simulates the demodulator output.
+
+void tone_gen_put_bit (int chan, int data)
+{
+ il2p_rec_bit (chan, 0, 0, data);
+}
+
+// This is called when a complete frame has been deserialized.
+
+void multi_modem_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, int is_fx25)
+{
+ if (rec_count < 0) return; // Skip check before serdes test.
+
+ rec_count++;
+
+ // Does it have the the expected content?
+
+ unsigned char *pinfo;
+ int len = ax25_get_info(pp, &pinfo);
+ assert (len == strlen(text));
+ assert (strcmp(text, (char*)pinfo) == 0);
+
+ dw_printf ("Number of symbols corrected: %d\n", retries);
+ if (polarity == 2) { // expecting errors corrected.
+ assert (retries == 10);
+ }
+ else { // should be no errors.
+ assert (retries == 0);
+ }
+
+ ax25_delete (pp);
+}
+
+alevel_t demod_get_audio_level (int chan, int subchan)
+{
+ alevel_t alevel;
+ memset (&alevel, 0, sizeof(alevel));
+ return (alevel);
+}
+
+// end il2p_test.c
\ No newline at end of file
diff --git a/src/kiss.c b/src/kiss.c
index 76cb322d..f93cb94c 100644
--- a/src/kiss.c
+++ b/src/kiss.c
@@ -94,7 +94,7 @@ void kisspt_set_debug (int n)
return;
}
-void kisspt_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int client)
+void kisspt_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, struct kissport_status_s *kps, int client)
{
return;
}
@@ -374,7 +374,7 @@ static int kisspt_open_pt (void)
* flen - Length of raw received frame not including the FCS
* or -1 for a text string.
*
- * client - Not used for pseudo terminal.
+ * kps, client - Not used for pseudo terminal.
* Here so that 3 related functions all have
* the same parameter list.
*
@@ -385,7 +385,7 @@ static int kisspt_open_pt (void)
*--------------------------------------------------------------------*/
-void kisspt_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int client)
+void kisspt_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, struct kissport_status_s *kps, int client)
{
unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN + 2];
int kiss_len;
@@ -465,11 +465,11 @@ void kisspt_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int f
*
* Returns: one byte (value 0 - 255) or terminate thread on error.
*
- * Description: There is room for improvment here. Reading one byte
+ * Description: There is room for improvement here. Reading one byte
* at a time is inefficient. We could read a large block
* into a local buffer and return a byte from that most of the time.
* Is it worth the effort? I don't know. With GHz processors and
- * the low data rate here it might not make a noticable difference.
+ * the low data rate here it might not make a noticeable difference.
*
*--------------------------------------------------------------------*/
@@ -591,7 +591,7 @@ static void * kisspt_listen_thread (void *arg)
while (1) {
ch = kisspt_get();
- kiss_rec_byte (&kf, ch, kisspt_debug, -1, kisspt_send_rec_packet);
+ kiss_rec_byte (&kf, ch, kisspt_debug, NULL, -1, kisspt_send_rec_packet);
}
return (void *) 0; /* Unreachable but avoids compiler warning. */
diff --git a/src/kiss.h b/src/kiss.h
index 47874405..1dc40daf 100644
--- a/src/kiss.h
+++ b/src/kiss.h
@@ -10,12 +10,13 @@
#include "config.h"
-
+#include "kiss_frame.h" // for struct kissport_status_s
void kisspt_init (struct misc_config_s *misc_config);
-void kisspt_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int client);
+void kisspt_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen,
+ struct kissport_status_s *notused1, int notused2);
void kisspt_set_debug (int n);
diff --git a/src/kiss_frame.c b/src/kiss_frame.c
index c0876357..862fb714 100644
--- a/src/kiss_frame.c
+++ b/src/kiss_frame.c
@@ -64,7 +64,7 @@
* _6 SetHardware TNC specific.
*
* _C XKISS extension - not supported.
- * _E XKISS extention - not supported.
+ * _E XKISS extension - not supported.
*
* FF Return Exit KISS mode. Ignored.
*
@@ -107,6 +107,7 @@ void hex_dump (unsigned char *p, int len)
offset = 0;
while (len > 0) {
n = len < 16 ? len : 16;
+ // FIXME: Is there some reason not to use dw_printf here?
printf (" %03x: ", offset);
for (i=0; inoise)) == 0 ||
strcasecmp("reset\r", (char*)(kf->noise)) == 0) {
// first 2 parameters don't matter when length is -1 indicating text.
- (*sendfun) (0, 0, (unsigned char *)"\xc0\xc0", -1, client);
+ (*sendfun) (0, 0, (unsigned char *)"\xc0\xc0", -1, kps, client);
}
else {
- (*sendfun) (0, 0, (unsigned char *)"\r\ncmd:", -1, client);
+ (*sendfun) (0, 0, (unsigned char *)"\r\ncmd:", -1, kps, client);
}
#endif
kf->noise_len = 0;
@@ -469,7 +475,7 @@ void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, v
hex_dump (unwrapped+1, ulen-1);
}
- kiss_process_msg (unwrapped, ulen, debug, client, sendfun);
+ kiss_process_msg (unwrapped, ulen, debug, kps, client, sendfun);
kf->state = KS_SEARCHING;
return;
@@ -506,6 +512,9 @@ void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, v
*
* debug - Debug option is selected.
*
+ * kps - Used only for TCP KISS.
+ * Should be NULL for pseudo terminal and serial port.
+ *
* client - Client app number for TCP KISS.
* Should be -1 for pseudo termal and serial port.
*
@@ -519,28 +528,40 @@ void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, v
// Some functions are only for the TNC end.
// Other functions are suitble for both TNC and client app.
-// This is used only by the TNC sided.
+// This is used only by the TNC side.
-void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int))
+void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, struct kissport_status_s *kps, int client,
+ void (*sendfun)(int chan, int kiss_cmd, unsigned char *fbuf, int flen, struct kissport_status_s *kps, int client))
{
- int port; // Should rename to chan because that's what we use everywhere else.
+ int chan;
int cmd;
- packet_t pp;
alevel_t alevel;
- port = (kiss_msg[0] >> 4) & 0xf;
+// New in 1.7:
+// We can have KISS TCP ports which convey only a single radio channel.
+// This is to allow operation by applications which only know how to talk to single radio TNCs.
+
+ if (kps != NULL && kps->chan != -1) {
+ // Ignore channel from KISS and substitute radio channel for that KISS TCP port.
+ chan = kps->chan;
+ }
+ else {
+ // Normal case of getting radio channel from the KISS frame.
+ chan = (kiss_msg[0] >> 4) & 0xf;
+ }
cmd = kiss_msg[0] & 0xf;
switch (cmd)
{
case KISS_CMD_DATA_FRAME: /* 0 = Data Frame */
- if (client >= 0) {
- kissnet_copy (kiss_msg, kiss_len, port, cmd, client);
- }
+ // kissnet_copy clobbers first byte but we don't care
+ // because we have already determined channel and command.
+
+ kissnet_copy (kiss_msg, kiss_len, chan, cmd, kps, client);
/* Note July 2017: There is a variant of of KISS, called SMACK, that assumes */
- /* a TNC can never have more than 8 ports. http://symek.de/g/smack.html */
+ /* a TNC can never have more than 8 channels. http://symek.de/g/smack.html */
/* It uses the MSB to indicate that a checksum is added. I wonder if this */
/* is why we sometimes hear about a request to transmit on channel 8. */
/* Should we have a message that asks the user if SMACK is being used, */
@@ -580,20 +601,22 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int cli
// Would it make sense to implement SMACK? I don't think so.
// Adding a checksum to the KISS data offers no benefit because it is very reliable.
-// It violates the original protocol specification which states that 16 ports (radio channels) are possible.
+// It violates the original protocol specification which states that 16 radio channels are possible.
+// (Some times the term 'port' is used but I try to use 'channel' all the time because 'port'
+// has too many other meanings. Serial port, TCP port, ...)
// SMACK imposes a limit of 8. That limit might have been OK back in 1991 but not now.
// There are people using more than 8 radio channels (using SDR not traditional radios) with direwolf.
- /* Verify that the port (channel) number is valid. */
+ /* Verify that the radio channel number is valid. */
/* Any sort of medium should be OK here. */
- if (port < 0 || port >= MAX_CHANS || save_audio_config_p->achan[port].medium == MEDIUM_NONE) {
+ if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->chan_medium[chan] == MEDIUM_NONE) {
text_color_set(DW_COLOR_ERROR);
- dw_printf ("Invalid transmit channel %d from KISS client app.\n", port);
+ dw_printf ("Invalid transmit channel %d from KISS client app.\n", chan);
dw_printf ("\n");
dw_printf ("Are you using AX.25 for Linux? It might be trying to use a modified\n");
- dw_printf ("version of KISS which uses the port (channel) field differently than the\n");
+ dw_printf ("version of KISS which uses the channel field differently than the\n");
dw_printf ("original KISS protocol specification. The solution might be to use\n");
dw_printf ("a command like \"kissparms -c 1 -p radio\" to set CRC none mode.\n");
dw_printf ("Another way of doing this is pre-loading the \"kiss\" kernel module with CRC disabled:\n");
@@ -606,7 +629,7 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int cli
}
memset (&alevel, 0xff, sizeof(alevel));
- pp = ax25_from_frame (kiss_msg+1, kiss_len-1, alevel);
+ packet_t pp = ax25_from_frame (kiss_msg+1, kiss_len-1, alevel);
if (pp == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - Invalid KISS data frame from client app.\n");
@@ -621,10 +644,10 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int cli
if (ax25_get_num_repeaters(pp) >= 1 &&
ax25_get_h(pp,AX25_REPEATER_1)) {
- tq_append (port, TQ_PRIO_0_HI, pp);
+ tq_append (chan, TQ_PRIO_0_HI, pp);
}
else {
- tq_append (port, TQ_PRIO_1_LO, pp);
+ tq_append (chan, TQ_PRIO_1_LO, pp);
}
}
break;
@@ -637,13 +660,13 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int cli
return;
}
text_color_set(DW_COLOR_INFO);
- dw_printf ("KISS protocol set TXDELAY = %d (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, port);
+ dw_printf ("KISS protocol set TXDELAY = %d (*10mS units = %d mS), chan %d\n", kiss_msg[1], kiss_msg[1] * 10, chan);
if (kiss_msg[1] < 4 || kiss_msg[1] > 100) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Are you sure you want such an extreme value for TXDELAY?\n");
dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n");
}
- xmit_set_txdelay (port, kiss_msg[1]);
+ xmit_set_txdelay (chan, kiss_msg[1]);
break;
case KISS_CMD_PERSISTENCE: /* 2 = Persistence */
@@ -654,13 +677,13 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int cli
return;
}
text_color_set(DW_COLOR_INFO);
- dw_printf ("KISS protocol set Persistence = %d, port %d\n", kiss_msg[1], port);
+ dw_printf ("KISS protocol set Persistence = %d, chan %d\n", kiss_msg[1], chan);
if (kiss_msg[1] < 5 || kiss_msg[1] > 250) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Are you sure you want such an extreme value for PERSIST?\n");
dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n");
}
- xmit_set_persist (port, kiss_msg[1]);
+ xmit_set_persist (chan, kiss_msg[1]);
break;
case KISS_CMD_SLOTTIME: /* 3 = SlotTime */
@@ -671,13 +694,13 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int cli
return;
}
text_color_set(DW_COLOR_INFO);
- dw_printf ("KISS protocol set SlotTime = %d (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, port);
+ dw_printf ("KISS protocol set SlotTime = %d (*10mS units = %d mS), chan %d\n", kiss_msg[1], kiss_msg[1] * 10, chan);
if (kiss_msg[1] < 2 || kiss_msg[1] > 50) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Are you sure you want such an extreme value for SLOTTIME?\n");
dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n");
}
- xmit_set_slottime (port, kiss_msg[1]);
+ xmit_set_slottime (chan, kiss_msg[1]);
break;
case KISS_CMD_TXTAIL: /* 4 = TXtail */
@@ -688,13 +711,13 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int cli
return;
}
text_color_set(DW_COLOR_INFO);
- dw_printf ("KISS protocol set TXtail = %d (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, port);
+ dw_printf ("KISS protocol set TXtail = %d (*10mS units = %d mS), chan %d\n", kiss_msg[1], kiss_msg[1] * 10, chan);
if (kiss_msg[1] < 2) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Setting TXTAIL so low is asking for trouble. You probably don't want to do this.\n");
dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n");
}
- xmit_set_txtail (port, kiss_msg[1]);
+ xmit_set_txtail (chan, kiss_msg[1]);
break;
case KISS_CMD_FULLDUPLEX: /* 5 = FullDuplex */
@@ -705,8 +728,8 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int cli
return;
}
text_color_set(DW_COLOR_INFO);
- dw_printf ("KISS protocol set FullDuplex = %d, port %d\n", kiss_msg[1], port);
- xmit_set_fulldup (port, kiss_msg[1]);
+ dw_printf ("KISS protocol set FullDuplex = %d, chan %d\n", kiss_msg[1], chan);
+ xmit_set_fulldup (chan, kiss_msg[1]);
break;
case KISS_CMD_SET_HARDWARE: /* 6 = TNC specific */
@@ -718,11 +741,11 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int cli
}
kiss_msg[kiss_len] = '\0';
text_color_set(DW_COLOR_INFO);
- dw_printf ("KISS protocol set hardware \"%s\", port %d\n", (char*)(kiss_msg+1), port);
- kiss_set_hardware (port, (char*)(kiss_msg+1), debug, client, sendfun);
+ dw_printf ("KISS protocol set hardware \"%s\", chan %d\n", (char*)(kiss_msg+1), chan);
+ kiss_set_hardware (chan, (char*)(kiss_msg+1), debug, kps, client, sendfun);
break;
- case KISS_CMD_END_KISS: /* 15 = End KISS mode, port should be 15. */
+ case KISS_CMD_END_KISS: /* 15 = End KISS mode, channel should be 15. */
/* Ignore it. */
text_color_set(DW_COLOR_INFO);
dw_printf ("KISS protocol end KISS mode - Ignored.\n");
@@ -842,7 +865,8 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int cli
#ifndef KISSUTIL
-static void kiss_set_hardware (int chan, char *command, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int))
+static void kiss_set_hardware (int chan, char *command, int debug, struct kissport_status_s *kps, int client,
+ void (*sendfun)(int chan, int kiss_cmd, unsigned char *fbuf, int flen, struct kissport_status_s *onlykps, int onlyclient))
{
char *param;
char response[100];
@@ -860,7 +884,7 @@ static void kiss_set_hardware (int chan, char *command, int debug, int client, v
}
snprintf (response, sizeof(response), "DIREWOLF %d.%d", MAJOR_VERSION, MINOR_VERSION);
- (*sendfun) (chan, KISS_CMD_SET_HARDWARE, (unsigned char *)response, strlen(response), client);
+ (*sendfun) (chan, KISS_CMD_SET_HARDWARE, (unsigned char *)response, strlen(response), kps, client);
}
else if (strcmp(command, "TXBUF") == 0) { /* TXBUF - Number of bytes in transmit queue. */
@@ -872,7 +896,7 @@ static void kiss_set_hardware (int chan, char *command, int debug, int client, v
int n = tq_count (chan, -1, "", "", 1);
snprintf (response, sizeof(response), "TXBUF:%d", n);
- (*sendfun) (chan, KISS_CMD_SET_HARDWARE, (unsigned char *)response, strlen(response), client);
+ (*sendfun) (chan, KISS_CMD_SET_HARDWARE, (unsigned char *)response, strlen(response), kps, client);
}
else {
diff --git a/src/kiss_frame.h b/src/kiss_frame.h
index 0bddf3ec..47f3dfe5 100644
--- a/src/kiss_frame.h
+++ b/src/kiss_frame.h
@@ -1,6 +1,10 @@
/* kiss_frame.h */
+#ifndef KISS_FRAME_H
+#define KISS_FRAME_H
+
+
#include "audio.h" /* for struct audio_s */
@@ -41,7 +45,7 @@ enum kiss_state_e {
#define MAX_KISS_LEN 2048 /* Spec calls for at least 1024. */
- /* Might want to make it longer to accomodate */
+ /* Might want to make it longer to accommodate */
/* maximum packet length. */
#define MAX_NOISE_LEN 100
@@ -61,6 +65,40 @@ typedef struct kiss_frame_s {
} kiss_frame_t;
+// This is used only for TCPKISS but it put in kissnet.h,
+// there would be a circular dependency between the two header files.
+// Each KISS TCP port has its own status block.
+
+struct kissport_status_s {
+
+ struct kissport_status_s *pnext; // To next in list.
+
+ volatile int arg2; // temp for passing second arg into
+ // kissnet_listen_thread
+
+ int tcp_port; // default 8001
+
+ int chan; // Radio channel for this tcp port.
+ // -1 for all.
+
+ // The default is a limit of 3 client applications at the same time.
+ // You can increase the limit by changing the line below.
+ // A larger number consumes more resources so don't go crazy by making it larger than needed.
+
+#define MAX_NET_CLIENTS 3
+
+ int client_sock[MAX_NET_CLIENTS];
+ /* File descriptor for socket for */
+ /* communication with client application. */
+ /* Set to -1 if not connected. */
+ /* (Don't use SOCKET type because it is unsigned.) */
+
+ kiss_frame_t kf[MAX_NET_CLIENTS];
+ /* Accumulated KISS frame and state of decoder. */
+};
+
+
+
#ifndef KISSUTIL
void kiss_frame_init (struct audio_s *pa);
#endif
@@ -69,12 +107,18 @@ int kiss_encapsulate (unsigned char *in, int ilen, unsigned char *out);
int kiss_unwrap (unsigned char *in, int ilen, unsigned char *out);
-void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int));
+void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, struct kissport_status_s *kps, int client,
+ void (*sendfun)(int chan, int kiss_cmd, unsigned char *fbuf, int flen, struct kissport_status_s *onlykps, int onlyclient));
typedef enum fromto_e { FROM_CLIENT=0, TO_CLIENT=1 } fromto_t;
-void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int));
+void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, struct kissport_status_s *kps, int client,
+ void (*sendfun)(int chan, int kiss_cmd, unsigned char *fbuf, int flen, struct kissport_status_s *onlykps, int onlyclient));
void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len);
+
+#endif // KISS_FRAME_H
+
+
/* end kiss_frame.h */
diff --git a/src/kissnet.c b/src/kissnet.c
index 7b4f1c4f..97094a08 100644
--- a/src/kissnet.c
+++ b/src/kissnet.c
@@ -1,7 +1,6 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
-//
-// Copyright (C) 2011-2014, 2015, 2017 John Langner, WB2OSZ
+// Copyright (C) 2011-2014, 2015, 2017, 2021 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -87,6 +86,83 @@
*
*---------------------------------------------------------------*/
+/*
+ Separate TCP ports per radio:
+
+An increasing number of people are using multiple radios.
+direwolf is capable of handling many radio channels and
+provides cross-band repeating, etc.
+Maybe a single stereo audio interface is used for 2 radios.
+
+ +------------+ tcp 8001, all channels
+Radio A -------- | | -------------------------- Application A
+ | direwolf |
+Radio B -------- | | -------------------------- Application B
+ +------------+ tcp 8001, all channels
+
+The KISS protocol has a 4 bit field for the TNC port (which I prefer to
+call channel because port has too many different meanings).
+direwolf handles this fine. However, most applications were written assuming
+that a TNC could only talk to a single radio. On reception, they ignore the
+channel in the KISS frame. For transmit, the channel is always set to 0.
+
+Many people are using the work-around of two separate instances of direwolf.
+
+ +------------+ tcp 8001, KISS ch 0
+Radio A -------- | direwolf | -------------------------- Application A
+ +------------+
+
+ +------------+ tcp 8002, KISS ch 0
+Radio B -------- | direwolf | -------------------------- Application B
+ +------------+
+
+
+Or they might be using a single application that knows how to talk to multiple
+single port TNCs. But they don't know how to multiplex multiple channels
+thru a single KISS stream.
+
+ +------------+ tcp 8001, KISS ch 0
+Radio A -------- | direwolf | ------------------------
+ +------------+ \
+ -- Application
+ +------------+ tcp 8002, KISS ch 0 /
+Radio B -------- | direwolf | ------------------------
+ +------------+
+
+Using two different instances of direwolf means more complex configuration
+and loss of cross-channel digipeating. It is possible to use a stereo
+audio interface but some ALSA magic is required to make it look like two
+independent virtual mono interfaces.
+
+In version 1.7, we add the capability of multiple KISS TCP ports, each for
+a single radio channel. e.g.
+
+KISSPORT 8001 1
+KISSPORT 8002 2
+
+Now can use a single instance of direwolf.
+
+
+ +------------+ tcp 8001, KISS ch 0
+Radio A -------- | | -------------------------- Application A
+ | direwolf |
+Radio B -------- | | -------------------------- Application B
+ +------------+ tcp 8002, KISS ch 0
+
+When receiving, the KISS channel is set to 0.
+ - only radio channel 1 would be sent over tcp port 8001.
+ - only radio channel 2 would be sent over tcp port 8001.
+
+When transmitting, the KISS channel is ignored.
+ - frames from tcp port 8001 are transmitted on radio channel 1.
+ - frames from tcp port 8002 are transmitted on radio channel 2.
+
+Of course, you could also use an application, capable of connecting to
+multiple single radio TNCs. Separate TCP ports actually go to the
+same direwolf instance.
+
+*/
+
/*
* Native Windows: Use the Winsock interface.
@@ -127,25 +203,6 @@
void hex_dump (unsigned char *p, int len); // This should be in a .h file.
-/*
- * Early on we allowed one AGW connection and one KISS TCP connection at a time.
- * In version 1.1, we allowed multiple concurrent client apps to attach with the AGW network protocol.
- * In Version 1.5, we do essentially the same here to allow multiple concurrent KISS TCP clients.
- * The default is a limit of 3 client applications at the same time.
- * You can increase the limit by changing the line below.
- * A larger number consumes more resources so don't go crazy by making it larger than needed.
- */
-
-#define MAX_NET_CLIENTS 3
-
-static int client_sock[MAX_NET_CLIENTS];
- /* File descriptor for socket for */
- /* communication with client application. */
- /* Set to -1 if not connected. */
- /* (Don't use SOCKET type because it is unsigned.) */
-
-static kiss_frame_t kf[MAX_NET_CLIENTS];
- /* Accumulated KISS frame and state of decoder. */
@@ -162,6 +219,12 @@ static THREAD_F kissnet_listen_thread (void *arg);
static struct misc_config_s *s_misc_config_p;
+
+// Each TCP port has its own status block.
+// There is a variable number so use a linked list.
+
+static struct kissport_status_s *all_ports = NULL;
+
static int kiss_debug = 0; /* Print information flowing from and to client. */
void kiss_net_set_debug (int n)
@@ -177,11 +240,9 @@ void kiss_net_set_debug (int n)
*
* Purpose: Set up a server to listen for connection requests from
* an application such as Xastir or APRSIS32.
+ * This is called once from the main program.
*
* Inputs: mc->kiss_port - TCP port for server.
- * Main program has default of 8000 but allows
- * an alternative to be specified on the command line
- *
* 0 means disable. New in version 1.2.
*
* Outputs:
@@ -193,8 +254,34 @@ void kiss_net_set_debug (int n)
*
*--------------------------------------------------------------------*/
+static void kissnet_init_one (struct kissport_status_s *kps);
void kissnet_init (struct misc_config_s *mc)
+{
+ s_misc_config_p = mc;
+
+ for (int i = 0; i < MAX_KISS_TCP_PORTS; i++) {
+ if (mc->kiss_port[i] != 0) {
+ struct kissport_status_s *kps = calloc(sizeof(struct kissport_status_s), 1);
+ if (kps == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
+ }
+
+ kps->tcp_port = mc->kiss_port[i];
+ kps->chan = mc->kiss_chan[i];
+ kissnet_init_one (kps);
+
+ // Add to list.
+ kps->pnext = all_ports;
+ all_ports = kps;
+ }
+ }
+}
+
+
+static void kissnet_init_one (struct kissport_status_s *kps)
{
int client;
@@ -206,23 +293,22 @@ void kissnet_init (struct misc_config_s *mc)
pthread_t cmd_listen_tid[MAX_NET_CLIENTS];
int e;
#endif
- s_misc_config_p = mc;
- int kiss_port = mc->kiss_port; /* default 8001 but easily changed. */
+
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
- dw_printf ("kissnet_init ( %d )\n", kiss_port);
+ dw_printf ("kissnet_init ( tcp port %d, radio chan = %d )\n", kps->tcp_port, kps->chan);
#endif
for (client=0; clientclient_sock[client] = -1;
+ memset (&(kps->kf[client]), 0, sizeof(kps->kf[client]));
}
- if (kiss_port == 0) {
+ if (kps->tcp_port == 0) {
text_color_set(DW_COLOR_INFO);
dw_printf ("Disabled KISS network client port.\n");
return;
@@ -232,17 +318,18 @@ void kissnet_init (struct misc_config_s *mc)
* This waits for a client to connect and sets client_sock[n].
*/
#if __WIN32__
- connect_listen_th = (HANDLE)_beginthreadex (NULL, 0, connect_listen_thread, (void *)(ptrdiff_t)kiss_port, 0, NULL);
+ connect_listen_th = (HANDLE)_beginthreadex (NULL, 0, connect_listen_thread, (void *)kps, 0, NULL);
if (connect_listen_th == NULL) {
text_color_set(DW_COLOR_ERROR);
- dw_printf ("Could not create KISS socket connect listening thread\n");
+ dw_printf ("Could not create KISS socket connect listening thread for tcp port %d, radio chan %d\n", kps->tcp_port, kps->chan);
return;
}
#else
- e = pthread_create (&connect_listen_tid, NULL, connect_listen_thread, (void *)(ptrdiff_t)kiss_port);
+ e = pthread_create (&connect_listen_tid, NULL, connect_listen_thread, (void *)kps);
if (e != 0) {
text_color_set(DW_COLOR_ERROR);
perror("Could not create KISS socket connect listening thread");
+ dw_printf ("for tcp port %d, radio chan %d\n", kps->tcp_port, kps->chan);
return;
}
#endif
@@ -254,15 +341,17 @@ void kissnet_init (struct misc_config_s *mc)
*/
for (client = 0; client < MAX_NET_CLIENTS; client++) {
+ kps->arg2 = client;
+
#if __WIN32__
- cmd_listen_th[client] = (HANDLE)_beginthreadex (NULL, 0, kissnet_listen_thread, (void*)(ptrdiff_t)client, 0, NULL);
+ cmd_listen_th[client] = (HANDLE)_beginthreadex (NULL, 0, kissnet_listen_thread, (void*)kps, 0, NULL);
if (cmd_listen_th[client] == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not create KISS command listening thread for client %d\n", client);
return;
}
#else
- e = pthread_create (&(cmd_listen_tid[client]), NULL, kissnet_listen_thread, (void *)(ptrdiff_t)client);
+ e = pthread_create (&(cmd_listen_tid[client]), NULL, kissnet_listen_thread, (void *)kps);
if (e != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not create KISS command listening thread for client %d\n", client);
@@ -271,6 +360,18 @@ void kissnet_init (struct misc_config_s *mc)
return;
}
#endif
+ // Wait for new thread to get content of arg2 before reusing it for the next thread create.
+
+ int timer = 0;
+ while (kps->arg2 >= 0) {
+ SLEEP_MS(10);
+ timer++;
+ if (timer > 100) { // 1 second - thread did not start
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("KISS data listening thread did not start for tcp port %d, client slot %d\n", kps->tcp_port, client);
+ kps->arg2 = -1; // Keep moving along.
+ }
+ }
}
}
@@ -281,9 +382,7 @@ void kissnet_init (struct misc_config_s *mc)
*
* Purpose: Wait for a connection request from an application.
*
- * Inputs: arg - TCP port for server.
- * Main program has default of 8001 but allows
- * an alternative to be specified on the command line
+ * Inputs: arg - KISS port status block.
*
* Outputs: client_sock - File descriptor for communicating with client app.
*
@@ -296,20 +395,22 @@ void kissnet_init (struct misc_config_s *mc)
static THREAD_F connect_listen_thread (void *arg)
{
+ struct kissport_status_s *kps = arg;
+
#if __WIN32__
struct addrinfo hints;
struct addrinfo *ai = NULL;
int err;
- char kiss_port_str[12];
+ char tcp_port_str[12];
SOCKET listen_sock;
WSADATA wsadata;
- snprintf (kiss_port_str, sizeof(kiss_port_str), "%d", (int)(ptrdiff_t)arg);
+ snprintf (tcp_port_str, sizeof(tcp_port_str), "%d", kps->tcp_port);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
- dw_printf ("DEBUG: kissnet port = %d = '%s'\n", (int)(ptrdiff_t)arg, kiss_port_str);
+ dw_printf ("DEBUG: kissnet port = %d = '%s'\n", (int)(ptrdiff_t)arg, tcp_port_str);
#endif
err = WSAStartup (MAKEWORD(2,2), &wsadata);
if (err != 0) {
@@ -332,7 +433,7 @@ static THREAD_F connect_listen_thread (void *arg)
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
- err = getaddrinfo(NULL, kiss_port_str, &hints, &ai);
+ err = getaddrinfo(NULL, tcp_port_str, &hints, &ai);
if (err != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf("getaddrinfo failed: %d\n", err);
@@ -350,14 +451,14 @@ static THREAD_F connect_listen_thread (void *arg)
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
- dw_printf("Binding to port %s ... \n", kiss_port_str);
+ dw_printf("Binding to port %s ... \n", tcp_port_str);
#endif
err = bind( listen_sock, ai->ai_addr, (int)ai->ai_addrlen);
if (err == SOCKET_ERROR) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Bind failed with error: %d\n", WSAGetLastError()); // TODO: provide corresponding text.
- dw_printf("Some other application is probably already using port %s.\n", kiss_port_str);
+ dw_printf("Some other application is probably already using port %s.\n", tcp_port_str);
dw_printf("Try using a different port number with KISSPORT in the configuration file.\n");
freeaddrinfo(ai);
closesocket(listen_sock);
@@ -369,7 +470,7 @@ static THREAD_F connect_listen_thread (void *arg)
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
- dw_printf("opened KISS socket as fd (%d) on port (%s) for stream i/o\n", listen_sock, kiss_port_str );
+ dw_printf("opened KISS socket as fd (%d) on port (%s) for stream i/o\n", listen_sock, tcp_port_str );
#endif
while (1) {
@@ -379,7 +480,7 @@ static THREAD_F connect_listen_thread (void *arg)
client = -1;
for (c = 0; c < MAX_NET_CLIENTS && client < 0; c++) {
- if (client_sock[c] <= 0) {
+ if (kps->client_sock[c] <= 0) {
client = c;
}
}
@@ -397,11 +498,16 @@ static THREAD_F connect_listen_thread (void *arg)
}
text_color_set(DW_COLOR_INFO);
- dw_printf("Ready to accept KISS TCP client application %d on port %s ...\n", client, kiss_port_str);
-
- client_sock[client] = accept(listen_sock, NULL, NULL);
+ if (kps->chan == -1) {
+ dw_printf("Ready to accept KISS TCP client application %d on port %s ...\n", client, tcp_port_str);
+ }
+ else {
+ dw_printf("Ready to accept KISS TCP client application %d on port %s (radio channel %d) ...\n", client, tcp_port_str, kps->chan);
+ }
- if (client_sock[client] == -1) {
+ kps->client_sock[client] = accept(listen_sock, NULL, NULL);
+
+ if (kps->client_sock[client] == -1) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Accept failed with error: %d\n", WSAGetLastError());
closesocket(listen_sock);
@@ -410,10 +516,15 @@ static THREAD_F connect_listen_thread (void *arg)
}
text_color_set(DW_COLOR_INFO);
- dw_printf("\nAttached to KISS TCP client application %d ...\n\n", client);
+ if (kps->chan == -1) {
+ dw_printf("\nAttached to KISS TCP client application %d on port %s ...\n\n", client, tcp_port_str);
+ }
+ else {
+ dw_printf("\nAttached to KISS TCP client application %d on port %s (radio channel %d) ...\n\n", client, tcp_port_str, kps->chan);
+ }
// Reset the state and buffer.
- memset (&(kf[client]), 0, sizeof(kf[client]));
+ memset (&(kps->kf[client]), 0, sizeof(kps->kf[client]));
}
else {
SLEEP_SEC(1); /* wait then check again if more clients allowed. */
@@ -421,12 +532,11 @@ static THREAD_F connect_listen_thread (void *arg)
}
-#else /* End of Windows case, now Linux. */
+#else /* End of Windows case, now Linux / Unix / Mac OSX. */
- struct sockaddr_in sockaddr; /* Internet socket address stuct */
+ struct sockaddr_in sockaddr; /* Internet socket address struct */
socklen_t sockaddr_size = sizeof(struct sockaddr_in);
- int kiss_port = (int)(ptrdiff_t)arg;
int listen_sock;
int bcopt = 1;
@@ -446,19 +556,19 @@ static THREAD_F connect_listen_thread (void *arg)
setsockopt (listen_sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&bcopt, 4);
sockaddr.sin_addr.s_addr = INADDR_ANY;
- sockaddr.sin_port = htons(kiss_port);
+ sockaddr.sin_port = htons(kps->tcp_port);
sockaddr.sin_family = AF_INET;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
- dw_printf("Binding to port %d ... \n", kiss_port);
+ dw_printf("Binding to port %d ... \n", kps->tcp_port);
#endif
if (bind(listen_sock,(struct sockaddr*)&sockaddr,sizeof(sockaddr)) == -1) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Bind failed with error: %d\n", errno);
dw_printf("%s\n", strerror(errno));
- dw_printf("Some other application is probably already using port %d.\n", kiss_port);
+ dw_printf("Some other application is probably already using port %d.\n", kps->tcp_port);
dw_printf("Try using a different port number with KISSPORT in the configuration file.\n");
return (NULL);
}
@@ -477,7 +587,7 @@ static THREAD_F connect_listen_thread (void *arg)
client = -1;
for (c = 0; c < MAX_NET_CLIENTS && client < 0; c++) {
- if (client_sock[c] <= 0) {
+ if (kps->client_sock[c] <= 0) {
client = c;
}
}
@@ -492,15 +602,25 @@ static THREAD_F connect_listen_thread (void *arg)
}
text_color_set(DW_COLOR_INFO);
- dw_printf("Ready to accept KISS TCP client application %d on port %d ...\n", client, kiss_port);
-
- client_sock[client] = accept(listen_sock, (struct sockaddr*)(&sockaddr),&sockaddr_size);
+ if (kps->chan == -1) {
+ dw_printf("Ready to accept KISS TCP client application %d on port %d ...\n", client, kps->tcp_port);
+ }
+ else {
+ dw_printf("Ready to accept KISS TCP client application %d on port %d (radio channel %d) ...\n", client, kps->tcp_port, kps->chan);
+ }
+
+ kps->client_sock[client] = accept(listen_sock, (struct sockaddr*)(&sockaddr),&sockaddr_size);
text_color_set(DW_COLOR_INFO);
- dw_printf("\nAttached to KISS TCP client application %d...\n\n", client);
+ if (kps->chan == -1) {
+ dw_printf("\nAttached to KISS TCP client application %d on port %d ...\n\n", client, kps->tcp_port);
+ }
+ else {
+ dw_printf("\nAttached to KISS TCP client application %d on port %d (radio channel %d) ...\n\n", client, kps->tcp_port, kps->chan);
+ }
// Reset the state and buffer.
- memset (&(kf[client]), 0, sizeof(kf[client]));
+ memset (&(kps->kf[client]), 0, sizeof(kps->kf[client]));
}
else {
SLEEP_SEC(1); /* wait then check again if more clients allowed. */
@@ -517,7 +637,7 @@ static THREAD_F connect_listen_thread (void *arg)
*
* Name: kissnet_send_rec_packet
*
- * Purpose: Send a received packet to the client app.
+ * Purpose: Send a packet, received over the radio, to the client app.
*
* Inputs: chan - Channel number where packet was received.
* 0 = first, 1 = second if any.
@@ -537,119 +657,131 @@ static THREAD_F connect_listen_thread (void *arg)
* it is using a traditional TNC and tries to put it
* into KISS mode.
*
- * tcpclient - It is possible to have more than client attached
+ * onlykps - KISS TCP status block pointer or NULL.
+ *
+ * onlyclient - It is possible to have more than client attached
* at the same time with TCP KISS.
- * When a frame is received from the radio we want it
- * to go to all of the clients. In this case specify -1.
+ * Starting with version 1.7 we can have multiple TCP ports.
+ * When a frame is received from the radio we normally want it
+ * to go to all of the clients.
+ * In this case specify NULL for onlykps and -1 tcp client.
* When responding to a command from the client, we want
* to send only to that one client app. In this case
- * use the value 0 .. MAX_NET_CLIENTS-1.
+ * a non NULL kps and onlyclient >= 0.
*
* Description: Send message to client(s) if connected.
* Disconnect from client, and notify user, if any error.
*
*--------------------------------------------------------------------*/
-
-void kissnet_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int tcpclient)
+void kissnet_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen,
+ struct kissport_status_s *onlykps, int onlyclient)
{
unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN];
int kiss_len;
int err;
- int first, last, client;
-// Something received over the radio would be sent to all attached clients.
+// Something received over the radio would normally be sent to all attached clients.
// However, there are times we want to send a response only to a particular client.
// In the case of a serial port or pseudo terminal, there is only one potential client.
// so the response would be sent to only one place. A new parameter has been added for this.
- if (tcpclient >= 0 && tcpclient < MAX_NET_CLIENTS) {
- first = tcpclient;
- last = tcpclient;
- }
- else if (tcpclient == -1) {
- first = 0;
- last = MAX_NET_CLIENTS - 1;
- }
- else {
- text_color_set(DW_COLOR_ERROR);
- dw_printf ("KISS TCP: Internal error, kissnet_send_rec_packet, tcpclient = %d.\n", tcpclient);
- return;
- }
+ for (struct kissport_status_s *kps = all_ports; kps != NULL; kps = kps->pnext) {
+ if (onlykps == NULL || kps == onlykps) {
- for (client = first; client <= last; client++) {
+ for (int client = 0; client < MAX_NET_CLIENTS; client++) {
- if (client_sock[client] != -1) {
+ if (onlyclient == -1 || client == onlyclient) {
- if (flen < 0) {
+ if (kps->client_sock[client] != -1) {
+
+ if (flen < 0) {
// A client app might think it is attached to a traditional TNC.
// It might try sending commands over and over again trying to get the TNC into KISS mode.
// We recognize this attempt and send it something to keep it happy.
- text_color_set(DW_COLOR_ERROR);
- dw_printf ("KISS TCP: Something unexpected from client application.\n");
- dw_printf ("Is client app treating this like an old TNC with command mode?\n");
- dw_printf ("This can be caused by the application sending commands to put a\n");
- dw_printf ("traditional TNC into KISS mode. It is usually a harmless warning.\n");
- dw_printf ("For best results, configure for a KISS-only TNC to avoid this.\n");
- dw_printf ("In the case of APRSISCE/32, use \"Simply(KISS)\" rather than \"KISS.\"\n");
-
- flen = strlen((char*)fbuf);
- if (kiss_debug) {
- kiss_debug_print (TO_CLIENT, "Fake command prompt", fbuf, flen);
- }
- strlcpy ((char *)kiss_buff, (char *)fbuf, sizeof(kiss_buff));
- kiss_len = strlen((char *)kiss_buff);
- }
- else {
- unsigned char stemp[AX25_MAX_PACKET_LEN + 1];
-
- assert (flen < (int)(sizeof(stemp)));
-
- stemp[0] = (chan << 4) | kiss_cmd;
- memcpy (stemp+1, fbuf, flen);
-
- if (kiss_debug >= 2) {
- /* AX.25 frame with the CRC removed. */
- text_color_set(DW_COLOR_DEBUG);
- dw_printf ("\n");
- dw_printf ("Packet content before adding KISS framing and any escapes:\n");
- hex_dump (fbuf, flen);
- }
-
- kiss_len = kiss_encapsulate (stemp, flen+1, kiss_buff);
-
- /* This has the escapes and the surrounding FENDs. */
-
- if (kiss_debug) {
- kiss_debug_print (TO_CLIENT, NULL, kiss_buff, kiss_len);
- }
- }
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("KISS TCP: Something unexpected from client application.\n");
+ dw_printf ("Is client app treating this like an old TNC with command mode?\n");
+ dw_printf ("This can be caused by the application sending commands to put a\n");
+ dw_printf ("traditional TNC into KISS mode. It is usually a harmless warning.\n");
+ dw_printf ("For best results, configure for a KISS-only TNC to avoid this.\n");
+ dw_printf ("In the case of APRSISCE/32, use \"Simply(KISS)\" rather than \"KISS.\"\n");
+
+ flen = strlen((char*)fbuf);
+ if (kiss_debug) {
+ kiss_debug_print (TO_CLIENT, "Fake command prompt", fbuf, flen);
+ }
+ strlcpy ((char *)kiss_buff, (char *)fbuf, sizeof(kiss_buff));
+ kiss_len = strlen((char *)kiss_buff);
+ }
+ else {
+ unsigned char stemp[AX25_MAX_PACKET_LEN + 1];
+
+ assert (flen < (int)(sizeof(stemp)));
+
+ // New in 1.7.
+ // Previously all channels were sent to everyone.
+ // We now have tcp ports which carry only a single radio channel.
+ // The application will see KISS channel 0 regardless of the radio channel.
+
+ if (kps->chan == -1) {
+ // Normal case, all channels.
+ stemp[0] = (chan << 4) | kiss_cmd;
+ }
+ else if (kps->chan == chan) {
+ // Single radio channel for this port. Application sees 0.
+ stemp[0] = (0 << 4) | kiss_cmd;
+ }
+ else {
+ // Skip it.
+ continue;
+ }
+
+ memcpy (stemp+1, fbuf, flen);
+
+ if (kiss_debug >= 2) {
+ /* AX.25 frame with the CRC removed. */
+ text_color_set(DW_COLOR_DEBUG);
+ dw_printf ("\n");
+ dw_printf ("Packet content before adding KISS framing and any escapes:\n");
+ hex_dump (fbuf, flen);
+ }
+
+ kiss_len = kiss_encapsulate (stemp, flen+1, kiss_buff);
+
+ /* This has the escapes and the surrounding FENDs. */
+
+ if (kiss_debug) {
+ kiss_debug_print (TO_CLIENT, NULL, kiss_buff, kiss_len);
+ }
+ }
#if __WIN32__
- err = SOCK_SEND(client_sock[client], (char*)kiss_buff, kiss_len);
- if (err == SOCKET_ERROR)
- {
- text_color_set(DW_COLOR_ERROR);
- dw_printf ("\nError %d sending message to KISS client %d application. Closing connection.\n\n", WSAGetLastError(), client);
- closesocket (client_sock[client]);
- client_sock[client] = -1;
- WSACleanup();
- }
+ err = SOCK_SEND(kps->client_sock[client], (char*)kiss_buff, kiss_len);
+ if (err == SOCKET_ERROR) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("\nError %d sending message to KISS client application %d on port %d. Closing connection.\n\n", WSAGetLastError(), client, kps->tcp_port);
+ closesocket (kps->client_sock[client]);
+ kps->client_sock[client] = -1;
+ WSACleanup();
+ }
#else
- err = SOCK_SEND (client_sock[client], kiss_buff, kiss_len);
- if (err <= 0)
- {
- text_color_set(DW_COLOR_ERROR);
- dw_printf ("\nError sending message to KISS client %d application. Closing connection.\n\n", client);
- close (client_sock[client]);
- client_sock[client] = -1;
- }
+ err = SOCK_SEND (kps->client_sock[client], kiss_buff, kiss_len);
+ if (err <= 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("\nError %d sending message to KISS client application %d on port %d. Closing connection.\n\n", err, client, kps->tcp_port);
+ close (kps->client_sock[client]);
+ kps->client_sock[client] = -1;
+ }
#endif
- }
- }
+ } // frame length >= 0
+ } // if all clients or the one specifie
+ } // for each client on the tcp port
+ } // if all ports or the one specified
+ } // for each tcp port
} /* end kissnet_send_rec_packet */
@@ -661,14 +793,14 @@ void kissnet_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int f
* Purpose: Send data from one network KISS client to all others.
*
* Inputs: in_msg - KISS frame data without the framing or escapes.
- * The first byte is channel (port) and command (should be data).
+ * The first byte is channel and command (should be data).
+ * Caller no longer cares this byte. We will clobber it here.
*
* in_len - Number of bytes in above.
*
- * chan - Channel. Redundant because it is also in first byte of kiss_msg.
- * Not currently used.
+ * chan - Channel. Use this instead of first byte of in_msg.
*
- * cmd - KISS command nybble. Redundant because it is in first byte.
+ * cmd - KISS command nybble.
* Should be 0 because I'm expecting this only for data.
*
* from_client - Number of network (TCP) client instance.
@@ -687,52 +819,66 @@ void kissnet_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int f
*--------------------------------------------------------------------*/
-void kissnet_copy (unsigned char *in_msg, int in_len, int chan, int cmd, int from_client)
+void kissnet_copy (unsigned char *in_msg, int in_len, int chan, int cmd, struct kissport_status_s *from_kps, int from_client)
{
unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN];
- int kiss_len;
int err;
- int send_to;
- (void) chan;
- (void) cmd;
if (s_misc_config_p->kiss_copy) {
- for (send_to = 0; send_to < MAX_NET_CLIENTS; send_to++) {
+ for (struct kissport_status_s *kps = all_ports; kps != NULL; kps = kps->pnext) {
+
+ for (int client = 0; client < MAX_NET_CLIENTS; client++) {
+
+ if ( ! ( kps == from_kps && client == from_client ) ) { // To all but origin.
- if (send_to != from_client && client_sock[send_to] != -1) {
+ if (kps->client_sock[client] != -1) {
- kiss_len = kiss_encapsulate (in_msg, in_len, kiss_buff);
+ if (kps-> chan == -1 || kps->chan == chan) {
- /* This has the escapes and the surrounding FENDs. */
+ // Two different cases here:
+ // - The TCP port allows all channels, or
+ // - The TCP port allows only one channel. In this case set KISS channel to 0.
- if (kiss_debug) {
- kiss_debug_print (TO_CLIENT, NULL, kiss_buff, kiss_len);
- }
+ if (kps->chan == -1) {
+ in_msg[0] = (chan << 4) | cmd;
+ }
+ else {
+ in_msg[0] = 0 | cmd; // set channel to zero.
+ }
+
+ int kiss_len = kiss_encapsulate (in_msg, in_len, kiss_buff);
+
+ /* This has the escapes and the surrounding FENDs. */
+
+ if (kiss_debug) {
+ kiss_debug_print (TO_CLIENT, NULL, kiss_buff, kiss_len);
+ }
#if __WIN32__
- err = SOCK_SEND(client_sock[send_to], (char*)kiss_buff, kiss_len);
- if (err == SOCKET_ERROR)
- {
- text_color_set(DW_COLOR_ERROR);
- dw_printf ("\nError %d copying message to KISS client %d application. Closing connection.\n\n", WSAGetLastError(), send_to);
- closesocket (client_sock[send_to]);
- client_sock[send_to] = -1;
- WSACleanup();
- }
+ err = SOCK_SEND(kps->client_sock[client], (char*)kiss_buff, kiss_len);
+ if (err == SOCKET_ERROR) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("\nError %d copying message to KISS TCP port %d client %d application. Closing connection.\n\n", WSAGetLastError(), kps->tcp_port, client);
+ closesocket (kps->client_sock[client]);
+ kps->client_sock[client] = -1;
+ WSACleanup();
+ }
#else
- err = SOCK_SEND (client_sock[send_to], kiss_buff, kiss_len);
- if (err <= 0)
- {
- text_color_set(DW_COLOR_ERROR);
- dw_printf ("\nError copying message to KISS client %d application. Closing connection.\n\n", send_to);
- close (client_sock[send_to]);
- client_sock[send_to] = -1;
- }
+ err = SOCK_SEND (kps->client_sock[client], kiss_buff, kiss_len);
+ if (err <= 0) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("\nError copying message to KISS TCP port %d client %d application. Closing connection.\n\n", kps->tcp_port, client);
+ close (kps->client_sock[client]);
+ kps->client_sock[client] = -1;
+ }
#endif
- } // if origin and destination different.
- } // loop over all KISS network clients.
+ } // Channel is allowed on this port.
+ } // socket is open
+ } // if origin and destination different.
+ } // loop over all KISS network clients for one port.
+ } // loop over all KISS TCP ports
} // Feature enabled.
} /* end kissnet_copy */
@@ -759,20 +905,19 @@ void kissnet_copy (unsigned char *in_msg, int in_len, int chan, int cmd, int fro
/* Return one byte (value 0 - 255) */
-static int kiss_get (int client)
+static int kiss_get (struct kissport_status_s *kps, int client)
{
- unsigned char ch;
- int n;
while (1) {
- while (client_sock[client] <= 0) {
+ while (kps->client_sock[client] <= 0) {
SLEEP_SEC(1); /* Not connected. Try again later. */
}
/* Just get one byte at a time. */
- n = SOCK_RECV (client_sock[client], (char *)(&ch), 1);
+ unsigned char ch;
+ int n = SOCK_RECV (kps->client_sock[client], (char *)(&ch), 1);
if (n == 1) {
#if DEBUG9
@@ -792,13 +937,13 @@ static int kiss_get (int client)
}
text_color_set(DW_COLOR_ERROR);
- dw_printf ("\nKISS client application %d has gone away.\n\n", client);
+ dw_printf ("\nKISS client application %d on TCP port %d has gone away.\n\n", client, kps->tcp_port);
#if __WIN32__
- closesocket (client_sock[client]);
+ closesocket (kps->client_sock[client]);
#else
- close (client_sock[client]);
+ close (kps->client_sock[client]);
#endif
- client_sock[client] = -1;
+ kps->client_sock[client] = -1;
}
}
@@ -806,17 +951,19 @@ static int kiss_get (int client)
static THREAD_F kissnet_listen_thread (void *arg)
{
- unsigned char ch;
-
+ struct kissport_status_s *kps = arg;
- int client = (int)(ptrdiff_t)arg;
+ int client = kps->arg2;
+ assert (client >= 0 && client < MAX_NET_CLIENTS);
+
+ kps->arg2 = -1; // Indicates thread is running so
+ // arg2 can be reused for the next one.
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
- dw_printf ("kissnet_listen_thread ( client = %d, socket fd = %d )\n", client, client_sock[client]);
+ dw_printf ("kissnet_listen_thread ( tcp_port = %d, client = %d, socket fd = %d )\n", kps->tcp_port, client, kps->client_sock[client]);
#endif
- assert (client >= 0 && client < MAX_NET_CLIENTS);
// So why is kissnet_send_rec_packet mentioned here for incoming from the client app?
@@ -832,8 +979,8 @@ static THREAD_F kissnet_listen_thread (void *arg)
while (1) {
- ch = kiss_get(client);
- kiss_rec_byte (&(kf[client]), ch, kiss_debug, client, kissnet_send_rec_packet);
+ unsigned char ch = kiss_get(kps, client);
+ kiss_rec_byte (&(kps->kf[client]), ch, kiss_debug, kps, client, kissnet_send_rec_packet);
}
#if __WIN32__
diff --git a/src/kissnet.h b/src/kissnet.h
index ac00752e..469e4e63 100644
--- a/src/kissnet.h
+++ b/src/kissnet.h
@@ -3,21 +3,27 @@
* Name: kissnet.h
*/
+#ifndef KISSNET_H
+#define KISSNET_H
#include "ax25_pad.h" /* for packet_t */
#include "config.h"
+#include "kiss_frame.h"
void kissnet_init (struct misc_config_s *misc_config);
-void kissnet_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int client);
+void kissnet_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen,
+ struct kissport_status_s *onlykps, int onlyclient);
void kiss_net_set_debug (int n);
-void kissnet_copy (unsigned char *kiss_msg, int kiss_len, int chan, int cmd, int from_client);
+void kissnet_copy (unsigned char *kiss_msg, int kiss_len, int chan, int cmd, struct kissport_status_s *from_kps, int from_client);
+#endif // KISSNET_H
+
/* end kissnet.h */
diff --git a/src/kissserial.c b/src/kissserial.c
index 9f185ef1..1ee5356a 100644
--- a/src/kissserial.c
+++ b/src/kissserial.c
@@ -261,6 +261,7 @@ void kissserial_init (struct misc_config_s *mc)
* flen - Length of raw received frame not including the FCS
* or -1 for a text string.
*
+ * kps
* client - Not used for serial port version.
* Here so that 3 related functions all have
* the same parameter list.
@@ -272,7 +273,8 @@ void kissserial_init (struct misc_config_s *mc)
*--------------------------------------------------------------------*/
-void kissserial_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int client)
+void kissserial_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen,
+ struct kissport_status_s *notused1, int notused2)
{
unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN + 2];
int kiss_len;
@@ -370,11 +372,11 @@ void kissserial_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, i
*
* Returns: one byte (value 0 - 255) or terminate thread on error.
*
- * Description: There is room for improvment here. Reading one byte
+ * Description: There is room for improvement here. Reading one byte
* at a time is inefficient. We could read a large block
* into a local buffer and return a byte from that most of the time.
* Is it worth the effort? I don't know. With GHz processors and
- * the low data rate here it might not make a noticable difference.
+ * the low data rate here it might not make a noticeable difference.
*
*--------------------------------------------------------------------*/
@@ -488,7 +490,7 @@ static THREAD_F kissserial_listen_thread (void *arg)
while (1) {
ch = kissserial_get();
- kiss_rec_byte (&kf, ch, kissserial_debug, -1, kissserial_send_rec_packet);
+ kiss_rec_byte (&kf, ch, kissserial_debug, NULL, -1, kissserial_send_rec_packet);
}
#if __WIN32__
diff --git a/src/kissserial.h b/src/kissserial.h
index a6775291..44fb3c3f 100644
--- a/src/kissserial.h
+++ b/src/kissserial.h
@@ -8,12 +8,14 @@
#include "config.h"
-
+#include "kiss_frame.h"
void kissserial_init (struct misc_config_s *misc_config);
-void kissserial_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen, int client);
+void kissserial_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen,
+ struct kissport_status_s *notused1, int notused2);
+
void kissserial_set_debug (int n);
diff --git a/src/kissutil.c b/src/kissutil.c
index 026a6ea3..470d178d 100644
--- a/src/kissutil.c
+++ b/src/kissutil.c
@@ -308,10 +308,15 @@ int main (int argc, char *argv[])
}
#endif
+// Give the threads a little while to open the TNC connection before trying to use it.
+// This was a problem when the transmit queue already existed when starting up.
+
+ SLEEP_MS (500);
+
/*
* Process keyboard or other input source.
*/
- char stuff[1000];
+ char stuff[AX25_MAX_PACKET_LEN];
if (strlen(transmit_from) > 0) {
/*
@@ -544,8 +549,8 @@ static void process_input (char *stuff)
static void send_to_kiss_tnc (int chan, int cmd, char *data, int dlen)
{
- unsigned char temp[1000];
- unsigned char kissed[2000];
+ unsigned char temp[AX25_MAX_PACKET_LEN]; // We don't limit to 256 info bytes.
+ unsigned char kissed[AX25_MAX_PACKET_LEN*2];
int klen;
if (chan < 0 || chan > 15) {
@@ -587,6 +592,7 @@ static void send_to_kiss_tnc (int chan, int cmd, char *data, int dlen)
if (rc != klen) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR writing KISS frame to serial port.\n");
+ //dw_printf ("DEBUG wanted %d, got %d\n", klen, rc);
}
}
@@ -663,7 +669,7 @@ static THREAD_F tnc_listen_net (void *arg)
// on the assumption it was being used in only one direction.
// Not worried enough about it to do anything at this time.
- kiss_rec_byte (&kstate, data[j], verbose, client, NULL);
+ kiss_rec_byte (&kstate, data[j], verbose, NULL, client, NULL);
}
}
@@ -726,7 +732,7 @@ static THREAD_F tnc_listen_serial (void *arg)
// Feed in one byte at a time.
// kiss_process_msg is called when a complete frame has been accumulated.
- kiss_rec_byte (&kstate, ch, verbose, client, NULL);
+ kiss_rec_byte (&kstate, ch, verbose, NULL, client, NULL);
}
} /* end tnc_listen_serial */
@@ -754,7 +760,8 @@ static THREAD_F tnc_listen_serial (void *arg)
*
*-----------------------------------------------------------------*/
-void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int))
+void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, struct kissport_status_s *kps, int client,
+ void (*sendfun)(int chan, int kiss_cmd, unsigned char *fbuf, int flen, struct kissport_status_s *onlykps, int onlyclient))
{
int chan;
int cmd;
@@ -915,7 +922,7 @@ static void usage(void)
dw_printf (" a serial port. e.g. /dev/ttyAMA0 or COM3.\n");
dw_printf (" -s Serial port speed, default 9600.\n");
dw_printf (" -v Verbose. Show the KISS frame contents.\n");
- dw_printf (" -f Transmit files directory. Processs and delete files here.\n");
+ dw_printf (" -f Transmit files directory. Process and delete files here.\n");
dw_printf (" -o Receive output queue directory. Store received frames here.\n");
dw_printf (" -T Precede received frames with 'strftime' format time stamp.\n");
usage2();
@@ -929,7 +936,7 @@ static void usage2 (void)
dw_printf ("Input, starting with upper case letter or digit, is assumed\n");
dw_printf ("to be an AX.25 frame in the usual TNC2 monitoring format.\n");
dw_printf ("\n");
- dw_printf ("Input, starting with a lower case letter is a commmand.\n");
+ dw_printf ("Input, starting with a lower case letter is a command.\n");
dw_printf ("Whitespace, as shown in examples, is optional.\n");
dw_printf ("\n");
dw_printf (" letter meaning example\n");
diff --git a/src/latlong.c b/src/latlong.c
index b3eadcc8..d5f23365 100644
--- a/src/latlong.c
+++ b/src/latlong.c
@@ -55,7 +55,11 @@
* ambiguity - If 1, 2, 3, or 4, blank out that many trailing digits.
*
* Outputs: slat - String in format ddmm.mm[NS]
- * Should always be exactly 8 characters + NUL.
+ * Must always be exactly 8 characters + NUL.
+ * Put in leading zeros if necessary.
+ * We must have exactly ddmm.mm and hemisphere because
+ * the APRS position report has fixed width fields.
+ * Trailing digits can be blanked for position ambiguity.
*
* Returns: None
*
@@ -101,6 +105,12 @@ void latitude_to_str (double dlat, int ambiguity, char *slat)
ideg = (int)dlat;
dmin = (dlat - ideg) * 60.;
+ // dmin is known to be in range of 0 <= dmin < 60.
+
+ // Minutes must be exactly like 99.99 with leading zeros,
+ // if needed, to make it fixed width.
+ // Two digits, decimal point, two digits, nul terminator.
+
snprintf (smin, sizeof(smin), "%05.2f", dmin);
/* Due to roundoff, 59.9999 could come out as "60.00" */
if (smin[0] == '6') {
@@ -108,6 +118,10 @@ void latitude_to_str (double dlat, int ambiguity, char *slat)
ideg++;
}
+ // Assumes slat can hold 8 characters + nul.
+ // Degrees must be exactly 2 digits, with leading zero, if needed.
+
+ // FIXME: Should pass in sizeof slat and use snprintf
sprintf (slat, "%02d%s%c", ideg, smin, hemi);
if (ambiguity >= 1) {
@@ -135,9 +149,12 @@ void latitude_to_str (double dlat, int ambiguity, char *slat)
* Inputs: dlong - Floating point degrees.
* ambiguity - If 1, 2, 3, or 4, blank out that many trailing digits.
*
- * Outputs: slat - String in format dddmm.mm[NS]
- * Should always be exactly 9 characters + NUL.
- *
+ * Outputs: slong - String in format dddmm.mm[NS]
+ * Must always be exactly 9 characters + NUL.
+ * Put in leading zeros if necessary.
+ * We must have exactly dddmm.mm and hemisphere because
+ * the APRS position report has fixed width fields.
+ * Trailing digits can be blanked for position ambiguity.
* Returns: None
*
*----------------------------------------------------------------*/
@@ -178,7 +195,12 @@ void longitude_to_str (double dlong, int ambiguity, char *slong)
ideg++;
}
+ // Assumes slong can hold 9 characters + nul.
+ // Degrees must be exactly 3 digits, with leading zero, if needed.
+
+ // FIXME: Should pass in sizeof slong and use snprintf
sprintf (slong, "%03d%s%c", ideg, smin, hemi);
+
/*
* The spec says position ambiguity in latitude also
* applies to longitude automatically.
@@ -352,6 +374,7 @@ void latitude_to_nmea (double dlat, char *slat, char *hemi)
ideg++;
}
+ // FIXME: Should pass in sizeof slat and use snprintf
sprintf (slat, "%02d%s", ideg, smin);
} /* end latitude_to_str */
@@ -413,6 +436,7 @@ void longitude_to_nmea (double dlong, char *slong, char *hemi)
ideg++;
}
+ // FIXME: Should pass in sizeof slong and use snprintf
sprintf (slong, "%03d%s", ideg, smin);
} /* end longitude_to_nmea */
@@ -903,6 +927,14 @@ int main (int argc, char *argv[])
latitude_to_str (45.999830, 4, result);
if (strcmp(result, "45 . N") != 0) { errors++; dw_printf ("Error 1.8: Did not expect \"%s\"\n", result); }
+ // Test for leading zeros for small values. Result must be fixed width.
+
+ latitude_to_str (0.016666666, 0, result);
+ if (strcmp(result, "0001.00N") != 0) { errors++; dw_printf ("Error 1.9: Did not expect \"%s\"\n", result); }
+
+ latitude_to_str (-1.999999, 0, result);
+ if (strcmp(result, "0200.00S") != 0) { errors++; dw_printf ("Error 1.10: Did not expect \"%s\"\n", result); }
+
/* Longitude to APRS format. */
longitude_to_str (45.25, 0, result);
@@ -931,6 +963,15 @@ int main (int argc, char *argv[])
longitude_to_str (45.999830, 4, result);
if (strcmp(result, "045 . E") != 0) { errors++; dw_printf ("Error 2.8: Did not expect \"%s\"\n", result); }
+ // Test for leading zeros for small values. Result must be fixed width.
+
+ longitude_to_str (0.016666666, 0, result);
+ if (strcmp(result, "00001.00E") != 0) { errors++; dw_printf ("Error 2.9: Did not expect \"%s\"\n", result); }
+
+ longitude_to_str (-1.999999, 0, result);
+ if (strcmp(result, "00200.00W") != 0) { errors++; dw_printf ("Error 2.10: Did not expect \"%s\"\n", result); }
+
+
/* Compressed format. */
/* Protocol spec example has <*e7 but I got <*e8 due to rounding rather than truncation to integer. */
diff --git a/src/log.c b/src/log.c
index 02aab2ab..c8b2f825 100644
--- a/src/log.c
+++ b/src/log.c
@@ -345,7 +345,7 @@ void log_write (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_
char sstatus[40];
char stelemetry[200];
char scomment[256];
- char alevel_text[32];
+ char alevel_text[40];
diff --git a/src/log2gpx.c b/src/log2gpx.c
index b13d80ea..15d389f4 100644
--- a/src/log2gpx.c
+++ b/src/log2gpx.c
@@ -258,8 +258,6 @@ static void read_csv(FILE *fp)
float speed = UNKNOWN_VALUE;
float course = UNKNOWN_VALUE;
float alt = UNKNOWN_VALUE;
- double freq = UNKNOWN_VALUE;
- int offset = UNKNOWN_VALUE;
char stemp[16], desc[32], comment[256];
if (pspeed != NULL && strlen(pspeed) > 0) {
@@ -275,7 +273,7 @@ static void read_csv(FILE *fp)
/* combine freq/offset/tone into one description string. */
if (pfreq != NULL && strlen(pfreq) > 0) {
- freq = atof(pfreq);
+ double freq = atof(pfreq);
snprintf (desc, sizeof(desc), "%.3f MHz", freq);
}
else {
@@ -283,7 +281,7 @@ static void read_csv(FILE *fp)
}
if (poffset != NULL && strlen(poffset) > 0) {
- offset = atoi(poffset);
+ int offset = atoi(poffset);
if (offset != 0 && offset % 1000 == 0) {
snprintf (stemp, sizeof(stemp), "%+dM", offset / 1000);
}
diff --git a/src/mgn_icon.h b/src/mgn_icon.h
index 4563cce1..c870bc0e 100644
--- a/src/mgn_icon.h
+++ b/src/mgn_icon.h
@@ -208,9 +208,9 @@ static const char mgn_alternate_symtab[SYMTAB_SIZE][3] = {
MGN_park, // ; 27 Park/Picnic area
MGN_default, // < 28 ADVISORY (one WX flag)
MGN_default, // = 29 APRStt Touchtone (DTMF users)
- MGN_default, // > 30 OVERLAYED CAR
+ MGN_default, // > 30 OVERLAID CAR
MGN_tourist_info, // ? 31 INFO Kiosk (Blue box with ?)
- MGN_default, // @ 32 HURICANE/Trop-Storm
+ MGN_default, // @ 32 HURRICANE/Trop-Storm
MGN_box, // A 33 overlayBOX DTMF & RFID & XO
MGN_default, // B 34 Blwng Snow (& future codes)
MGN_boating, // C 35 Coast Guard
@@ -220,7 +220,7 @@ static const char mgn_alternate_symtab[SYMTAB_SIZE][3] = {
MGN_default, // G 39 Snow Shwr (& future ovrlys)
MGN_default, // H 40 Haze (& Overlay Hazards)
MGN_default, // I 41 Rain Shower
- MGN_default, // J 42 Lightening (& future ovrlys)
+ MGN_default, // J 42 Lightning (& future ovrlys)
MGN_default, // K 43 Kenwood HT (W)
MGN_lighthouse, // L 44 Lighthouse
MGN_default, // M 45 MARS (A=Army,N=Navy,F=AF)
@@ -263,12 +263,12 @@ static const char mgn_alternate_symtab[SYMTAB_SIZE][3] = {
MGN_default, // r 82 Restrooms
MGN_default, // s 83 OVERLAY SHIP/boat (top view)
MGN_default, // t 84 Tornado
- MGN_default, // u 85 OVERLAYED TRUCK
- MGN_default, // v 86 OVERLAYED Van
+ MGN_default, // u 85 OVERLAID TRUCK
+ MGN_default, // v 86 OVERLAID Van
MGN_default, // w 87 Flooding
MGN_wreck, // x 88 Wreck or Obstruction ->X<-
MGN_default, // y 89 Skywarn
- MGN_default, // z 90 OVERLAYED Shelter
+ MGN_default, // z 90 OVERLAID Shelter
MGN_default, // { 91 Fog (& future ovrly codes)
MGN_default, // | 92 TNC Stream Switch
MGN_default, // } 93
diff --git a/src/mheard.c b/src/mheard.c
index eff4709a..e751c365 100644
--- a/src/mheard.c
+++ b/src/mheard.c
@@ -102,7 +102,7 @@ typedef struct mheard_s {
double dlat, dlon; // Last position. G_UNKNOWN for unknown.
- int msp; // Allow message sender positon report.
+ int msp; // Allow message sender position report.
// When non zero, an IS>RF position report is allowed.
// Then decremented.
@@ -350,6 +350,11 @@ void mheard_save_rf (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, r
}
mptr = calloc(sizeof(mheard_t),1);
+ if (mptr == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
+ }
strlcpy (mptr->callsign, source, sizeof(mptr->callsign));
mptr->count = 1;
mptr->chan = chan;
@@ -485,6 +490,11 @@ void mheard_save_is (char *ptext)
}
mptr = calloc(sizeof(mheard_t),1);
+ if (mptr == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
+ }
strlcpy (mptr->callsign, source, sizeof(mptr->callsign));
mptr->count = 1;
mptr->last_heard_is = now;
@@ -768,7 +778,7 @@ void mheard_set_msp (char *callsign, int num)
*
* Inputs: callsign - Callsign for station which sent the "message."
*
- * Returns: The cound for the specified station.
+ * Returns: The count for the specified station.
* 0 if not found.
*
*------------------------------------------------------------------*/
diff --git a/src/morse.c b/src/morse.c
index 8e709fd9..b61e75cb 100644
--- a/src/morse.c
+++ b/src/morse.c
@@ -70,7 +70,7 @@ static const struct morse_s {
{ 'F', "..-." },
{ 'G', "--." },
{ 'H', "...." },
- { 'I', "." },
+ { 'I', ".." },
{ 'J', ".---" },
{ 'K', "-.-" },
{ 'L', ".-.." },
@@ -313,7 +313,7 @@ static void morse_tone (int chan, int tu, int wpm) {
int f1_change_per_sample; // How much to advance phase for each audio sample.
- if (save_audio_config_p->achan[chan].medium != MEDIUM_RADIO) {
+ if (save_audio_config_p->chan_medium[chan] != MEDIUM_RADIO) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid channel %d for sending Morse Code.\n", chan);
return;
@@ -365,7 +365,7 @@ static void morse_quiet (int chan, int tu, int wpm) {
int nsamples;
int j;
- if (save_audio_config_p->achan[chan].medium != MEDIUM_RADIO) {
+ if (save_audio_config_p->chan_medium[chan] != MEDIUM_RADIO) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid channel %d for sending Morse Code.\n", chan);
return;
@@ -404,7 +404,7 @@ static void morse_quiet_ms (int chan, int ms) {
int nsamples;
int j;
- if (save_audio_config_p->achan[chan].medium != MEDIUM_RADIO) {
+ if (save_audio_config_p->chan_medium[chan] != MEDIUM_RADIO) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid channel %d for sending Morse Code.\n", chan);
return;
diff --git a/src/multi_modem.c b/src/multi_modem.c
index c59af071..d02b8c2a 100644
--- a/src/multi_modem.c
+++ b/src/multi_modem.c
@@ -83,7 +83,7 @@
//#define DEBUG 1
-#define DIGIPEATER_C
+#define DIGIPEATER_C // Why?
#include "direwolf.h"
@@ -172,7 +172,7 @@ void multi_modem_init (struct audio_s *pa)
hdlc_rec_init (save_audio_config_p);
for (chan=0; chanachan[chan].medium == MEDIUM_RADIO) {
+ if (save_audio_config_p->chan_medium[chan] == MEDIUM_RADIO) {
if (save_audio_config_p->achan[chan].baud <= 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Internal error, chan=%d, %s, %d\n", chan, __FILE__, __LINE__);
@@ -345,13 +345,20 @@ void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned c
else {
pp = ax25_from_frame (fbuf, flen, alevel);
}
+
+ multi_modem_process_rec_packet (chan, subchan, slice, pp, alevel, retries, is_fx25);
+}
+
+// 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)
+{
if (pp == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Unexpected internal problem, %s %d\n", __FILE__, __LINE__);
return; /* oops! why would it fail? */
}
-
/*
* If only one demodulator/slicer, and no FX.25 in progress,
* push it thru and forget about all this foolishness.
@@ -468,7 +475,7 @@ static void pick_best_candidate (int chan)
spectrum[n] = '.';
}
- /* Begining score depends on effort to get a valid frame CRC. */
+ /* Beginning score depends on effort to get a valid frame CRC. */
if (candidate[chan][j][k].packet_p == NULL) {
candidate[chan][j][k].score = 0;
@@ -488,6 +495,9 @@ static void pick_best_candidate (int chan)
}
}
+ // FIXME: IL2p & FX.25 don't have CRC calculated. Must fill it in first.
+
+
/* Bump it up slightly if others nearby have the same CRC. */
for (n = 0; n < num_bars; n++) {
diff --git a/src/multi_modem.h b/src/multi_modem.h
index 0734492b..de3061ed 100644
--- a/src/multi_modem.h
+++ b/src/multi_modem.h
@@ -16,6 +16,9 @@ void multi_modem_process_sample (int c, int audio_sample);
int multi_modem_get_dc_average (int chan);
+// Deprecated. Replace with ...packet
void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries, int is_fx25);
+void multi_modem_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, int is_fx25);
+
#endif
diff --git a/src/pfilter.c b/src/pfilter.c
index 626f0712..9ae3fe87 100644
--- a/src/pfilter.c
+++ b/src/pfilter.c
@@ -116,7 +116,7 @@ typedef struct pfstate_s {
/*
* Are we processing APRS or connected mode?
- * This determines whch types of filters are available.
+ * This determines which types of filters are available.
*/
int is_aprs;
@@ -220,7 +220,7 @@ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp, int is_aprs)
pfstate.from_chan = from_chan;
pfstate.to_chan = to_chan;
- /* Copy filter string, changing any control characers to spaces. */
+ /* Copy filter string, changing any control characters to spaces. */
strlcpy (pfstate.filter_str, filter, sizeof(pfstate.filter_str));
@@ -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;
if (is_aprs) {
- decode_aprs (&pfstate.decoded, pp, 1);
+ decode_aprs (&pfstate.decoded, pp, 1, 0);
}
next_token(&pfstate);
@@ -776,7 +776,7 @@ static int parse_filter_spec (pfstate_t *pf)
* -1 = error detected
*
* Description: Same function is used for all of these because they are so similar.
- * Look for exact match to any of the specifed strings.
+ * Look for exact match to any of the specified strings.
* All of them allow wildcarding with single * at the end.
*
*------------------------------------------------------------------------------*/
@@ -967,7 +967,7 @@ static int filt_t (pfstate_t *pf)
if (strncmp(infop, ":BOM", 4) == 0) return (1);
/*
* Or we can have an object.
- * It's not exactly clear how to distiguish this from other objects.
+ * 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.
*/
@@ -1290,7 +1290,7 @@ static int filt_s (pfstate_t *pf)
* -1 = error detected
*
* Description: Selection is based on time since last heard on RF, and distance
- * in terms of digipeater hops and/or phyiscal location.
+ * in terms of digipeater hops and/or physical location.
*
* i/time
* i/time/hops
diff --git a/src/ptt.c b/src/ptt.c
index 2a943006..3bad92c4 100644
--- a/src/ptt.c
+++ b/src/ptt.c
@@ -126,7 +126,7 @@
This is documented in the User Guide, section called,
"Hamlib PTT Example 2: Use GPIO of USB audio adapter. (e.g. DMK URI)"
- It's rather involved and the explantion doesn't cover the case of multiple
+ It's rather involved and the explanation doesn't cover the case of multiple
USB-Audio adapters. It would be nice to have a little script which lists all
of the USB-Audio adapters and the corresponding /dev/hidraw device.
( We now have it. The included "cm108" application. )
@@ -162,15 +162,15 @@
#include
#endif
-#ifdef USE_CM108
-#include "cm108.h"
-#endif
-
/* So we can have more common code for fd. */
typedef int HANDLE;
#define INVALID_HANDLE_VALUE (-1)
-#endif
+#endif /* __WIN32__ */
+
+#ifdef USE_CM108
+#include "cm108.h"
+#endif /* USE_CM108 */
#include "textcolor.h"
#include "audio.h"
@@ -737,7 +737,7 @@ void ptt_init (struct audio_s *audio_config_p)
for (ch = 0; ch < MAX_CHANS; ch++) {
- if (audio_config_p->achan[ch].medium == MEDIUM_RADIO) {
+ if (audio_config_p->chan_medium[ch] == MEDIUM_RADIO) {
int ot;
for (ot = 0; ot < NUM_OCTYPES; ot++) {
@@ -768,7 +768,7 @@ void ptt_init (struct audio_s *audio_config_p)
int j, k;
for (j = ch; j >= 0; j--) {
- if (audio_config_p->achan[j].medium == MEDIUM_RADIO) {
+ if (audio_config_p->chan_medium[j] == MEDIUM_RADIO) {
for (k = ((j==ch) ? (ot - 1) : (NUM_OCTYPES-1)); k >= 0; k--) {
if (strcmp(audio_config_p->achan[ch].octrl[ot].ptt_device,audio_config_p->achan[j].octrl[k].ptt_device) == 0) {
fd = ptt_fd[j][k];
@@ -851,7 +851,7 @@ void ptt_init (struct audio_s *audio_config_p)
using_gpio = 0;
for (ch=0; chachan[ch].medium == MEDIUM_RADIO) {
+ if (save_audio_config_p->chan_medium[ch] == MEDIUM_RADIO) {
int ot;
for (ot = 0; ot < NUM_OCTYPES; ot++) {
if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_GPIO) {
@@ -876,7 +876,7 @@ void ptt_init (struct audio_s *audio_config_p)
*/
for (ch = 0; ch < MAX_CHANS; ch++) {
- if (save_audio_config_p->achan[ch].medium == MEDIUM_RADIO) {
+ if (save_audio_config_p->chan_medium[ch] == MEDIUM_RADIO) {
int ot; // output control type, PTT, DCD, CON, ...
int it; // input control type
@@ -908,13 +908,13 @@ void ptt_init (struct audio_s *audio_config_p)
#if ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) )
for (ch = 0; ch < MAX_CHANS; ch++) {
- if (save_audio_config_p->achan[ch].medium == MEDIUM_RADIO) {
+ if (save_audio_config_p->chan_medium[ch] == MEDIUM_RADIO) {
int ot;
for (ot = 0; ot < NUM_OCTYPES; ot++) {
if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_LPT) {
/* Can't open the same device more than once so we */
- /* need more logic to look for the case of mutiple radio */
+ /* need more logic to look for the case of multiple radio */
/* channels using different pins of the LPT port. */
/* Did some earlier channel use the same ptt device name? */
@@ -923,7 +923,7 @@ void ptt_init (struct audio_s *audio_config_p)
int j, k;
for (j = ch; j >= 0; j--) {
- if (audio_config_p->achan[j].medium == MEDIUM_RADIO) {
+ if (audio_config_p->chan_medium[j] == MEDIUM_RADIO) {
for (k = ((j==ch) ? (ot - 1) : (NUM_OCTYPES-1)); k >= 0; k--) {
if (strcmp(audio_config_p->achan[ch].octrl[ot].ptt_device,audio_config_p->achan[j].octrl[k].ptt_device) == 0) {
fd = ptt_fd[j][k];
@@ -975,7 +975,7 @@ void ptt_init (struct audio_s *audio_config_p)
#ifdef USE_HAMLIB
for (ch = 0; ch < MAX_CHANS; ch++) {
- if (save_audio_config_p->achan[ch].medium == MEDIUM_RADIO) {
+ if (save_audio_config_p->chan_medium[ch] == MEDIUM_RADIO) {
int ot;
for (ot = 0; ot < NUM_OCTYPES; ot++) {
if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_HAMLIB) {
@@ -1075,7 +1075,7 @@ void ptt_init (struct audio_s *audio_config_p)
for (ch = 0; ch < MAX_CHANS; ch++) {
- if (audio_config_p->achan[ch].medium == MEDIUM_RADIO) {
+ if (audio_config_p->chan_medium[ch] == MEDIUM_RADIO) {
int ot;
for (ot = 0; ot < NUM_OCTYPES; ot++) {
if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_CM108) {
@@ -1096,7 +1096,7 @@ void ptt_init (struct audio_s *audio_config_p)
/* Why doesn't it transmit? Probably forgot to specify PTT option. */
for (ch=0; chachan[ch].medium == MEDIUM_RADIO) {
+ if (audio_config_p->chan_medium[ch] == MEDIUM_RADIO) {
if(audio_config_p->achan[ch].octrl[OCTYPE_PTT].ptt_method == PTT_METHOD_NONE) {
text_color_set(DW_COLOR_INFO);
dw_printf ("Note: PTT not configured for channel %d. (Ignore this if using VOX.)\n", ch);
@@ -1148,7 +1148,7 @@ void ptt_set (int ot, int chan, int ptt_signal)
assert (chan >= 0 && chan < MAX_CHANS);
- if ( save_audio_config_p->achan[chan].medium != MEDIUM_RADIO) {
+ if ( save_audio_config_p->chan_medium[chan] != MEDIUM_RADIO) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error, ptt_set ( %s, %d, %d ), did not expect invalid channel.\n", otnames[ot], chan, ptt);
return;
@@ -1359,7 +1359,7 @@ int get_input (int it, int chan)
assert (it >= 0 && it < NUM_ICTYPES);
assert (chan >= 0 && chan < MAX_CHANS);
- if ( save_audio_config_p->achan[chan].medium != MEDIUM_RADIO) {
+ if ( save_audio_config_p->chan_medium[chan] != MEDIUM_RADIO) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Internal error, get_input ( %d, %d ), did not expect invalid channel.\n", it, chan);
return -1;
@@ -1423,7 +1423,7 @@ void ptt_term (void)
int n;
for (n = 0; n < MAX_CHANS; n++) {
- if (save_audio_config_p->achan[n].medium == MEDIUM_RADIO) {
+ if (save_audio_config_p->chan_medium[n] == MEDIUM_RADIO) {
int ot;
for (ot = 0; ot < NUM_OCTYPES; ot++) {
ptt_set (ot, n, 0);
@@ -1432,7 +1432,7 @@ void ptt_term (void)
}
for (n = 0; n < MAX_CHANS; n++) {
- if (save_audio_config_p->achan[n].medium == MEDIUM_RADIO) {
+ if (save_audio_config_p->chan_medium[n] == MEDIUM_RADIO) {
int ot;
for (ot = 0; ot < NUM_OCTYPES; ot++) {
if (ptt_fd[n][ot] != INVALID_HANDLE_VALUE) {
@@ -1450,7 +1450,7 @@ void ptt_term (void)
#ifdef USE_HAMLIB
for (n = 0; n < MAX_CHANS; n++) {
- if (save_audio_config_p->achan[n].medium == MEDIUM_RADIO) {
+ if (save_audio_config_p->chan_medium[n] == MEDIUM_RADIO) {
int ot;
for (ot = 0; ot < NUM_OCTYPES; ot++) {
if (rig[n][ot] != NULL) {
@@ -1489,14 +1489,14 @@ int main ()
my_audio_config.adev[0].num_channels = 2;
- my_audio_config.achan[0].medium = MEDIUM_RADIO;
+ my_audio_config.chan_medium[0] = MEDIUM_RADIO;
my_audio_config.achan[0].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_SERIAL;
// TODO: device should be command line argument.
strlcpy (my_audio_config.achan[0].octrl[OCTYPE_PTT].ptt_device, "COM3", sizeof(my_audio_config.achan[0].octrl[OCTYPE_PTT].ptt_device));
//strlcpy (my_audio_config.achan[0].octrl[OCTYPE_PTT].ptt_device, "/dev/ttyUSB0", sizeof(my_audio_config.achan[0].octrl[OCTYPE_PTT].ptt_device));
my_audio_config.achan[0].octrl[OCTYPE_PTT].ptt_line = PTT_LINE_RTS;
- my_audio_config.achan[1].medium = MEDIUM_RADIO;
+ my_audio_config.chan_medium[1] = MEDIUM_RADIO;
my_audio_config.achan[1].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_SERIAL;
strlcpy (my_audio_config.achan[1].octrl[OCTYPE_PTT].ptt_device, "COM3", sizeof(my_audio_config.achan[1].octrl[OCTYPE_PTT].ptt_device));
//strlcpy (my_audio_config.achan[1].octrl[OCTYPE_PTT].ptt_device, "/dev/ttyUSB0", sizeof(my_audio_config.achan[1].octrl[OCTYPE_PTT].ptt_device));
@@ -1570,7 +1570,7 @@ int main ()
memset (&my_audio_config, 0, sizeof(my_audio_config));
my_audio_config.adev[0].num_channels = 1;
- my_audio_config.achan[0].medium = MEDIUM_RADIO;
+ my_audio_config.chan_medium[0] = MEDIUM_RADIO;
my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_GPIO;
my_audio_config.adev[0].octrl[OCTYPE_PTT].out_gpio_num = 25;
@@ -1601,10 +1601,10 @@ int main ()
#if 0
memset (&my_audio_config, 0, sizeof(my_audio_config));
my_audio_config.num_channels = 2;
- my_audio_config.achan[0].medium = MEDIUM_RADIO;
+ my_audio_config.chan_medium[0] = MEDIUM_RADIO;
my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_LPT;
my_audio_config.adev[0].octrl[OCTYPE_PTT].ptt_lpt_bit = 0;
- my_audio_config.achan[1].medium = MEDIUM_RADIO;
+ my_audio_config.chan_medium[1] = MEDIUM_RADIO;
my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_method = PTT_METHOD_LPT;
my_audio_config.adev[1].octrl[OCTYPE_PTT].ptt_lpt_bit = 1;
diff --git a/src/recv.c b/src/recv.c
index f5c78167..f93f1d1e 100644
--- a/src/recv.c
+++ b/src/recv.c
@@ -42,7 +42,7 @@
* multi_modem_process_sample(s)
*
*
- * When a packet is succesfully decoded, somebody calls
+ * When a packet is successfully decoded, somebody calls
* app_process_rec_frame, also in direwolf.c
*
*
diff --git a/src/rrbb.c b/src/rrbb.c
index 82d8aaea..2047d69f 100644
--- a/src/rrbb.c
+++ b/src/rrbb.c
@@ -88,7 +88,11 @@ rrbb_t rrbb_new (int chan, int subchan, int slice, int is_scrambled, int descram
assert (slice >= 0 && slice < MAX_SLICERS);
result = malloc(sizeof(struct rrbb_s));
-
+ if (result == NULL) {
+ text_color_set(DW_COLOR_ERROR);
+ dw_printf ("FATAL ERROR: Out of memory.\n");
+ exit (EXIT_FAILURE);
+ }
result->magic1 = MAGIC1;
result->chan = chan;
result->subchan = subchan;
diff --git a/src/serial_port.c b/src/serial_port.c
index 7dff33a4..c57ee202 100644
--- a/src/serial_port.c
+++ b/src/serial_port.c
@@ -237,12 +237,14 @@ MYFDTYPE serial_port_open (char *devicename, int baud)
case 9600: cfsetispeed (&ts, B9600); cfsetospeed (&ts, B9600); break;
case 19200: cfsetispeed (&ts, B19200); cfsetospeed (&ts, B19200); break;
case 38400: cfsetispeed (&ts, B38400); cfsetospeed (&ts, B38400); break;
-#ifndef __APPLE__
+// This does not seem to be a problem anymore.
+// Leaving traces behind, as clue, in case failure is encountered in some older version.
+//#ifndef __APPLE__
// Not defined for Mac OSX.
// https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/2072
case 57600: cfsetispeed (&ts, B57600); cfsetospeed (&ts, B57600); break;
case 115200: cfsetispeed (&ts, B115200); cfsetospeed (&ts, B115200); break;
-#endif
+//#endif
default: text_color_set(DW_COLOR_ERROR);
dw_printf ("serial_port_open: Unsupported speed %d. Using 4800.\n", baud);
cfsetispeed (&ts, B4800); cfsetospeed (&ts, B4800);
@@ -302,17 +304,12 @@ int serial_port_write (MYFDTYPE fd, char *str, int len)
{
text_color_set(DW_COLOR_ERROR);
dw_printf ("Error writing to serial port. Error %d.\n\n", err);
+ return (-1);
}
}
- else if ((int)nwritten != len)
- {
- // Do we want this message here?
- // Or rely on caller to check and provide something more meaningful for the usage?
- //text_color_set(DW_COLOR_ERROR);
- //dw_printf ("Error writing to serial port. Only %d of %d written.\n\n", (int)nwritten, len);
- }
- return (nwritten);
+ // nwritten is 0 for asynchronous write, at this point, so just return the requested len.
+ return (len);
#else
int written;
diff --git a/src/server.c b/src/server.c
index 8c52b199..1639b5f9 100644
--- a/src/server.c
+++ b/src/server.c
@@ -116,6 +116,10 @@
* References: AGWPE TCP/IP API Tutorial
* http://uz7ho.org.ua/includes/agwpeapi.htm
*
+ * It has disappeared from the original location but you can find it here:
+ * https://web.archive.org/web/20130807113413/http:/uz7ho.org.ua/includes/agwpeapi.htm
+ * https://www.on7lds.net/42/sites/default/files/AGWPEAPI.HTM
+ *
* Getting Started with Winsock
* http://msdn.microsoft.com/en-us/library/windows/desktop/bb530742(v=vs.85).aspx
*
@@ -397,7 +401,7 @@ static void debug_print (fromto_t fromto, int client, struct agwpe_s *pmsg, int
if (msg_len < 36) {
text_color_set (DW_COLOR_ERROR);
- dw_printf ("AGWPE message length, %d, is shorter than minumum 36.\n", msg_len);
+ dw_printf ("AGWPE message length, %d, is shorter than minimum 36.\n", msg_len);
}
if (msg_len != netle2host(pmsg->data_len_NETLE) + 36) {
text_color_set (DW_COLOR_ERROR);
@@ -666,7 +670,7 @@ static THREAD_F connect_listen_thread (void *arg)
#else /* End of Windows case, now Linux */
- struct sockaddr_in sockaddr; /* Internet socket address stuct */
+ struct sockaddr_in sockaddr; /* Internet socket address struct */
socklen_t sockaddr_size = sizeof(struct sockaddr_in);
int server_port = (int)(ptrdiff_t)arg;
int listen_sock;
@@ -864,7 +868,7 @@ void server_send_monitored (int chan, packet_t pp, int own_xmit)
*/
struct {
struct agwpe_s hdr;
- char data[1+AX25_MAX_PACKET_LEN];
+ char data[128+AX25_MAX_PACKET_LEN]; // Add plenty of room for header prefix.
} agwpe_msg;
int err;
@@ -902,7 +906,7 @@ void server_send_monitored (int chan, packet_t pp, int own_xmit)
// Add the description with <... >
- char desc[80];
+ char desc[120];
agwpe_msg.hdr.datakind = mon_desc (pp, desc, sizeof(desc));
if (own_xmit) {
agwpe_msg.hdr.datakind = 'T';
@@ -917,16 +921,22 @@ void server_send_monitored (int chan, packet_t pp, int own_xmit)
snprintf (ts, sizeof(ts), "[%02d:%02d:%02d]\r", tm->tm_hour, tm->tm_min, tm->tm_sec);
strlcat ((char*)(agwpe_msg.data), ts, sizeof(agwpe_msg.data));
- // Information if any with \r\r.
+ // Information if any with \r.
unsigned char *pinfo = NULL;
int info_len = ax25_get_info (pp, &pinfo);
+ int msg_data_len = strlen((char*)(agwpe_msg.data)); // result length so far
+
if (info_len > 0 && pinfo != NULL) {
- strlcat ((char*)(agwpe_msg.data), (char*)pinfo, sizeof(agwpe_msg.data));
- strlcat ((char*)(agwpe_msg.data), "\r", sizeof(agwpe_msg.data));
+ // Issue 367: Use of strlcat truncated information part at any nul character.
+ // Use memcpy instead to preserve binary data, e.g. NET/ROM.
+ memcpy (agwpe_msg.data + msg_data_len, pinfo, info_len);
+ msg_data_len += info_len;
+ agwpe_msg.data[msg_data_len++] = '\r';
}
- agwpe_msg.hdr.data_len_NETLE = host2netle(strlen(agwpe_msg.data) + 1) /* +1 to include terminating null */ ;
+ agwpe_msg.data[msg_data_len++] = '\0'; // add nul at end, included in length.
+ agwpe_msg.hdr.data_len_NETLE = host2netle(msg_data_len);
if (debug_client) {
debug_print (TO_CLIENT, client, &agwpe_msg.hdr, sizeof(agwpe_msg.hdr) + netle2host(agwpe_msg.hdr.data_len_NETLE));
@@ -1032,7 +1042,7 @@ static char mon_desc (packet_t pp, char *result, int result_size)
switch (ftype) {
- case frame_type_I: snprintf (result, result_size, "", ns, nr, ax25_get_pid(pp), info_len, pf_text, pf); return ('I');
+ case frame_type_I: snprintf (result, result_size, "", ns, nr, ax25_get_pid(pp), info_len, pf_text, pf); return ('I');
case frame_type_U_UI: snprintf (result, result_size, "", ax25_get_pid(pp), info_len, pf_text, pf); return ('U'); break;
@@ -1534,9 +1544,9 @@ static THREAD_F cmd_listen_thread (void *arg)
count = 0;
for (j=0; jachan[j].medium == MEDIUM_RADIO ||
- save_audio_config_p->achan[j].medium == MEDIUM_IGATE ||
- save_audio_config_p->achan[j].medium == MEDIUM_NETTNC) {
+ if (save_audio_config_p->chan_medium[j] == MEDIUM_RADIO ||
+ save_audio_config_p->chan_medium[j] == MEDIUM_IGATE ||
+ save_audio_config_p->chan_medium[j] == MEDIUM_NETTNC) {
count++;
}
}
@@ -1544,7 +1554,7 @@ static THREAD_F cmd_listen_thread (void *arg)
for (j=0; jachan[j].medium) {
+ switch (save_audio_config_p->chan_medium[j]) {
case MEDIUM_RADIO:
{
@@ -1823,7 +1833,7 @@ static THREAD_F cmd_listen_thread (void *arg)
// Connected mode can only be used with internal modems.
- if (chan >= 0 && chan < MAX_CHANS && save_audio_config_p->achan[chan].medium == MEDIUM_RADIO) {
+ if (chan >= 0 && chan < MAX_CHANS && save_audio_config_p->chan_medium[chan] == MEDIUM_RADIO) {
ok = 1;
dlq_register_callsign (cmd.hdr.call_from, chan, client);
}
@@ -1852,7 +1862,7 @@ static THREAD_F cmd_listen_thread (void *arg)
// Connected mode can only be used with internal modems.
- if (chan >= 0 && chan < MAX_CHANS && save_audio_config_p->achan[chan].medium == MEDIUM_RADIO) {
+ if (chan >= 0 && chan < MAX_CHANS && save_audio_config_p->chan_medium[chan] == MEDIUM_RADIO) {
dlq_unregister_callsign (cmd.hdr.call_from, chan, client);
}
else {
@@ -1860,7 +1870,7 @@ static THREAD_F cmd_listen_thread (void *arg)
dw_printf ("AGW protocol error. Unregister callsign for invalid channel %d.\n", chan);
}
}
- /* No reponse is expected. */
+ /* No response is expected. */
break;
case 'C': /* Connect, Start an AX.25 Connection */
@@ -2065,7 +2075,7 @@ static THREAD_F cmd_listen_thread (void *arg)
// Before disconnecting from another station, it would be good to know
// that it actually received the last message we sent. For this reason,
// I think it would be good for this to include information frames that were
- // transmitted but not yet acknowleged.
+ // transmitted but not yet acknowledged.
// You could say that a particular frame is still waiting to be sent even
// if was already sent because it could be sent again if lost previously.
diff --git a/src/symbols.c b/src/symbols.c
index bb29e4f2..35dba807 100644
--- a/src/symbols.c
+++ b/src/symbols.c
@@ -1,7 +1,7 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
-// Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ
+// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2022 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -193,9 +193,9 @@ static const struct {
/* ; 27 */ { "NS", "Park/Picnic area" },
/* < 28 */ { "NT", "ADVISORY (one WX flag)" },
/* = 29 */ { "NU", "APRStt Touchtone (DTMF users)" },
- /* > 30 */ { "NV", "OVERLAYED CAR" },
+ /* > 30 */ { "NV", "OVERLAID CAR" },
/* ? 31 */ { "NW", "INFO Kiosk (Blue box with ?)" },
- /* @ 32 */ { "NX", "HURICANE/Trop-Storm" },
+ /* @ 32 */ { "NX", "HURRICANE/Trop-Storm" },
/* A 33 */ { "AA", "overlayBOX DTMF & RFID & XO" },
/* B 34 */ { "AB", "Blwng Snow (& future codes)" },
/* C 35 */ { "AC", "Coast Guard" },
@@ -205,7 +205,7 @@ static const struct {
/* G 39 */ { "AG", "Snow Shwr (& future ovrlys)" },
/* H 40 */ { "AH", "Haze (& Overlay Hazards)" },
/* I 41 */ { "AI", "Rain Shower" },
- /* J 42 */ { "AJ", "Lightening (& future ovrlys)" },
+ /* J 42 */ { "AJ", "Lightning (& future ovrlys)" },
/* K 43 */ { "AK", "Kenwood HT (W)" },
/* L 44 */ { "AL", "Lighthouse" },
/* M 45 */ { "AM", "MARS (A=Army,N=Navy,F=AF)" },
@@ -248,12 +248,12 @@ static const struct {
/* r 82 */ { "SR", "Restrooms" },
/* s 83 */ { "SS", "OVERLAY SHIP/boat (top view)" },
/* t 84 */ { "ST", "Tornado" },
- /* u 85 */ { "SU", "OVERLAYED TRUCK" },
- /* v 86 */ { "SV", "OVERLAYED Van" },
+ /* u 85 */ { "SU", "OVERLAID TRUCK" },
+ /* v 86 */ { "SV", "OVERLAID Van" },
/* w 87 */ { "SW", "Flooding" },
/* x 88 */ { "SX", "Wreck or Obstruction ->X<-" },
/* y 89 */ { "SY", "Skywarn" },
- /* z 90 */ { "SZ", "OVERLAYED Shelter" },
+ /* z 90 */ { "SZ", "OVERLAID Shelter" },
/* { 91 */ { "Q1", "Fog (& future ovrly codes)" },
/* | 92 */ { "Q2", "TNC Stream Switch" },
/* } 93 */ { "Q3", "" },
@@ -298,7 +298,7 @@ static const char *search_locations[] = {
* Description: The primary and alternate symbol tables are constant
* so they are hardcoded.
* However the "new" sysmbols, which give new meanings to
- * overlayed symbols, are always evolving.
+ * OVERLAID symbols, are always evolving.
* For maximum flexibility, we will read the
* data file at run time rather than compiling it in.
*
@@ -319,7 +319,7 @@ static const char *search_locations[] = {
typedef struct new_sym_s {
char overlay;
char symbol;
- char description[NEW_SYM_DESC_LEN+1];
+ char *description;
} new_sym_t;
static new_sym_t *new_sym_ptr = NULL; /* Dynamically allocated array. */
@@ -352,10 +352,22 @@ void symbols_init (void)
char stuff[200];
int j;
+// Feb. 2022 - Noticed that some lines have - rather than =.
+
+// LD = LIght Rail or Subway (new Aug 2014)
+// SD = Seaport Depot (new Aug 2014)
+// DIGIPEATERS
+// /# - Generic digipeater
+// 1# - WIDE1-1 digipeater
+
+
#define GOOD_LINE(x) (strlen(x) > 6 && \
(x[COL1_OVERLAY] == '/' || x[COL1_OVERLAY] == '\\' || isupper(x[COL1_OVERLAY]) || isdigit(x[COL1_OVERLAY])) \
&& x[COL2_SYMBOL] >= '!' && x[COL2_SYMBOL] <= '~' \
- && x[COL3_SP] == ' ' && x[COL4_EQUAL] == '=' && x[COL5_SP] == ' ' && x[COL6_DESC] != ' ')
+ && x[COL3_SP] == ' ' \
+ && (x[COL4_EQUAL] == '=' || x[COL4_EQUAL] == '-') \
+ && x[COL5_SP] == ' ' \
+ && x[COL6_DESC] != ' ')
if (new_sym_ptr != NULL) {
return; /* was called already. */
@@ -374,7 +386,7 @@ void symbols_init (void)
text_color_set(DW_COLOR_ERROR);
dw_printf ("Warning: Could not open 'symbols-new.txt'.\n");
- dw_printf ("The \"new\" overlayed character information will not be available.\n");
+ dw_printf ("The \"new\" OVERLAID character information will not be available.\n");
new_sym_size = 1;
new_sym_ptr = calloc(new_sym_size, sizeof(new_sym_t)); /* Don't try again. */
@@ -406,7 +418,7 @@ void symbols_init (void)
}
new_sym_ptr[new_sym_len].overlay = stuff[COL1_OVERLAY];
new_sym_ptr[new_sym_len].symbol = stuff[COL2_SYMBOL];
- strncpy(new_sym_ptr[new_sym_len].description, stuff+COL6_DESC, NEW_SYM_DESC_LEN);
+ new_sym_ptr[new_sym_len].description = strdup(stuff+COL6_DESC);
new_sym_len++;
}
}
@@ -556,6 +568,7 @@ static const char ssid_to_sym[16] = {
'v' /* 15 - Van */
};
+
void symbols_from_dest_or_src (char dti, char *src, char *dest, char *symtab, char *symbol)
{
char *p;
@@ -659,18 +672,34 @@ void symbols_from_dest_or_src (char dti, char *src, char *dest, char *symtab, ch
/*
* When all else fails, use source SSID.
+ * This is totally non-obvious and confusing, but it is in the APRS protocol spec.
+ * Chapter 20, "Symbol in the Source Address SSID"
*/
- p = strchr (src, '-');
- if (p != NULL)
- {
- int ssid;
+// January 2022 - Every time this shows up, it confuses people terribly.
+// e.g. An APRS "message" shows up with Bus or Motorcycle in the description.
- ssid = atoi(p+1);
- if (ssid >= 1 && ssid <= 15) {
- *symtab = '/'; /* All in Primary table. */
- *symbol = ssid_to_sym[ssid];
- return;
+// 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
+// a postion 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.
+// The protocol spec mentions a "MIM tracker" but I can't find any references to it.
+
+// If this was completely removed, no one would probably ever notice.
+// The only possible useful case I can think of would be someone sending a
+// NMEA string directly from a GPS receiver and wanting to keep the destination field
+// for the system type.
+
+ if (dti == '$') {
+
+ p = strchr (src, '-');
+ if (p != NULL) {
+ int ssid = atoi(p+1);
+ if (ssid >= 1 && ssid <= 15) {
+ *symtab = '/'; /* All in Primary table. */
+ *symbol = ssid_to_sym[ssid];
+ return;
+ }
}
}
diff --git a/src/telemetry.c b/src/telemetry.c
index b71bc8d9..2a6c690c 100644
--- a/src/telemetry.c
+++ b/src/telemetry.c
@@ -69,7 +69,7 @@
#define T_NUM_ANALOG 5 /* Number of analog channels. */
-#define T_NUM_DIGITAL 8 /* Number of digital channnels. */
+#define T_NUM_DIGITAL 8 /* Number of digital channels. */
#define T_STR_LEN 16 /* Max len for labels and units. */
@@ -707,7 +707,7 @@ void telemetry_unit_label_message (char *station, char *msg)
*
* Name: telemetry_coefficents_message
*
- * Purpose: Interpret message with scaling coefficents for analog channels.
+ * Purpose: Interpret message with scaling coefficients for analog channels.
*
* Inputs: station - Name of station reporting telemetry.
* In this case it is the destination for the message,
@@ -771,7 +771,7 @@ void telemetry_coefficents_message (char *station, char *msg, int quiet)
else {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
- dw_printf ("Equation coefficent position A%d%c is empty.\n", n/3+1, n%3+'a');
+ dw_printf ("Equation coefficient position A%d%c is empty.\n", n/3+1, n%3+'a');
dw_printf ("Some applications might not handle this correctly.\n");
}
}
@@ -782,7 +782,7 @@ void telemetry_coefficents_message (char *station, char *msg, int quiet)
if (n != T_NUM_ANALOG * 3) {
if ( ! quiet) {
text_color_set(DW_COLOR_ERROR);
- dw_printf ("Found %d equation coefficents when 15 were expected.\n", n);
+ dw_printf ("Found %d equation coefficients when 15 were expected.\n", n);
dw_printf ("Some applications might not handle this correctly.\n");
}
}
@@ -807,7 +807,7 @@ void telemetry_coefficents_message (char *station, char *msg, int quiet)
*
* Name: telemetry_bit_sense_message
*
- * Purpose: Interpret message with scaling coefficents for analog channels.
+ * Purpose: Interpret message with scaling coefficients for analog channels.
*
* Inputs: station - Name of station reporting telemetry.
* In this case it is the destination for the message,
@@ -910,7 +910,7 @@ void telemetry_bit_sense_message (char *station, char *msg, int quiet)
* seq - Sequence number.
* araw - 5 analog raw values.
* ndp - Number of decimal points for each.
- * draw - 8 digial raw vales.
+ * draw - 8 digital raw vales.
*
* Outputs: output - Decoded telemetry in human readable format.
*
diff --git a/src/textcolor.c b/src/textcolor.c
index a515e2eb..dea90f09 100644
--- a/src/textcolor.c
+++ b/src/textcolor.c
@@ -349,7 +349,7 @@ void text_color_set ( enum dw_color_e c )
* output to stdout or other desired destination.
*
* Inputs: fmt - C language format.
- * ... - Addtional arguments, just like printf.
+ * ... - Additional arguments, just like printf.
*
*
* Returns: Number of characters in result.
diff --git a/src/tnctest.c b/src/tnctest.c
new file mode 100644
index 00000000..0d4c26b4
--- /dev/null
+++ b/src/tnctest.c
@@ -0,0 +1,1284 @@
+//
+// This file is part of Dire Wolf, an amateur radio packet TNC.
+//
+// Copyright (C) 2016 John Langner, WB2OSZ
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * Module: tnctest.c
+ *
+ * Purpose: Test AX.25 connected mode between two TNCs.
+ *
+ * Description: The first TNC will connect to the second TNC and send a bunch of data.
+ * Proper transfer of data will be verified.
+ *
+ * Usage: tnctest [options] port0=name0 port1=name1
+ *
+ * Example: tnctest localhost:8000=direwolf COM1=KPC-3+
+ *
+ * Each port can have the following forms:
+ *
+ * * host-name:tcp-port
+ * * ip-addr:tcp-port
+ * * tcp-port
+ * * serial port name (e.g. COM1, /dev/ttyS0)
+ *
+ *---------------------------------------------------------------*/
+
+
+
+/*
+ * Native Windows: Use the Winsock interface.
+ * Linux: Use the BSD socket interface.
+ */
+
+#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h
+
+#if __WIN32__
+
+#include
+#include // _WIN32_WINNT must be set to 0x0501 before including this
+
+#else
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+//#include
+#include
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+//#include "ax25_pad.h"
+#include "textcolor.h"
+#include "dtime_now.h"
+#include "serial_port.h"
+
+
+/* We don't deal with big-endian processors here. */
+/* TODO: Use agwlib (which did not exist when this was written) */
+/* rather than duplicating the effort here. */
+
+struct agwpe_s {
+
+#if 1
+
+ unsigned char portx; /* 0 for first, 1 for second, etc. */
+ unsigned char reserved1;
+ unsigned char reserved2;
+ unsigned char reserved3;
+
+ unsigned char datakind; /* message type, usually written as a letter. */
+ unsigned char reserved4;
+ unsigned char pid;
+ unsigned char reserved5;
+
+#else
+ short portx; /* 0 for first, 1 for second, etc. */
+ short port_hi_reserved;
+ short kind_lo; /* message type */
+ short kind_hi;
+#endif
+ char call_from[10];
+ char call_to[10];
+ int data_len; /* Number of data bytes following. */
+ int user_reserved;
+};
+
+
+#if __WIN32__
+static unsigned __stdcall tnc_thread_net (void *arg);
+static unsigned __stdcall tnc_thread_serial (void *arg);
+#else
+static void * tnc_thread_net (void *arg);
+static void * tnc_thread_serial (void *arg);
+#endif
+
+
+static void tnc_connect (int from, int to);
+static void tnc_disconnect (int from, int to);
+static void tnc_send_data (int from, int to, char * data);
+static void tnc_reset (int from, int to);
+
+/*
+ * Convert Internet address to text.
+ * Can't use InetNtop because it is supported only on Windows Vista and later.
+ */
+
+static char * ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t StringBufSize)
+{
+ struct sockaddr_in *sa4;
+ struct sockaddr_in6 *sa6;
+
+ switch (Family) {
+ case AF_INET:
+ sa4 = (struct sockaddr_in *)pAddr;
+#if __WIN32__
+ snprintf (pStringBuf, StringBufSize, "%d.%d.%d.%d", sa4->sin_addr.S_un.S_un_b.s_b1,
+ sa4->sin_addr.S_un.S_un_b.s_b2,
+ sa4->sin_addr.S_un.S_un_b.s_b3,
+ sa4->sin_addr.S_un.S_un_b.s_b4);
+#else
+ inet_ntop (AF_INET, &(sa4->sin_addr), pStringBuf, StringBufSize);
+#endif
+ break;
+ case AF_INET6:
+ sa6 = (struct sockaddr_in6 *)pAddr;
+#if __WIN32__
+ snprintf (pStringBuf, StringBufSize, "%x:%x:%x:%x:%x:%x:%x:%x",
+ ntohs(((unsigned short *)(&(sa6->sin6_addr)))[0]),
+ ntohs(((unsigned short *)(&(sa6->sin6_addr)))[1]),
+ ntohs(((unsigned short *)(&(sa6->sin6_addr)))[2]),
+ ntohs(((unsigned short *)(&(sa6->sin6_addr)))[3]),
+ ntohs(((unsigned short *)(&(sa6->sin6_addr)))[4]),
+ ntohs(((unsigned short *)(&(sa6->sin6_addr)))[5]),
+ ntohs(((unsigned short *)(&(sa6->sin6_addr)))[6]),
+ ntohs(((unsigned short *)(&(sa6->sin6_addr)))[7]));
+#else
+ inet_ntop (AF_INET6, &(sa6->sin6_addr), pStringBuf, StringBufSize);
+#endif
+ break;
+ default:
+ snprintf (pStringBuf, StringBufSize, "Invalid address family!");
+ }
+ assert (strlen(pStringBuf) < StringBufSize);
+ return pStringBuf;
+}
+
+
+
+/*------------------------------------------------------------------
+ *
+ * Name: main
+ *
+ * Purpose: Basic test for connected AX.25 data mode between TNCs.
+ *
+ * Usage: Described above.
+ *
+ *---------------------------------------------------------------*/
+
+#define MAX_TNC 2 // Just 2 for now.
+ // Could be more later for multiple concurrent connections.
+
+/* Obtained from the command line. */
+
+static int num_tnc; /* How many TNCs for this test? */
+ /* Initially only 2 but long term we might */
+ /* enhance it to allow multiple concurrent connections. */
+
+static char hostname[MAX_TNC][50]; /* DNS host name or IPv4 address. */
+ /* Some of the code is there for IPv6 but */
+ /* needs more work. */
+ /* Defaults to "localhost" if not specified. */
+
+static char port[MAX_TNC][30]; /* If it begins with a digit, it is considered */
+ /* a TCP port number at the hostname. */
+ /* Otherwise, we treat it as a serial port name. */
+
+static char description[MAX_TNC][50]; /* Name used in the output. */
+
+static int using_tcp[MAX_TNC]; /* Are we using TCP or serial port for each TNC? */
+ /* Use corresponding one of the next two. */
+
+static int server_sock[MAX_TNC]; /* File descriptor for AGW socket interface. */
+ /* Set to -1 if not used. */
+ /* (Don't use SOCKET type because it is unsigned.) */
+
+static MYFDTYPE serial_fd[MAX_TNC]; /* Serial port handle. */
+
+static volatile int busy[MAX_TNC]; /* True when TNC busy and can't accept more data. */
+ /* For serial port, this is set by XON / XOFF characters. */
+
+#define XOFF 0x13
+#define XON 0x11
+
+static char tnc_address[MAX_TNC][12]; /* Name of the TNC used in the frames. Originally, this */
+ /* was simply TNC0 and TNC1 but that can get hard to read */
+ /* and confusing. Later used DW0, DW1, for direwolf */
+ /* so the direction of flow is easier to grasp. */
+
+#if __WIN32__
+ static HANDLE tnc_th[MAX_TNC];
+#else
+ static pthread_t tnc_tid[MAX_TNC];
+#endif
+
+#define LINE_WIDTH 80
+//#define LINE_WIDTH 120 /* If I was more ambitious I might try to get */
+ /* this from the terminal properties. */
+
+static int column_width; /* Line width divided by number of TNCs. */
+
+
+/*
+ * Current state for each TNC.
+ */
+
+static int is_connected[MAX_TNC]; /* -1 = not yet available. */
+ /* 0 = not connected. */
+ /* 1 = not connected. */
+
+static int have_cmd_prompt[MAX_TNC]; /* Set if "cmd:" was the last thing seen. */
+
+static int last_rec_seq[MAX_TNC]; /* Each data packet will contain a sequence number. */
+ /* This is used to verify that all have been */
+ /* received in the correct order. */
+
+
+
+/*
+ * Start time so we can print relative elapsed time.
+ */
+
+static double start_dtime;
+
+
+static int max_count;
+
+int main (int argc, char *argv[])
+{
+ int j;
+ int timeout;
+ int send_count = 0;
+ int burst_size = 1;
+ int errors = 0;
+
+ //max_count = 20;
+ max_count = 200;
+ //max_count = 6;
+ max_count = 1000;
+ max_count = 9999;
+
+#if __WIN32__
+#else
+ int e;
+
+ setlinebuf (stdout);
+#endif
+
+ start_dtime = dtime_now();
+
+/*
+ * Extract command line args.
+ */
+ num_tnc = argc - 1;
+
+ if (num_tnc < 2 || num_tnc > MAX_TNC) {
+ printf ("Specify minimum 2, maximum %d TNCs on the command line.\n", MAX_TNC);
+ exit (EXIT_FAILURE);
+ }
+
+ column_width = LINE_WIDTH / num_tnc;
+
+ for (j=0; j 0) {
+
+ SLEEP_MS(100);
+ timeout--;
+ ready = 1;
+ for (j=0; j last0) {
+ last0 = last_rec_seq[0];
+ no_activity = 0;
+ }
+ if (last_rec_seq[1] > last1) {
+ last1 = last_rec_seq[1];
+ no_activity = 0;
+ }
+ }
+
+ if (last_rec_seq[0] == max_count) {
+ printf ("Got last expected reply.\n");
+ }
+ else {
+ printf ("ERROR: Timeout - No incoming activity for %d seconds.\n", no_activity);
+ errors++;
+ }
+
+/*
+ * Did we get all expected replies?
+ */
+ if (last_rec_seq[0] != max_count) {
+ printf ("ERROR: Last received reply was %d when we were expecting %d.\n", last_rec_seq[0], max_count);
+ errors++;
+ }
+
+/*
+ * Ask for disconnect. Wait until complete.
+ */
+
+ tnc_disconnect (0, 1);
+
+ timeout = 200; // 20 sec should be generous.
+ ready = 0;
+ while ( ! ready && timeout > 0) {
+
+ SLEEP_MS(100);
+ timeout--;
+ ready = 1;
+ for (j=0; j1 for the other end which answers.
+ *
+ * data - Should look something like this:
+ * 9999 send data
+ * 9999 reply
+ *
+ * Global In/Out: last_rec_seq[my_index]
+ *
+ * Description: Look for expected format.
+ * Extract the sequence number.
+ * Verify that it is the next expected one.
+ * Update it.
+ *
+ *--------------------------------------------------------------------*/
+
+void process_rec_data (int my_index, char *data)
+{
+ int n;
+
+ if (isdigit(*data) && strncmp(data+4, " send", 5) == 0) {
+ if (my_index > 0) {
+ last_rec_seq[my_index]++;
+
+ n = atoi(data);
+ if (n != last_rec_seq[my_index]) {
+ printf ("%*s%s: Received %d when %d was expected.\n", my_index*column_width, "", tnc_address[my_index], n, last_rec_seq[my_index]);
+ SLEEP_MS(10000);
+ printf ("TEST FAILED!\n");
+ exit (EXIT_FAILURE);
+ }
+ }
+ }
+
+ else if (isdigit(*data) && strncmp(data+4, " reply", 6) == 0) {
+ if (my_index == 0) {
+ last_rec_seq[my_index]++;
+ n = atoi(data);
+ if (n != last_rec_seq[my_index]) {
+ printf ("%*s%s: Received %d when %d was expected.\n", my_index*column_width, "", tnc_address[my_index], n, last_rec_seq[my_index]);
+ SLEEP_MS(10000);
+ printf ("TEST FAILED!\n");
+ exit (EXIT_FAILURE);
+ }
+ }
+ }
+
+ else if (data[0] == 'A') {
+
+ if (strncmp(data, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", strlen(data)-1) != 0) {
+ printf ("%*s%s: Segmentation is broken.\n", my_index*column_width, "", tnc_address[my_index]);
+ SLEEP_MS(10000);
+ printf ("TEST FAILED!\n");
+ exit (EXIT_FAILURE);
+ }
+ }
+}
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name: tnc_thread_net
+ *
+ * Purpose: Establish connection with a TNC via network.
+ *
+ * Inputs: arg - My instance index, 0 thru MAX_TNC-1.
+ *
+ * Outputs: packets - Received packets are put in the corresponding column
+ * and sent to a common function to check that they
+ * all arrived in order.
+ *
+ * Global Out: is_connected - Updated when connected/disconnected notifications are received.
+ *
+ * Description: Perform any necessary configuration for the TNC then wait
+ * for responses and process them.
+ *
+ *--------------------------------------------------------------------*/
+
+#define MAX_HOSTS 30
+
+#if __WIN32__
+static unsigned __stdcall tnc_thread_net (void *arg)
+#else
+static void * tnc_thread_net (void *arg)
+#endif
+{
+ int my_index;
+ struct addrinfo hints;
+ struct addrinfo *ai_head = NULL;
+ struct addrinfo *ai;
+ struct addrinfo *hosts[MAX_HOSTS];
+ int num_hosts, n;
+ int err;
+ char ipaddr_str[46]; /* text form of IP address */
+#if __WIN32__
+ WSADATA wsadata;
+#endif
+
+ struct agwpe_s mon_cmd;
+ char data[4096];
+ double dnow;
+
+
+ my_index = (int)(ptrdiff_t)arg;
+
+#if DEBUGx
+ printf ("DEBUG: tnc_thread_net %d start, port = '%s'\n", my_index, port[my_index]);
+#endif
+
+#if __WIN32__
+ err = WSAStartup (MAKEWORD(2,2), &wsadata);
+ if (err != 0) {
+ printf("WSAStartup failed: %d\n", err);
+ return (0);
+ }
+
+ if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
+ printf("Could not find a usable version of Winsock.dll\n");
+ WSACleanup();
+ //sleep (1);
+ return (0);
+ }
+#endif
+
+ memset (&hints, 0, sizeof(hints));
+
+ hints.ai_family = AF_UNSPEC; /* Allow either IPv4 or IPv6. */
+ // hints.ai_family = AF_INET; /* IPv4 only. */
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+/*
+ * Connect to TNC server.
+ */
+
+ ai_head = NULL;
+ err = getaddrinfo(hostname[my_index], port[my_index], &hints, &ai_head);
+ if (err != 0) {
+#if __WIN32__
+ printf ("Can't get address for server %s, err=%d\n",
+ hostname[my_index], WSAGetLastError());
+#else
+ printf ("Can't get address for server %s, %s\n",
+ hostname[my_index], gai_strerror(err));
+#endif
+ freeaddrinfo(ai_head);
+ exit (1);
+ }
+
+#if DEBUG_DNS
+ printf ("getaddrinfo returns:\n");
+#endif
+ num_hosts = 0;
+ for (ai = ai_head; ai != NULL; ai = ai->ai_next) {
+#if DEBUG_DNS
+ ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str));
+ printf (" %s\n", ipaddr_str);
+#endif
+ hosts[num_hosts] = ai;
+ if (num_hosts < MAX_HOSTS) num_hosts++;
+ }
+
+#if DEBUG_DNS
+ printf ("addresses for hostname:\n");
+ for (n=0; nai_family, hosts[n]->ai_addr, ipaddr_str, sizeof(ipaddr_str));
+ printf (" %s\n", ipaddr_str);
+ }
+#endif
+
+ // Try each address until we find one that is successful.
+
+ for (n=0; nai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str));
+ is = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+#if __WIN32__
+ if (is == INVALID_SOCKET) {
+ printf ("Socket creation failed, err=%d", WSAGetLastError());
+ WSACleanup();
+ is = -1;
+ continue;
+ }
+#else
+ if (err != 0) {
+ printf ("Socket creation failed, err=%s", gai_strerror(err));
+ (void) close (is);
+ is = -1;
+ continue;
+ }
+#endif
+
+#ifndef DEBUG_DNS
+ err = connect(is, ai->ai_addr, (int)ai->ai_addrlen);
+#if __WIN32__
+ if (err == SOCKET_ERROR) {
+#if DEBUGx
+ printf("Connect to %s on %s (%s), port %s failed.\n",
+ description[my_index], hostname[my_index], ipaddr_str, port[my_index]);
+#endif
+ closesocket (is);
+ is = -1;
+ continue;
+ }
+#else
+ if (err != 0) {
+#if DEBUGx
+ printf("Connect to %s on %s (%s), port %s failed.\n",
+ description[my_index], hostname[my_index], ipaddr_str, port[my_index]);
+#endif
+ (void) close (is);
+ is = -1;
+ continue;
+ }
+ int flag = 1;
+ err = setsockopt (is, IPPROTO_TCP, TCP_NODELAY, (void*)(long)(&flag), (socklen_t)sizeof(flag));
+ if (err < 0) {
+ printf("setsockopt TCP_NODELAY failed.\n");
+ }
+#endif
+
+/* Success. */
+
+
+ server_sock[my_index] = is;
+#endif
+ break;
+ }
+
+ freeaddrinfo(ai_head);
+
+ if (server_sock[my_index] == -1) {
+
+ printf("TNC %d unable to connect to %s on %s (%s), port %s\n",
+ my_index, description[my_index], hostname[my_index], ipaddr_str, port[my_index] );
+ exit (1);
+ }
+
+
+#if 1 // Temp test just to get something.
+
+/*
+ * Send command to toggle reception of frames in raw format.
+ */
+ memset (&mon_cmd, 0, sizeof(mon_cmd));
+
+ mon_cmd.datakind = 'k';
+
+ SOCK_SEND(server_sock[my_index], (char*)(&mon_cmd), sizeof(mon_cmd));
+
+#endif
+
+/*
+ * Send command to register my callsign for incoming connect request.
+ * Not really needed when we initiate the connection.
+ */
+
+ memset (&mon_cmd, 0, sizeof(mon_cmd));
+
+ mon_cmd.datakind = 'X';
+ strlcpy (mon_cmd.call_from, tnc_address[my_index], sizeof(mon_cmd.call_from));
+
+ SOCK_SEND(server_sock[my_index], (char*)(&mon_cmd), sizeof(mon_cmd));
+
+
+
+/*
+ * Inform main program and observer that we are ready to go.
+ */
+ printf("TNC %d now available. %s on %s (%s), port %s\n",
+ my_index, description[my_index], hostname[my_index], ipaddr_str, port[my_index] );
+ is_connected[my_index] = 0;
+
+/*
+ * Print what we get from TNC.
+ */
+
+ while (1) {
+ int n;
+
+ n = SOCK_RECV (server_sock[my_index], (char*)(&mon_cmd), sizeof(mon_cmd));
+
+ if (n != sizeof(mon_cmd)) {
+ printf ("Read error, TNC %d received %d command bytes.\n", my_index, n);
+ exit (1);
+ }
+
+
+#if DEBUGx
+ printf ("TNC %d received '%c' data, data_len = %d\n",
+ my_index, mon_cmd.datakind, mon_cmd.data_len);
+#endif
+ assert (mon_cmd.data_len >= 0 && mon_cmd.data_len < (int)(sizeof(data)));
+
+ if (mon_cmd.data_len > 0) {
+
+ n = SOCK_RECV (server_sock[my_index], data, mon_cmd.data_len);
+
+ if (n != mon_cmd.data_len) {
+ printf ("Read error, TNC %d received %d data bytes.\n", my_index, n);
+ exit (1);
+ }
+ data[mon_cmd.data_len] = '\0';
+ }
+
+/*
+ * What did we get?
+ */
+
+ dnow = dtime_now();
+
+ switch (mon_cmd.datakind) {
+
+ case 'C': // AX.25 Connection Received
+
+ printf("%*s[R %.3f] *** Connected to %s ***\n", my_index*column_width, "", dnow-start_dtime, mon_cmd.call_from);
+ is_connected[my_index] = 1;
+
+ break;
+
+ case 'D': // Connected AX.25 Data
+
+ printf("%*s[R %.3f] %s\n", my_index*column_width, "", dnow-start_dtime, data);
+
+ process_rec_data (my_index, data);
+
+
+ if (isdigit(data[0]) && isdigit(data[1]) && isdigit(data[2]) && isdigit(data[3]) &&
+ strncmp(data+4, " send", 5) == 0) {
+ // Expected message. Make sure it is expected sequence and send reply.
+ int n = atoi(data);
+ char reply[80];
+ snprintf (reply, sizeof(reply), "%04d reply\r", n);
+ tnc_send_data (my_index, 1 - my_index, reply);
+
+ // HACK!
+ // It gets very confusing because N(S) and N(R) are very close.
+ // Send a couple dozen I frames so they will be easier to distinguish visually.
+ // Currently don't have the same in serial port version.
+
+ // We change the length each time to test segmentation.
+ // Set PACLEN to some very small number like 5.
+
+ if (n == 1 && max_count > 1) {
+ int j;
+ for (j = 1; j <= 26; j++) {
+ snprintf (reply, sizeof(reply), "%.*s\r", j, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ tnc_send_data (my_index, 1 - my_index, reply);
+ }
+ }
+ }
+
+ break;
+
+ case 'd': // Disconnected
+
+ printf("%*s[R %.3f] *** Disconnected from %s ***\n", my_index*column_width, "", dnow-start_dtime, mon_cmd.call_from);
+ is_connected[my_index] = 0;
+
+ break;
+
+ case 'y': // Outstanding frames waiting on a Port
+
+ printf("%*s[R %.3f] *** Outstanding frames waiting %d ***\n", my_index*column_width, "", dnow-start_dtime, 123); // TODO
+
+ break;
+
+ default:
+
+ //printf("%*s[R %.3f] --- Ignoring cmd kind '%c' ---\n", my_index*column_width, "", dnow-start_dtime, mon_cmd.datakind);
+
+ break;
+ }
+ }
+
+} /* end tnc_thread_net */
+
+
+
+
+
+/*-------------------------------------------------------------------
+ *
+ * Name: tnc_thread_serial
+ *
+ * Purpose: Establish connection with a TNC via serial port.
+ *
+ * Inputs: arg - My instance index, 0 thru MAX_TNC-1.
+ *
+ * Outputs: packets - Received packets are put in the corresponding column
+ * and sent to a common function to check that they
+ * all arrived in order.
+ *
+ * Global Out: is_connected - Updated when connected/disconnected notifications are received.
+ *
+ * Description: Perform any necessary configuration for the TNC then wait
+ * for responses and process them.
+ *
+ *--------------------------------------------------------------------*/
+
+
+#if __WIN32__
+typedef HANDLE MYFDTYPE;
+#define MYFDERROR INVALID_HANDLE_VALUE
+#else
+typedef int MYFDTYPE;
+#define MYFDERROR (-1)
+#endif
+
+
+#if __WIN32__
+static unsigned __stdcall tnc_thread_serial (void *arg)
+#else
+static void * tnc_thread_serial (void *arg)
+#endif
+{
+ int my_index = (int)(ptrdiff_t)arg;
+ char cmd[80];
+
+ serial_fd[my_index] = serial_port_open (port[my_index], 9600);
+
+ if (serial_fd[my_index] == MYFDERROR) {
+ printf("TNC %d unable to connect to %s on %s.\n",
+ my_index, description[my_index], port[my_index] );
+ exit (1);
+ }
+
+
+/*
+ * Make sure we are in command mode.
+ */
+
+ strcpy (cmd, "\003\rreset\r");
+ serial_port_write (serial_fd[my_index], cmd, strlen(cmd));
+ SLEEP_MS (3000);
+
+ strcpy (cmd, "echo on\r");
+ serial_port_write (serial_fd[my_index], cmd, strlen(cmd));
+ SLEEP_MS (200);
+
+// do any necessary set up here. such as setting mycall
+
+ snprintf (cmd, sizeof(cmd), "mycall %s\r", tnc_address[my_index]);
+ serial_port_write (serial_fd[my_index], cmd, strlen(cmd));
+ SLEEP_MS (200);
+
+// Don't want to stop tty output when typing begins.
+
+ strcpy (cmd, "flow off\r");
+ serial_port_write (serial_fd[my_index], cmd, strlen(cmd));
+
+ strcpy (cmd, "echo off\r");
+ serial_port_write (serial_fd[my_index], cmd, strlen(cmd));
+
+/* Success. */
+
+ printf("TNC %d now available. %s on %s\n",
+ my_index, description[my_index], port[my_index] );
+ is_connected[my_index] = 0;
+
+
+/*
+ * Read and print.
+ */
+
+ while (1) {
+ int ch;
+ char result[500];
+ int len;
+ int done;
+
+ len = 0;
+ result[len] = '\0';
+ done = 0;
+
+ while ( ! done) {
+
+ ch = serial_port_get1(serial_fd[my_index]);
+
+ if (ch < 0) {
+ printf("TNC %d fatal read error.\n", my_index);
+ exit (1);
+ }
+
+ if (ch == '\r' || ch == '\n') {
+ done = 1;
+ }
+ else if (ch == XOFF) {
+ double dnow = dtime_now();
+ printf("%*s[R %.3f] \n", my_index*column_width, "", dnow-start_dtime);
+ busy[my_index] = 1;
+ }
+ else if (ch == XON) {
+ double dnow = dtime_now();
+ printf("%*s[R %.3f] \n", my_index*column_width, "", dnow-start_dtime);
+ busy[my_index] = 0;
+ }
+ else if (isprint(ch)) {
+ result[len] = ch;
+ len++;
+ result[len] = '\0';
+ }
+ else {
+ char hex[12];
+
+ snprintf (hex, sizeof(hex), "", ch);
+ strlcat (result, hex, sizeof(result));
+ len = strlen(result);
+ }
+ if (strcmp(result, "cmd:") == 0) {
+ done = 1;
+ have_cmd_prompt[my_index] = 1;
+ }
+ else {
+ have_cmd_prompt[my_index] = 0;
+ }
+ }
+
+ if (len > 0) {
+
+ double dnow = dtime_now();
+
+ printf("%*s[R %.3f] %s\n", my_index*column_width, "", dnow-start_dtime, result);
+
+ if (strncmp(result, "*** CONNECTED", 13) == 0) {
+ is_connected[my_index] = 1;
+ }
+
+ if (strncmp(result, "*** DISCONNECTED", 16) == 0) {
+ is_connected[my_index] = 0;
+ }
+
+ if (strncmp(result, "Not while connected", 19) == 0) {
+ // Not expecting this.
+ // What to do?
+ }
+
+ process_rec_data (my_index, result);
+
+ if (isdigit(result[0]) && isdigit(result[1]) && isdigit(result[2]) && isdigit(result[3]) &&
+ strncmp(result+4, " send", 5) == 0) {
+ // Expected message. Make sure it is expected sequence and send reply.
+ int n = atoi(result);
+ char reply[80];
+ snprintf (reply, sizeof(reply), "%04d reply\r", n);
+ tnc_send_data (my_index, 1 - my_index, reply);
+ }
+
+ }
+ }
+
+} /* end tnc_thread_serial */
+
+
+
+
+static void tnc_connect (int from, int to)
+{
+
+ double dnow = dtime_now();
+
+ printf("%*s[T %.3f] *** Send connect request ***\n", from*column_width, "", dnow-start_dtime);
+
+ if (using_tcp[from]) {
+
+//struct agwpe_s {
+// short portx; /* 0 for first, 1 for second, etc. */
+// short port_hi_reserved;
+// short datakind; /* message type */
+// short kind_hi;
+// char call_from[10];
+// char call_to[10];
+// int data_len; /* Number of data bytes following. */
+// int user_reserved;
+//};
+ struct agwpe_s cmd;
+
+ memset (&cmd, 0, sizeof(cmd));
+
+ cmd.datakind = 'C';
+ strlcpy (cmd.call_from, tnc_address[from], sizeof(cmd.call_from));
+ strlcpy (cmd.call_to, tnc_address[to], sizeof(cmd.call_to));
+
+ SOCK_SEND(server_sock[from], (char*)(&cmd), sizeof(cmd));
+ }
+ else {
+
+ char cmd[80];
+
+ if (! have_cmd_prompt[from]) {
+
+ SLEEP_MS (1500);
+ strcpy (cmd, "\003\003\003");
+ serial_port_write (serial_fd[from], cmd, strlen(cmd));
+ SLEEP_MS (1500);
+
+ strcpy (cmd, "\r");
+ serial_port_write (serial_fd[from], cmd, strlen(cmd));
+ SLEEP_MS (200);
+ }
+
+ snprintf (cmd, sizeof(cmd), "connect %s\r", tnc_address[to]);
+ serial_port_write (serial_fd[from], cmd, strlen(cmd));
+ }
+
+} /* end tnc_connect */
+
+
+static void tnc_disconnect (int from, int to)
+{
+ double dnow = dtime_now();
+
+ printf("%*s[T %.3f] *** Send disconnect request ***\n", from*column_width, "", dnow-start_dtime);
+
+ if (using_tcp[from]) {
+
+ struct agwpe_s cmd;
+
+ memset (&cmd, 0, sizeof(cmd));
+
+ cmd.datakind = 'd';
+ strlcpy (cmd.call_from, tnc_address[from], sizeof(cmd.call_from));
+ strlcpy (cmd.call_to, tnc_address[to], sizeof(cmd.call_to));
+
+ SOCK_SEND(server_sock[from], (char*)(&cmd), sizeof(cmd));
+ }
+ else {
+
+ char cmd[80];
+
+ if (! have_cmd_prompt[from]) {
+
+ SLEEP_MS (1500);
+ strcpy (cmd, "\003\003\003");
+ serial_port_write (serial_fd[from], cmd, strlen(cmd));
+ SLEEP_MS (1500);
+
+ strcpy (cmd, "\r");
+ serial_port_write (serial_fd[from], cmd, strlen(cmd));
+ SLEEP_MS (200);
+ }
+
+ strcpy (cmd, "disconnect\r");
+ serial_port_write (serial_fd[from], cmd, strlen(cmd));
+ }
+
+} /* end tnc_disconnect */
+
+
+static void tnc_reset (int from, int to)
+{
+ double dnow = dtime_now();
+
+ printf("%*s[T %.3f] *** Send reset ***\n", from*column_width, "", dnow-start_dtime);
+
+ if (using_tcp[from]) {
+
+
+ }
+ else {
+
+ char cmd[80];
+
+ SLEEP_MS (1500);
+ strcpy (cmd, "\003\003\003");
+ serial_port_write (serial_fd[from], cmd, strlen(cmd));
+ SLEEP_MS (1500);
+
+ strcpy (cmd, "\r");
+ serial_port_write (serial_fd[from], cmd, strlen(cmd));
+ SLEEP_MS (200);
+
+ strcpy (cmd, "reset\r");
+ serial_port_write (serial_fd[from], cmd, strlen(cmd));
+ }
+
+} /* end tnc_disconnect */
+
+
+
+static void tnc_send_data (int from, int to, char * data)
+{
+ double dnow = dtime_now();
+
+ printf("%*s[T %.3f] %s\n", from*column_width, "", dnow-start_dtime, data);
+
+ if (using_tcp[from]) {
+
+ struct {
+ struct agwpe_s hdr;
+ char data[256];
+ } cmd;
+
+ memset (&cmd.hdr, 0, sizeof(cmd.hdr));
+
+ cmd.hdr.datakind = 'D';
+ cmd.hdr.pid = 0xf0;
+ snprintf (cmd.hdr.call_from, sizeof(cmd.hdr.call_from), "%s", tnc_address[from]);
+ snprintf (cmd.hdr.call_to, sizeof(cmd.hdr.call_to), "%s", tnc_address[to]);
+ cmd.hdr.data_len = strlen(data);
+ strlcpy (cmd.data, data, sizeof(cmd.data));
+
+ SOCK_SEND(server_sock[from], (char*)(&cmd), sizeof(cmd.hdr) + strlen(data));
+ }
+ else {
+
+ // The assumption is that we are in CONVERS mode.
+ // The data should be terminated by carriage return.
+
+ int timeout = 600; // 60 sec. I've seen it take more than 20.
+ while (timeout > 0 && busy[from]) {
+ SLEEP_MS(100);
+ timeout--;
+ }
+ if (timeout == 0) {
+ printf ("ERROR: Gave up waiting while TNC busy.\n");
+ tnc_disconnect (0,1);
+ SLEEP_MS(5000);
+ printf ("TEST FAILED!\n");
+ exit (EXIT_FAILURE);
+ }
+ else {
+ serial_port_write (serial_fd[from], data, strlen(data));
+ }
+ }
+
+} /* end tnc_disconnect */
+
+
+
+
+/* end tnctest.c */
diff --git a/src/tq.c b/src/tq.c
index 0cc4bec0..3d1b056b 100644
--- a/src/tq.c
+++ b/src/tq.c
@@ -148,7 +148,7 @@ void tq_init (struct audio_s *audio_config_p)
for (c = 0; c < MAX_CHANS; c++) {
- if (audio_config_p->achan[c].medium == MEDIUM_RADIO) {
+ if (audio_config_p->chan_medium[c] == MEDIUM_RADIO) {
wake_up_event[c] = CreateEvent (NULL, 0, 0, NULL);
@@ -167,7 +167,7 @@ void tq_init (struct audio_s *audio_config_p)
xmit_thread_is_waiting[c] = 0;
- if (audio_config_p->achan[c].medium == MEDIUM_RADIO) {
+ if (audio_config_p->chan_medium[c] == MEDIUM_RADIO) {
err = pthread_cond_init (&(wake_up_cond[c]), NULL);
if (err != 0) {
text_color_set(DW_COLOR_ERROR);
@@ -247,7 +247,7 @@ void tq_append (int chan, int prio, packet_t pp)
}
#endif
- if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->achan[chan].medium == MEDIUM_NONE) {
+ if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->chan_medium[chan] == MEDIUM_NONE) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan);
dw_printf ("This is probably a client application error, not a problem with direwolf.\n");
@@ -451,7 +451,7 @@ void lm_data_request (int chan, int prio, packet_t pp)
}
#endif
- if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->achan[chan].medium != MEDIUM_RADIO) {
+ if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->chan_medium[chan] != MEDIUM_RADIO) {
// Connected mode is allowed only with internal modems.
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan);
@@ -609,7 +609,7 @@ void lm_seize_request (int chan)
#endif
- if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->achan[chan].medium != MEDIUM_RADIO) {
+ if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->chan_medium[chan] != MEDIUM_RADIO) {
// Connected mode is allowed only with internal modems.
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan);
diff --git a/src/tt_text.c b/src/tt_text.c
index 9ca7f466..112adfe5 100644
--- a/src/tt_text.c
+++ b/src/tt_text.c
@@ -382,7 +382,7 @@ int tt_text_to_two_key (const char *text, int quiet, char *buttons)
* Outputs: buttons - Sequence of two buttons to press.
* "00" for error because this is probably
* being used to build up a fixed length
- * string where positions are signficant.
+ * string where positions are significant.
* Must be at least 3 bytes.
*
* Returns: Number of errors detected.
@@ -470,7 +470,7 @@ int tt_text_to_call10 (const char *text, int quiet, char *buttons)
char padded[8];
char stemp[11];
-
+ // FIXME: Add parameter for sizeof buttons and use strlcpy
strcpy (buttons, "");
/* Quick validity check. */
@@ -540,6 +540,7 @@ int tt_text_to_call10 (const char *text, int quiet, char *buttons)
/* Binary to decimal for the columns. */
snprintf (stemp, sizeof(stemp), "%04d", packed);
+ // FIXME: add parameter for sizeof buttons and use strlcat
strcat (buttons, stemp);
return (errors);
@@ -1435,6 +1436,7 @@ int tt_satsq_to_text (const char *buttons, int quiet, char *text)
row = buttons[0] - '0';
col = buttons[1] - '0';
+ // FIXME: Add parameter for sizeof text and use strlcpy, strlcat.
strcpy (text, grid[row][col]);
strcat (text, buttons+2);
@@ -1603,11 +1605,11 @@ int main (int argc, char *argv[])
exit (1);
}
- strcpy (text, argv[1]);
+ strlcpy (text, argv[1], sizeof(text));
for (n = 2; n < argc; n++) {
- strcat (text, " ");
- strcat (text, argv[n]);
+ strlcat (text, " ", sizeof(text));
+ strlcat (text, argv[n], sizeof(text));
}
dw_printf ("Push buttons for multi-press method:\n");
@@ -1670,7 +1672,7 @@ int main (int argc, char *argv[])
exit (1);
}
- strcpy (buttons, argv[1]);
+ strlcpy (buttons, argv[1], sizeof(buttons));
for (n = 2; n < argc; n++) {
strlcat (buttons, argv[n], sizeof(buttons));
diff --git a/src/tt_user.c b/src/tt_user.c
index c0713264..46e44453 100644
--- a/src/tt_user.c
+++ b/src/tt_user.c
@@ -230,7 +230,7 @@ void tt_user_init (struct audio_s *p_audio_config, struct tt_config_s *p_tt_conf
* Inputs: callsign - full or a old style 3 DIGIT suffix abbreviation
* overlay
*
- * Returns: Handle for refering to table position or -1 if not found.
+ * Returns: Handle for referring to table position or -1 if not found.
* This happens to be an index into an array but
* the implementation could change so the caller should
* not make any assumptions.
@@ -290,7 +290,7 @@ int tt_user_search (char *callsign, char overlay)
*
* Outputs: callsign - corresponding full callsign or empty string.
*
- * Returns: Handle for refering to table position (>= 0) or -1 if not found.
+ * Returns: Handle for referring to table position (>= 0) or -1 if not found.
* This happens to be an index into an array but
* the implementation could change so the caller should
* not make any assumptions.
@@ -351,7 +351,7 @@ static void clear_user(int i)
*
* Inputs: none
*
- * Returns: Handle for refering to table position.
+ * Returns: Handle for referring to table position.
*
* Description: If table is already full, this should delete the
* least recently heard user to make room.
@@ -453,7 +453,7 @@ static void digit_suffix (char *callsign, char *suffix)
*
* Name: tt_user_heard
*
- * Purpose: Record information from an APRStt trasmission.
+ * Purpose: Record information from an APRStt transmission.
*
* Inputs: callsign - full or an abbreviation
* ssid
@@ -881,7 +881,7 @@ static void xmit_object_report (int i, int first_time)
* Any attached application(s).
* IGate.
*
- * When transmitting over the radio, it gets sent multipe times, to help
+ * When transmitting over the radio, it gets sent multiple times, to help
* probablity of being heard, with increasing delays between.
*
* The other methods are reliable so we only want to send it once.
@@ -897,9 +897,9 @@ static void xmit_object_report (int i, int first_time)
flen = ax25_pack(pp, fbuf);
server_send_rec_packet (save_tt_config_p->obj_recv_chan, pp, fbuf, flen);
- kissnet_send_rec_packet (save_tt_config_p->obj_recv_chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1);
- kissserial_send_rec_packet (save_tt_config_p->obj_recv_chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1);
- kisspt_send_rec_packet (save_tt_config_p->obj_recv_chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1);
+ kissnet_send_rec_packet (save_tt_config_p->obj_recv_chan, KISS_CMD_DATA_FRAME, fbuf, flen, NULL, -1);
+ kissserial_send_rec_packet (save_tt_config_p->obj_recv_chan, KISS_CMD_DATA_FRAME, fbuf, flen, NULL, -1);
+ kisspt_send_rec_packet (save_tt_config_p->obj_recv_chan, KISS_CMD_DATA_FRAME, fbuf, flen, NULL, -1);
}
if (first_time && save_tt_config_p->obj_send_to_ig) {
diff --git a/src/ttcalc.c b/src/ttcalc.c
index 51952b92..a031ec0a 100644
--- a/src/ttcalc.c
+++ b/src/ttcalc.c
@@ -187,7 +187,7 @@ int main (int argc, char *argv[])
ax25_format_addrs (pp, result);
info_len = ax25_get_info (pp, (unsigned char **)(&pinfo));
pinfo[info_len] = '\0';
- strcat (result, pinfo);
+ strlcat (result, pinfo, sizeof(result));
for (p=result; *p!='\0'; p++) {
if (! isprint(*p)) *p = ' ';
}
diff --git a/src/waypoint.c b/src/waypoint.c
index c06b362f..70ea3205 100644
--- a/src/waypoint.c
+++ b/src/waypoint.c
@@ -187,10 +187,10 @@ void waypoint_init (struct misc_config_s *mc)
s_waypoint_formats = mc->waypoint_formats;
if (s_waypoint_formats == 0) {
- s_waypoint_formats = WPT_FORMAT_NMEA_GENERIC | WPT_FORMAT_KENWOOD;
+ s_waypoint_formats = WPL_FORMAT_NMEA_GENERIC | WPL_FORMAT_KENWOOD;
}
- if (s_waypoint_formats & WPT_FORMAT_GARMIN) {
- s_waypoint_formats |= WPT_FORMAT_NMEA_GENERIC; /* See explanation below. */
+ if (s_waypoint_formats & WPL_FORMAT_GARMIN) {
+ s_waypoint_formats |= WPL_FORMAT_NMEA_GENERIC; /* See explanation below. */
}
#if DEBUG
@@ -376,7 +376,7 @@ void waypoint_send_sentence (char *name_in, double dlat, double dlong, char symt
* *99 is checksum
*/
- if (s_waypoint_formats & WPT_FORMAT_NMEA_GENERIC) {
+ if (s_waypoint_formats & WPL_FORMAT_NMEA_GENERIC) {
snprintf (sentence, sizeof(sentence), "$GPWPL,%s,%s,%s,%s,%s", slat, slat_ns, slong, slong_ew, wname);
append_checksum (sentence);
@@ -406,7 +406,7 @@ void waypoint_send_sentence (char *name_in, double dlat, double dlong, char symt
* *99 is checksum
*/
- if (s_waypoint_formats & WPT_FORMAT_GARMIN) {
+ if (s_waypoint_formats & WPL_FORMAT_GARMIN) {
int i = symbol - ' ';
int grm_sym; /* Garmin symbol code. */
@@ -455,7 +455,7 @@ void waypoint_send_sentence (char *name_in, double dlat, double dlong, char symt
* to delete that specific waypoint.
*/
- if (s_waypoint_formats & WPT_FORMAT_MAGELLAN) {
+ if (s_waypoint_formats & WPL_FORMAT_MAGELLAN) {
int i = symbol - ' ';
char sicon[3]; /* Magellan icon string. Currently 1 or 2 characters. */
@@ -567,7 +567,7 @@ void waypoint_send_sentence (char *name_in, double dlat, double dlong, char symt
- if (s_waypoint_formats & WPT_FORMAT_KENWOOD) {
+ if (s_waypoint_formats & WPL_FORMAT_KENWOOD) {
time_t now;
struct tm tm;
@@ -654,7 +654,7 @@ void waypoint_send_ais (char *sentence)
return;
}
- if (s_waypoint_formats & WPT_FORMAT_AIS) {
+ if (s_waypoint_formats & WPL_FORMAT_AIS) {
send_sentence (sentence);
}
}
diff --git a/src/xid.c b/src/xid.c
index 617720c7..14e67e8d 100644
--- a/src/xid.c
+++ b/src/xid.c
@@ -92,7 +92,7 @@
// The first byte must be of the form 1000 0xx0
// The second byte must be of the form 1010 xx00
// The third byte must be of the form 0000 0010
-// If we process the three byte "HDLC Optional Parmeters" like
+// If we process the three byte "HDLC Optional Parameters" like
// the other multibyte numeric fields, with the most significant
// byte first, we end up with bit masks like this.
// The bit order would be 8 7 6 5 4 3 2 1 16 15 14 13 12 11 10 9 24 23 22 21 20 19 18 17
diff --git a/src/xmit.c b/src/xmit.c
index 6a725a7d..9494dac9 100644
--- a/src/xmit.c
+++ b/src/xmit.c
@@ -88,7 +88,7 @@
*/
-static int xmit_slottime[MAX_CHANS]; /* Slot time in 10 mS units for persistance algorithm. */
+static int xmit_slottime[MAX_CHANS]; /* Slot time in 10 mS units for persistence algorithm. */
static int xmit_persist[MAX_CHANS]; /* Sets probability for transmitting after each */
/* slot time delay. Transmit if a random number */
@@ -278,7 +278,7 @@ void xmit_init (struct audio_s *p_modem, int debug_xmit_packet)
for (j=0; jachan[j].medium == MEDIUM_RADIO) {
+ if (p_modem->chan_medium[j] == MEDIUM_RADIO) {
#if __WIN32__
xmit_th[j] = (HANDLE)_beginthreadex (NULL, 0, xmit_thread, (void*)(ptrdiff_t)j, 0, NULL);
if (xmit_th[j] == NULL) {
@@ -597,6 +597,11 @@ static void * xmit_thread (void *arg)
case FLAVOR_APRS_DIGI:
xmit_ax25_frames (chan, prio, pp, 1); /* 1 means don't bundle */
+ // I don't know if this in some official specification
+ // somewhere, but it is generally agreed that APRS digipeaters
+ // should send only one frame at a time rather than
+ // bunding multiple frames into a single transmission.
+ // Discussion here: http://lists.tapr.org/pipermail/aprssig_lists.tapr.org/2021-September/049034.html
break;
case FLAVOR_APRS_NEW:
@@ -761,7 +766,7 @@ static void xmit_ax25_frames (int chan, int prio, packet_t pp, int max_bundle)
// machine, that the transmission opportunity has arrived."
pre_flags = MS_TO_BITS(xmit_txdelay[chan] * 10, chan) / 8;
- num_bits = hdlc_send_flags (chan, pre_flags, 0);
+ num_bits = layer2_preamble_postamble (chan, pre_flags, 0, save_audio_config_p);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("xmit_thread: t=%.3f, txdelay=%d [*10], pre_flags=%d, num_bits=%d\n", dtime_now()-time_ptt, xmit_txdelay[chan], pre_flags, num_bits);
@@ -862,7 +867,7 @@ static void xmit_ax25_frames (int chan, int prio, packet_t pp, int max_bundle)
*/
post_flags = MS_TO_BITS(xmit_txtail[chan] * 10, chan) / 8;
- nb = hdlc_send_flags (chan, post_flags, 1);
+ nb = layer2_preamble_postamble (chan, post_flags, 1, save_audio_config_p);
num_bits += nb;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
@@ -957,8 +962,6 @@ static void xmit_ax25_frames (int chan, int prio, packet_t pp, int max_bundle)
static int send_one_frame (int c, int p, packet_t pp)
{
- unsigned char fbuf[AX25_MAX_PACKET_LEN+2];
- int flen;
char stemp[1024]; /* max size needed? */
int info_len;
unsigned char *pinfo;
@@ -1002,10 +1005,10 @@ static int send_one_frame (int c, int p, packet_t pp)
ax25_format_addrs (pp, stemp);
info_len = ax25_get_info (pp, &pinfo);
text_color_set(DW_COLOR_XMIT);
-#if 0
+#if 0 // FIXME - enable this?
dw_printf ("[%d%c%s%s] ", c,
p==TQ_PRIO_0_HI ? 'H' : 'L',
- save_audio_config_p->fx25_xmit_enable ? "F" : "",
+ save_audio_config_p->achan[c].fx25_strength ? "F" : "",
ts);
#else
dw_printf ("[%d%c%s] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L', ts);
@@ -1059,9 +1062,6 @@ static int send_one_frame (int c, int p, packet_t pp)
/*
* Transmit the frame.
*/
- flen = ax25_pack (pp, fbuf);
- assert (flen >= 1 && flen <= (int)(sizeof(fbuf)));
-
int send_invalid_fcs2 = 0;
if (save_audio_config_p->xmit_error_rate != 0) {
@@ -1074,7 +1074,7 @@ static int send_one_frame (int c, int p, packet_t pp)
}
}
- nb = hdlc_send_frame (c, fbuf, flen, send_invalid_fcs2, save_audio_config_p->fx25_xmit_enable);
+ nb = layer2_send_frame (c, pp, send_invalid_fcs2, save_audio_config_p);
// Optionally send confirmation to AGW client app if monitoring enabled.
@@ -1384,7 +1384,7 @@ static void xmit_dtmf (int c, packet_t pp, int speed)
* New in version 1.5: full duplex.
* Just start transmitting rather than waiting for clear channel.
* This would only be appropriate when transmit and receive are
- * using different radio freqencies. e.g. VHF up, UHF down satellite.
+ * using different radio frequencies. e.g. VHF up, UHF down satellite.
*
* Transmit delay algorithm:
*
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 6d4336e1..a95cbaf1 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -6,6 +6,7 @@ set(GEN_PACKETS_BIN "${CMAKE_BINARY_DIR}/src/gen_packets${CMAKE_EXECUTABLE_SUFFI
set(ATEST_BIN "${CMAKE_BINARY_DIR}/src/atest${CMAKE_EXECUTABLE_SUFFIX}")
set(FXSEND_BIN "${CMAKE_BINARY_DIR}/test/fxsend${CMAKE_EXECUTABLE_SUFFIX}")
set(FXREC_BIN "${CMAKE_BINARY_DIR}/test/fxrec${CMAKE_EXECUTABLE_SUFFIX}")
+set(IL2P_TEST_BIN "${CMAKE_BINARY_DIR}/test/il2p_test${CMAKE_EXECUTABLE_SUFFIX}")
if(WIN32)
set(CUSTOM_SCRIPT_SUFFIX ".bat")
@@ -14,9 +15,12 @@ else()
endif()
set(TEST_CHECK-FX25_FILE "check-fx25")
+set(TEST_CHECK-IL2P_FILE "check-il2p")
set(TEST_CHECK-MODEM1200_FILE "check-modem1200")
+set(TEST_CHECK-MODEM1200_IL2P_FILE "check-modem1200-i")
set(TEST_CHECK-MODEM300_FILE "check-modem300")
set(TEST_CHECK-MODEM9600_FILE "check-modem9600")
+set(TEST_CHECK-MODEM9600_IL2P_FILE "check-modem9600-i")
set(TEST_CHECK-MODEM19200_FILE "check-modem19200")
set(TEST_CHECK-MODEM2400-a_FILE "check-modem2400-a")
set(TEST_CHECK-MODEM2400-b_FILE "check-modem2400-b")
@@ -31,12 +35,24 @@ configure_file(
@ONLY
)
+configure_file(
+ "${CUSTOM_TEST_SCRIPTS_DIR}/${TEST_CHECK-IL2P_FILE}"
+ "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-IL2P_FILE}${CUSTOM_SCRIPT_SUFFIX}"
+ @ONLY
+ )
+
configure_file(
"${CUSTOM_TEST_SCRIPTS_DIR}/${TEST_CHECK-MODEM1200_FILE}"
"${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM1200_FILE}${CUSTOM_SCRIPT_SUFFIX}"
@ONLY
)
+configure_file(
+ "${CUSTOM_TEST_SCRIPTS_DIR}/${TEST_CHECK-MODEM1200_IL2P_FILE}"
+ "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM1200_IL2P_FILE}${CUSTOM_SCRIPT_SUFFIX}"
+ @ONLY
+ )
+
configure_file(
"${CUSTOM_TEST_SCRIPTS_DIR}/${TEST_CHECK-MODEM300_FILE}"
"${CUSTOM_TEST_BINARY_DIR}${CUSTOM_SCRIPT_SUFFIX}"
@@ -49,6 +65,12 @@ configure_file(
@ONLY
)
+configure_file(
+ "${CUSTOM_TEST_SCRIPTS_DIR}/${TEST_CHECK-MODEM9600_IL2P_FILE}"
+ "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM9600_IL2P_FILE}${CUSTOM_SCRIPT_SUFFIX}"
+ @ONLY
+ )
+
configure_file(
"${CUSTOM_TEST_SCRIPTS_DIR}/${TEST_CHECK-MODEM19200_FILE}"
"${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM19200_FILE}${CUSTOM_SCRIPT_SUFFIX}"
@@ -100,63 +122,12 @@ if(WIN32 OR CYGWIN)
)
endif()
-# Why is there a special atest9 instead of using the normal one?
-
-# Unit test for demodulators
-list(APPEND atest9_SOURCES
- ${CUSTOM_SRC_DIR}/atest.c
- ${CUSTOM_SRC_DIR}/ais.c
- ${CUSTOM_SRC_DIR}/demod.c
- ${CUSTOM_SRC_DIR}/dsp.c
- ${CUSTOM_SRC_DIR}/demod_afsk.c
- ${CUSTOM_SRC_DIR}/demod_psk.c
- ${CUSTOM_SRC_DIR}/demod_9600.c
- ${CUSTOM_SRC_DIR}/fx25_extract.c
- ${CUSTOM_SRC_DIR}/fx25_init.c
- ${CUSTOM_SRC_DIR}/fx25_rec.c
- ${CUSTOM_SRC_DIR}/hdlc_rec.c
- ${CUSTOM_SRC_DIR}/hdlc_rec2.c
- ${CUSTOM_SRC_DIR}/multi_modem.c
- ${CUSTOM_SRC_DIR}/rrbb.c
- ${CUSTOM_SRC_DIR}/fcs_calc.c
- ${CUSTOM_SRC_DIR}/ax25_pad.c
- ${CUSTOM_SRC_DIR}/decode_aprs.c
- ${CUSTOM_SRC_DIR}/dwgpsnmea.c
- ${CUSTOM_SRC_DIR}/dwgps.c
- ${CUSTOM_SRC_DIR}/dwgpsd.c
- ${CUSTOM_SRC_DIR}/serial_port.c
- ${CUSTOM_SRC_DIR}/latlong.c
- ${CUSTOM_SRC_DIR}/symbols.c
- ${CUSTOM_SRC_DIR}/textcolor.c
- ${CUSTOM_SRC_DIR}/telemetry.c
- ${CUSTOM_SRC_DIR}/dtime_now.c
- ${CUSTOM_SRC_DIR}/tt_text.c
- )
-
if(WIN32 OR CYGWIN)
list(REMOVE_ITEM atest9_SOURCES
${CUSTOM_SRC_DIR}/dwgpsd.c
)
endif()
-add_executable(atest9
- ${atest9_SOURCES}
- )
-
-target_link_libraries(atest9
- ${MISC_LIBRARIES}
- ${REGEX_LIBRARIES}
- ${GPSD_LIBRARIES}
- Threads::Threads
- )
-
-if(WIN32 OR CYGWIN)
- set_target_properties(atest9
- PROPERTIES COMPILE_FLAGS "-DUSE_REGEX_STATIC"
- )
- target_link_libraries(atest9 ws2_32)
-endif()
-
# Unit test for inner digipeater algorithm
list(APPEND dtest_SOURCES
@@ -205,7 +176,7 @@ if(WIN32 OR CYGWIN)
endif()
-# Unit test for APRStt tone seqence parsing.
+# Unit test for APRStt tone sequence parsing.
list(APPEND ttest_SOURCES
${CUSTOM_SRC_DIR}/aprs_tt.c
${CUSTOM_SRC_DIR}/tt_text.c
@@ -468,6 +439,39 @@ set_target_properties(fxrec
)
+# Unit Test IL2P with out modems.
+
+list(APPEND il2p_test_SOURCES
+ ${CUSTOM_SRC_DIR}/il2p_test.c
+ ${CUSTOM_SRC_DIR}/il2p_init.c
+ ${CUSTOM_SRC_DIR}/il2p_rec.c
+ ${CUSTOM_SRC_DIR}/il2p_send.c
+ ${CUSTOM_SRC_DIR}/il2p_codec.c
+ ${CUSTOM_SRC_DIR}/il2p_payload.c
+ ${CUSTOM_SRC_DIR}/il2p_header.c
+ ${CUSTOM_SRC_DIR}/il2p_scramble.c
+ ${CUSTOM_SRC_DIR}/ax25_pad.c
+ ${CUSTOM_SRC_DIR}/ax25_pad2.c
+ ${CUSTOM_SRC_DIR}/fx25_encode.c
+ ${CUSTOM_SRC_DIR}/fx25_extract.c
+ ${CUSTOM_SRC_DIR}/fx25_init.c
+ ${CUSTOM_SRC_DIR}/fcs_calc.c
+ ${CUSTOM_SRC_DIR}/textcolor.c
+ )
+
+add_executable(il2p_test
+ ${il2p_test_SOURCES}
+ )
+
+#FIXME - remove if not needed.
+#set_target_properties(il2p_test
+# PROPERTIES COMPILE_FLAGS "-DXXXXX"
+# )
+
+target_link_libraries(il2p_test
+ ${MISC_LIBRARIES}
+ )
+
# doing ctest on previous programs
add_test(dtest dtest)
@@ -483,17 +487,18 @@ add_test(xidtest xidtest)
add_test(dtmftest dtmftest)
add_test(check-fx25 "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-FX25_FILE}${CUSTOM_SCRIPT_SUFFIX}")
+add_test(check-il2p "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-IL2P_FILE}${CUSTOM_SCRIPT_SUFFIX}")
add_test(check-modem1200 "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM1200_FILE}${CUSTOM_SCRIPT_SUFFIX}")
+add_test(check-modem1200-i "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM1200_IL2P_FILE}${CUSTOM_SCRIPT_SUFFIX}")
add_test(check-modem300 "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM300_FILE}${CUSTOM_SCRIPT_SUFFIX}")
add_test(check-modem9600 "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM9600_FILE}${CUSTOM_SCRIPT_SUFFIX}")
+add_test(check-modem9600-i "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM9600_IL2P_FILE}${CUSTOM_SCRIPT_SUFFIX}")
add_test(check-modem19200 "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM19200_FILE}${CUSTOM_SCRIPT_SUFFIX}")
add_test(check-modem2400-a "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM2400-a_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-modem4800 "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM4800_FILE}${CUSTOM_SCRIPT_SUFFIX}")
-# TODO miss the audio file
-# ./atest9 -B 9600 ../walkabout9600.wav | grep "packets decoded in" >atest.out
# ----------------------------- Manual tests and experiments ---------------------------
diff --git a/test/scripts/check-fx25 b/test/scripts/check-fx25
index 37df174f..d5003337 100755
--- a/test/scripts/check-fx25
+++ b/test/scripts/check-fx25
@@ -3,3 +3,14 @@
@FXSEND_BIN@
@FXREC_BIN@
+@GEN_PACKETS_BIN@ -B9600 -n 100 -X 0 -o test96f0.wav
+@ATEST_BIN@ -B9600 -F0 -L60 -G64 test96f0.wav
+
+@GEN_PACKETS_BIN@ -B9600 -n 100 -X 16 -o test96f16.wav
+@ATEST_BIN@ -B9600 -F0 -L63 -G67 test96f16.wav
+
+@GEN_PACKETS_BIN@ -B9600 -n 100 -X 32 -o test96f32.wav
+@ATEST_BIN@ -B9600 -F0 -L64 -G68 test96f32.wav
+
+@GEN_PACKETS_BIN@ -B9600 -n 100 -X 64 -o test96f64.wav
+@ATEST_BIN@ -B9600 -F0 -L71 -G75 test96f64.wav
diff --git a/test/scripts/check-il2p b/test/scripts/check-il2p
new file mode 100755
index 00000000..da404720
--- /dev/null
+++ b/test/scripts/check-il2p
@@ -0,0 +1,4 @@
+@CUSTOM_SHELL_SHABANG@
+
+@IL2P_TEST_BIN@
+
diff --git a/test/scripts/check-modem1200 b/test/scripts/check-modem1200
index 2b975871..d3022796 100755
--- a/test/scripts/check-modem1200
+++ b/test/scripts/check-modem1200
@@ -1,5 +1,7 @@
@CUSTOM_SHELL_SHABANG@
@GEN_PACKETS_BIN@ -n 100 -o test12.wav
-@ATEST_BIN@ -F0 -PE -L64 -G72 test12.wav
-@ATEST_BIN@ -F1 -PE -L70 -G75 test12.wav
+@ATEST_BIN@ -F0 -PA -D1 -L66 -G72 test12.wav
+@ATEST_BIN@ -F1 -PA -D1 -L72 -G78 test12.wav
+@ATEST_BIN@ -F0 -PB -D1 -L66 -G74 test12.wav
+@ATEST_BIN@ -F1 -PB -D1 -L70 -G82 test12.wav
diff --git a/test/scripts/check-modem1200-i b/test/scripts/check-modem1200-i
new file mode 100755
index 00000000..9ef01bab
--- /dev/null
+++ b/test/scripts/check-modem1200-i
@@ -0,0 +1,4 @@
+@CUSTOM_SHELL_SHABANG@
+
+@GEN_PACKETS_BIN@ -I1 -n 100 -o test12-il2p.wav
+@ATEST_BIN@ -P+ -D1 -L92 -G94 test12-il2p.wav
diff --git a/test/scripts/check-modem300 b/test/scripts/check-modem300
index da37dd2b..6d6e6432 100755
--- a/test/scripts/check-modem300
+++ b/test/scripts/check-modem300
@@ -1,5 +1,7 @@
@CUSTOM_SHELL_SHABANG@
@GEN_PACKETS_BIN@ -B300 -n 100 -o test3.wav
-@ATEST_BIN@ -B300 -F0 -L68 -G69 test3.wav
-@ATEST_BIN@ -B300 -F1 -L71 -G75 test3.wav
+@ATEST_BIN@ -B300 -PA -F0 -L65 -G71 test3.wav
+@ATEST_BIN@ -B300 -PA -F1 -L69 -G75 test3.wav
+@ATEST_BIN@ -B300 -PB -F0 -L69 -G75 test3.wav
+@ATEST_BIN@ -B300 -PB -F1 -L73 -G79 test3.wav
diff --git a/test/scripts/check-modem9600-i b/test/scripts/check-modem9600-i
new file mode 100755
index 00000000..0ba01bea
--- /dev/null
+++ b/test/scripts/check-modem9600-i
@@ -0,0 +1,18 @@
+@CUSTOM_SHELL_SHABANG@
+
+@GEN_PACKETS_BIN@ -B9600 -I1 -n 100 -o test96-il2p-I1.wav
+@ATEST_BIN@ -B9600 -L72 -G76 test96-il2p-I1.wav
+@ATEST_BIN@ -B9600 -P+ -L76 -G80 test96-il2p-I1.wav
+
+@GEN_PACKETS_BIN@ -B9600 -I0 -n 100 -o test96-il2p-I0.wav
+@ATEST_BIN@ -B9600 -L64 -G68 test96-il2p-I0.wav
+
+
+@GEN_PACKETS_BIN@ -B9600 -i1 -n 100 -o test96-il2p-i1.wav
+@ATEST_BIN@ -B9600 -L70 -G74 test96-il2p-i1.wav
+@ATEST_BIN@ -B9600 -P+ -L73 -G77 test96-il2p-i1.wav
+
+@GEN_PACKETS_BIN@ -B9600 -i0 -n 100 -o test96-il2p-i0.wav
+@ATEST_BIN@ -B9600 -L67 -G71 test96-il2p-i0.wav
+
+