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 ![](tnc-test-cd-results.png) -Dire Wolf now includes [FX.25](https://en.wikipedia.org/wiki/FX.25_Forward_Error_Correction/) which adds Forward Error Correction (FEC) in a way that is completely compatible with existing systems. If both ends are capable of FX.25, your information will continue to get through under conditions where regular AX.25 is completely useless. +Dire Wolf 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. ![](fx25.png) -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 + +