diff --git a/.gitignore b/.gitignore index 3f8779d..659c845 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,7 @@ $RECYCLE.BIN/ *.lnk /use_this_sdk *.dSYM + +# cmake +build/ +tmp/ \ No newline at end of file diff --git a/CHANGES.md b/CHANGES.md index 30ca455..5097db7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,67 @@ # Revision History # +## Version 1.6 -- October 2020 ## + + +### New Build Procedure: ### + + +- Rather than trying to keep a bunch of different platform specific Makefiles in sync, "cmake" is now used for greater portability and easier maintenance. + +- README.md has a quick summary of the process. More details in the ***User Guide***. + + +### New Features: ### + + +- "-X" option enables FX.25 transmission. FX.25 reception is always enabled so you don't need to do anything special. "What is FX.25?" you might ask. It is forward error correction (FEC) added in a way that is completely compatible with an ordinary AX.25 frame. See new document ***AX25\_plus\_FEC\_equals\_FX25.pdf*** for details. + +- Receive AIS location data from ships. Enable by using "-B AIS" command line option or "MODEM AIS" in the configuration file. AIS NMEA sentences are encapsulated in APRS user-defined data with a "{DA" prefix. This uses 9600 bps so you need to use wide band audio, not what comes out of the speaker. There is also a "-A" option to generate APRS Object Reports. + +- Receive Emergency Alert System (EAS) Specific Area Message Encoding (SAME). Enable by using "-B EAS" command line option or "MODEM EAS" in the configuration file. EAS SAME messages are encapsulated in APRS user-defined data with a "{DE" prefix. This uses low speed AFSK so speaker output is fine. + +- "-t" option now accepts more values to accommodate inconsistent handling of text color control codes by different terminal emulators. The default, 1, should work with most modern terminal types. If the colors are not right, try "-t 9" to see the result of the different choices and pick the best one. If none of them look right, file a bug report and specify: operating system version (e.g. Raspbian Buster), terminal emulator type and version (e.g. LXTerminal 0.3.2). Include a screen capture. + + +- "-g" option to force G3RUH mode for lower speeds where a different modem type may be the default. + +- 2400 bps compatibility with MFJ-2400. See ***2400-4800-PSK-for-APRS-Packet-Radio.pdf*** for details + +- "atest -h" will display the frame in hexadecimal for closer inspection. + +- Add support for Multi-GNSS NMEA sentences. + + + +### Bugs Fixed: ### + +- Proper counting of frames in transmit queue for AGW protocol 'Y' command. + + + +### New Documentation: ### + +- ***AX.25 + FEC = FX.25*** + +- ***AIS Reception*** + +- ***AX.25 Throughput: Why is 9600 bps Packet Radio only twice as fast as 1200?*** + +- [***Ham Radio of Things (HRoT) - IoT over Ham Radio***](https://github.com/wb2osz/hrot) + +- [***EAS SAME to APRS Message Converter***](https://github.com/wb2osz/eas2aprs) + +- [***Dire Wolf PowerPoint Slide Show***](https://github.com/wb2osz/direwolf-presentation) + +### Notes: ### + +The Windows binary distribution now uses gcc (MinGW) version 7.4.0. +The Windows version is built for both 32 and 64 bit operating systems. +Use the 64 bit version if possible; it runs considerably faster. + + + ## Version 1.5 -- September 2018 ## diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9e710f5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,343 @@ +cmake_minimum_required(VERSION 3.1.0) + +project(direwolf) + +# configure version +set(direwolf_VERSION_MAJOR "1") +set(direwolf_VERSION_MINOR "6") +set(direwolf_VERSION_PATCH "0") +set(direwolf_VERSION_SUFFIX "") + +# options +option(FORCE_SSE "Compile with SSE instruction only" OFF) +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) +# UNITTEST option must be after CMAKE_BUILT_TYPE + +# where cmake find custom modules +list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules) + +# fix c standard used on the project +set(CMAKE_C_STANDARD 99) + +# Set additional project information +set(COMPANY "wb2osz") +add_definitions("-DCOMPANY=\"${COMPANY}\"") +set(APPLICATION_NAME "Dire Wolf") +add_definitions("-DAPPLICATION_NAME=\"${APPLICATION_NAME}\"") +set(APPLICATION_MAINTAINER="John Langner, WB2OSZ") +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/") + # we can also use `git describe --tags` + execute_process(COMMAND "${GIT_EXECUTABLE}" rev-parse --short HEAD + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + RESULT_VARIABLE res + OUTPUT_VARIABLE out + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res) + string(REGEX REPLACE "^v([0-9]+)\.([0-9]+)\.([0-9]+)-" "" git_commit ${out}) + set(direwolf_VERSION_SUFFIX "-${git_commit}") + set(direwolf_VERSION_COMMIT "${git_commit}") + endif() +endif() + +# set variables +set(direwolf_VERSION "${direwolf_VERSION_MAJOR}.${direwolf_VERSION_MINOR}.${direwolf_VERSION_PATCH}${direwolf_VERSION_SUFFIX}") +message(STATUS "${APPLICATION_NAME} Version: ${direwolf_VERSION}") +add_definitions("-DIREWOLF_VERSION=\"${direwolf_VERSION}\"") +add_definitions("-DMAJOR_VERSION=${direwolf_VERSION_MAJOR}") +add_definitions("-DMINOR_VERSION=${direwolf_VERSION_MINOR}") +if(direwolf_VERSION_COMMIT) + add_definitions("-DEXTRA_VERSION=${direwolf_VERSION_COMMIT}") +endif() + +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_GEOTRANZ_DIR "${CUSTOM_EXTERNAL_DIR}/geotranz") +set(CUSTOM_DATA_DIR "${CMAKE_SOURCE_DIR}/data") +set(CUSTOM_SCRIPTS_DIR "${CMAKE_SOURCE_DIR}/scripts") +set(CUSTOM_TELEMETRY_DIR "${CUSTOM_SCRIPTS_DIR}/telemetry-toolkit") +set(CUSTOM_CONF_DIR "${CMAKE_SOURCE_DIR}/conf") +set(CUSTOM_DOC_DIR "${CMAKE_SOURCE_DIR}/doc") +set(CUSTOM_MAN_DIR "${CMAKE_SOURCE_DIR}/man") +set(CUSTOM_TEST_DIR "${CMAKE_SOURCE_DIR}/test") +set(CUSTOM_TEST_SCRIPTS_DIR "${CUSTOM_TEST_DIR}/scripts") +set(CUSTOM_SHELL_SHABANG "#!/bin/sh -e") + +# cpack variables +set(CPACK_GENERATOR "ZIP") +set(CPACK_STRIP_FILES true) +set(CPACK_PACKAGE_NAME "${CMAKE_PROJECT_NAME}") +# This has architecture of the build machine, not the target platform. +# e.g. Comes out as x86_64 when building for i686 target platform. +#set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${direwolf_VERSION}_${CMAKE_SYSTEM_PROCESSOR}") +# We don't know the target yet so this is set after FindCPUflags. +set(CPACK_PACKAGE_CONTACT "https://github.com/wb2osz/direwolf") +SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Dire Wolf is an AX.25 soundcard TNC, digipeater, APRS IGate, GPS tracker, and APRStt gateway") +set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/README.md") +set(CPACK_RESOURCE_FILE_README "${CMAKE_SOURCE_DIR}/README.md") +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") +set(CPACK_SOURCE_IGNORE_FILES "${PROJECT_BINARY_DIR};/.git/;.gitignore;menu.yml;.travis.yml;.appveyor.yml;default.nix;.envrc;TODOs.org;/.scripts/") +SET(CPACK_PACKAGE_VERSION "${direwolf_VERSION}") +SET(CPACK_PACKAGE_VERSION_MAJOR "${direwolf_VERSION_MAJOR}") +SET(CPACK_PACKAGE_VERSION_MINOR "${direwolf_VERSION_MINOR}") +SET(CPACK_PACKAGE_VERSION_PATCH "${direwolf_VERSION_PATCH}") +SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libasound2,libgps23") + +# if we don't set build_type +if(NOT DEFINED CMAKE_BUILD_TYPE OR "${CMAKE_BUILD_TYPE}" STREQUAL "") + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) +endif() +message(STATUS "Build type set to: ${CMAKE_BUILD_TYPE}") +message("CMake system: ${CMAKE_SYSTEM_NAME}") + +# Unittest should be on for dev builds and off for releases. +if(CMAKE_BUILD_TYPE MATCHES "Release") + option(UNITTEST "Build unittest binaries." OFF) +else() + option(UNITTEST "Build unittest binaries." ON) +endif() + +# set compiler +include(FindCompiler) + +# find cpu flags (and set compiler) +include(FindCPUflags) + +if(${ARCHITECTURE} MATCHES "x86") + set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${direwolf_VERSION}_i686") +else() + set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${direwolf_VERSION}_${ARCHITECTURE}") +endif() + +# auto include current directory +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# set OS dependant variables +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + set(LINUX TRUE) + + 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 "FreeBSD") + set(FREEBSD TRUE) + configure_file("${CMAKE_SOURCE_DIR}/cmake/cpack/${CMAKE_PROJECT_NAME}.desktop.in" + "${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.desktop" @ONLY) + +elseif(APPLE) + if("${CMAKE_OSX_DEPLOYMENT_TARGET}" STREQUAL "") + message(STATUS "Build for macOS target: local version") + else() + message(STATUS "Build for macOS target: ${CMAKE_OSX_DEPLOYMENT_TARGET}") + endif() + + # prepend path to find_*() + set(CMAKE_FIND_ROOT_PATH "/opt/local") + + set(CMAKE_MACOSX_RPATH ON) + message(STATUS "RPATH support: ${CMAKE_MACOSX_RPATH}") + +elseif (WIN32) + if(NOT VS2015 AND NOT VS2017) + message(FATAL_ERROR "You must use Microsoft Visual Studio 2015 or 2017 as compiler") + endif() + + # compile with full multicore + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + + set(CUSTOM_SHELL_BIN "") +endif() + +if (C_CLANG OR C_GCC) + # _BSD_SOURCE is deprecated we need to use _DEFAULT_SOURCE. + # + # That works find for more modern compilers but we have issues with: + # Centos 7, gcc 4.8.5, glibc 2.17 + # Centos 6, gcc 4.4.7, glibc 2.12 + # + # CentOS 6 & 7: Without -D_BSD_SOURCE, we get Warning: Implicit declaration of + # functions alloca, cfmakeraw, scandir, setlinebuf, strcasecmp, strncasecmp, and strsep. + # When a function (like strsep) returns a pointer, the compiler instead assumes a 32 bit + # int and sign extends it out to be a 64 bit pointer. Use the pointer and Kaboom! + # + # CentOS 6: We have additional problem. Without -D_POSIX_C_SOURCE=199309L, we get + # implicit declaration of function clock_gettime and the linker can't find it. + # + # It turns out that -D_GNU_SOURCE can be used instead of both of those. For more information, + # see https://www.gnu.org/software/libc/manual/html_node/Feature-Test-Macros.html + # + # Why was this not an issue before? If gcc is used without the -std=c99 option, + # it is perfectly happy with clock_gettime, strsep, etc. but with the c99 option, it no longer + # recognizes a bunch of commonly used functions. Using _GNU_SOURCE, rather than _DEFAULT_SOURCE + # solves the problem for CentOS 6 & 7. This also makes -D_XOPEN_SOURCE= unnecessary. + # I hope it doesn't break with newer versions of glibc. + # + # 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. + # + ###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 ${EXTRA_FLAGS}") + endif() + # + # + # -lm is needed for functions in math.h + if (LINUX) + # We have another problem with CentOS 6. clock_gettime() is in librt so we need -lrt. + # The clock_* functions were moved into gnu libc for version 2.17. + # https://sourceware.org/ml/libc-announce/2012/msg00001.html + # If using gnu libc 2.17, or later, the -lrt is no longer needed but doesn't hurt. + # I'm making this conditional on LINUX because it is not needed for BSD and MacOSX. + link_libraries("-lrt -lm") + else() + link_libraries("-lm") + endif() +elseif (C_MSVC) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -W3 -MP ${EXTRA_FLAGS}") +endif() + +if (C_CLANG) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ferror-limit=1") +elseif (C_GCC) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fmax-errors=1") +endif() + +# set installation directories +if (WIN32 OR CYGWIN) + set(INSTALL_BIN_DIR ".") + set(INSTALL_DOC_DIR "doc") + set(INSTALL_CONF_DIR ".") + set(INSTALL_SCRIPTS_DIR "scripts") + set(INSTALL_MAN_DIR "man") + set(INSTALL_DATA_DIR "data") +else() + set(INSTALL_BIN_DIR "bin") + set(INSTALL_DOC_DIR "share/doc/${CMAKE_PROJECT_NAME}") + set(INSTALL_CONF_DIR "${INSTALL_DOC_DIR}/conf") + set(INSTALL_SCRIPTS_DIR "${INSTALL_DOC_DIR}/scripts") + if(FREEBSD) + set(INSTALL_MAN_DIR "man/man1") + else() + set(INSTALL_MAN_DIR "share/man/man1") + endif() + set(INSTALL_DATA_DIR "share/${PROJECT_NAME}") +endif(WIN32 OR CYGWIN) + +# requirements +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + +find_package(GPSD) +if(GPSD_FOUND) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DENABLE_GPSD") +else() + set(GPSD_INCLUDE_DIRS "") + set(GPSD_LIBRARIES "") +endif() + +find_package(hamlib) +if(HAMLIB_FOUND) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_HAMLIB") +else() + set(HAMLIB_INCLUDE_DIRS "") + set(HAMLIB_LIBRARIES "") +endif() + +if(LINUX) + find_package(ALSA REQUIRED) + if(ALSA_FOUND) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_ALSA") + endif() + + find_package(udev) + if(UDEV_FOUND) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_CM108") + endif() + +elseif (NOT WIN32 AND NOT CYGWIN) + find_package(Portaudio REQUIRED) + if(PORTAUDIO_FOUND) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_PORTAUDIO") + endif() + +else() + set(ALSA_INCLUDE_DIRS "") + set(ALSA_LIBRARIES "") + set(UDEV_INCLUDE_DIRS "") + set(UDEV_LIBRARIES "") + set(PORTAUDIO_INCLUDE_DIRS "") + set(PORTAUDIO_LIBRARIES "") +endif() + +# manage and fetch new data +add_subdirectory(data) + +# external libraries +add_subdirectory(${CUSTOM_GEOTRANZ_DIR}) +add_subdirectory(${CUSTOM_REGEX_DIR}) +add_subdirectory(${CUSTOM_MISC_DIR}) + +# direwolf source code and utilities +add_subdirectory(src) + +# ctest +if(UNITTEST) + message(STATUS "Build unit test binaries") + include(CTest) + enable_testing() + add_subdirectory(test) +endif(UNITTEST) + +# manage scripts +add_subdirectory(scripts) + +# manage config +add_subdirectory(conf) + +# install basic docs +install(FILES ${CMAKE_SOURCE_DIR}/CHANGES.md DESTINATION ${INSTALL_DOC_DIR}) +install(FILES ${CMAKE_SOURCE_DIR}/LICENSE DESTINATION ${INSTALL_DOC_DIR}) +install(FILES ${CMAKE_SOURCE_DIR}/external/LICENSE DESTINATION ${INSTALL_DOC_DIR}/external) +add_subdirectory(doc) +add_subdirectory(man) + +# install desktop link +if (LINUX OR FREEBSD) + install(FILES ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.desktop DESTINATION share/applications) + install(FILES ${CMAKE_SOURCE_DIR}/cmake/cpack/${CMAKE_PROJECT_NAME}_icon.png DESTINATION share/pixmaps) +endif() + +############ uninstall target ################ +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/include/uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/uninstall.cmake" + IMMEDIATE @ONLY) + +add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P + ${CMAKE_CURRENT_BINARY_DIR}/uninstall.cmake) + +############ packaging ################ +add_subdirectory(cmake/cpack) diff --git a/LICENSE-dire-wolf.txt b/LICENSE similarity index 100% rename from LICENSE-dire-wolf.txt rename to LICENSE diff --git a/Makefile b/Makefile index 0ae5394..7b0dcf7 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,36 @@ -# Select proper Makefile for operating system. -# The Windows version is built with the help of Cygwin. -# In my case, I see CYGWIN_NT-6.1-WOW so we don't check for -# equal to some value. Your mileage my vary. - -win := $(shell uname | grep CYGWIN) -dar := $(shell uname | grep Darwin) - -ifneq ($(win),) - include Makefile.win -else ifeq ($(dar),Darwin) - include Makefile.macosx -else - include Makefile.linux -endif +all: + @echo "The build procedure has changed in version 1.6." + @echo "In general, it now looks like this:" + @echo " " + @echo "Download the source code:" + @echo " " + @echo " cd ~" + @echo " git clone https://www.github.com/wb2osz/direwolf" + @echo " cd direwolf" + @echo " " + @echo "Optional - Do this to get the latest development version" + @echo "rather than the latest stable release." + @echo " " + @echo " git checkout dev" + @echo " " + @echo "Build it. There are two new steps not used for earlier releases." + @echo " " + @echo " mkdir build && cd build" + @echo " cmake .." + @echo " make -j4" + @echo " " + @echo "Install:" + @echo " " + @echo " sudo make install" + @echo " make install-conf" + @echo " " + @echo "You will probably need to install additional applications and" + @echo "libraries depending on your operating system." + @echo "More details are in the README.md file." + @echo " " + @echo "Questions?" + @echo " " + @echo " - Extensive documentation can be found in the 'doc' directory." + @echo " - Join the discussion forum here: https://groups.io/g/direwolf" + @echo " " diff --git a/Makefile.linux b/Makefile.linux deleted file mode 100644 index 5010833..0000000 --- a/Makefile.linux +++ /dev/null @@ -1,988 +0,0 @@ -# -# Makefile for Linux version of Dire Wolf. -# - - - -APPS := direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc kissutil cm108 - -all : $(APPS) direwolf.desktop direwolf.conf - @echo " " - @echo "Next step - install with:" - @echo " " - @echo " sudo make install" - @echo " " - -CC := gcc - -# Just for fun, let's see how clang compares to gcc. First install like this: -# sudo apt-get update -# apt-cache search clang -# sudo apt-get install clang-3.5 -# -# CC := clang-3.5 - -# _XOPEN_SOURCE=600 and _DEFAULT_SOURCE=1 are needed for glibc >= 2.24. -# Explanation here: https://github.com/wb2osz/direwolf/issues/62 - -# There are a few source files where it had been necessary to define __USE_XOPEN2KXSI, -# __USE_XOPEN, or _POSIX_C_SOURCE. Doesn't seem to be needed after adding this. - -# The first assignment to CFLAGS and LDFLAGS uses +=, rather than :=, so -# we will inherit options already set in build environment. -# Explanation - https://github.com/wb2osz/direwolf/pull/38 - -CFLAGS += -O3 -pthread -Igeotranz -D_XOPEN_SOURCE=600 -D_DEFAULT_SOURCE=1 -Wall - -# That was fine for a recent Ubuntu and Raspbian Jessie. -# However, Raspbian wheezy was then missing declaration for strsep and definition of fd_set. - -CFLAGS += -D_BSD_SOURCE - -LDFLAGS += -lm -lpthread -lrt - - - -# -# The DSP filters spend a lot of time spinning around in little -# loops multiplying and adding arrays of numbers. The Intel "SSE" -# instructions, introduced in 1999 with the Pentium III series, -# can speed this up considerably. -# -# SSE2 instructions, added in 2000, don't seem to offer any advantage. -# -# -# Let's take a look at the effect of the compile options. -# -# -# Times are elapsed time to process Track 2 of the TNC test CD. -# -# i.e. "./atest 02_Track_2.wav" -# Default demodulator type is new "E" added for version 1.2. -# - -# -# ---------- x86 (32 bit) ---------- -# - -# -# gcc 4.6.3 running on Ubuntu 12.04.05. -# Intel(R) Celeron(R) CPU 2.53GHz. Appears to have only 32 bit instructions. -# Probably from around 2004 or 2005. -# -# When gcc is generating code for a 32 bit x86 target, it assumes the ancient -# i386 processor. This is good for portability but bad for performance. -# -# The code can run considerably faster by taking advantage of the SSE instructions -# available in the Pentium 3 or later. -# -# seconds options comments -# ------ ------- -------- -# 524 -# 183 -O2 -# 182 -O3 -# 183 -O3 -ffast-math (should be same as -Ofast) -# 184 -Ofast -# 189 -O3 -ffast-math -march=pentium -# 122 -O3 -ffast-math -msse -# 122 -O3 -ffast-math -march=pentium -msse -# 121 -O3 -ffast-math -march=pentium3 (this implies -msse) -# 120 -O3 -ffast-math -march=native -# -# Note that "-march=native" is essentially the same as "-march=pentium3." -# - -# If the compiler is generating code for the i386 target, we can -# get much better results by telling it we have at least a Pentium 3. - -arch := $(shell echo | gcc -E -dM - | grep __i386__) -ifneq ($(arch),) -CFLAGS += -march=pentium3 -endif - - -# -# ---------- x86_64 ---------- -# - -# -# gcc 4.8.2 running on Ubuntu 14.04.1. -# Intel Core 2 Duo from around 2007 or 2008. -# -# 64 bit target implies that we have SSE and probably even better vector instructions. -# -# seconds options comments -# ------ ------- -------- -# 245 -# 75 -01 -# 72 -02 -# 71 -03 -# 73 -O3 -march=native -# 42 -O3 -ffast-math -# 42 -Ofast (note below) -# 40 -O3 -ffast-math -march=native -# -# -# Note that "-Ofast" is a newer option roughly equivalent to "-O3 -ffast-math". -# I use the longer form because it is compatible with older compilers. -# -# Why don't I don't have "-march=native?" -# Older compilers don't recognize "native" as one of the valid options. -# One article said it was added with gcc 4.2 but I haven't verified that. -# - -# ---------- How does clang compare? - Ubuntu x86_64 ---------- -# -# I keep hearing a lot about "clang." Let's see how it compares with gcc. -# Simply use different compiler and keep all options the same. -# -# test case: atest 02_Track_2.wav -# -# gcc 4.8.4: 988 packets decoded in 40.503 seconds. 38.3 x realtime -# 988 packets decoded in 40.403 seconds. 38.4 x realtime -# -# clang 3.8.0-2: 988 packets decoded in 77.454 seconds. 20.0 x realtime -# 988 packets decoded in 77.232 seconds. 20.1 x realtime -# -# I'm not impressed. Almost twice as long. Maybe we need to try some other compiler options. -# -march=native did not help. -# Makefile.macosx, which uses clang, has these: -# -fvectorize -fslp-vectorize -fslp-vectorize-aggressive -# Those did not help. -# - - -useffast := $(shell gcc --help -v 2>/dev/null | grep ffast-math) -ifneq ($(useffast),) -CFLAGS += -ffast-math -endif - - -# -# ---------- ARM - Raspberry Pi 1 models ---------- -# -# Raspberry Pi (before RPi model 2), ARM11 (ARMv6 + VFP2) -# gcc (Debian 4.6.3-14+rpi1) 4.6.3 -# -# -# seconds options comments -# ------ ------- --------- -# 892 -O3 -# 887 -O3 -ffast-math -# x -O3 -ffast-math -march=native (error: bad value for -march switch) -# 887 -O3 -ffast-math -mfpu=vfp -# 890 -O3 -ffast-math -march=armv6zk -mcpu=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp -# -# -# The compiler, supplied with Raspbian, is configured with these options which are -# good for the pre version 2 models. -# --with-arch=armv6 --with-fpu=vfp --with-float=hard -# -# Run "gcc --help -v 2" and look near the end. -# -# - -# -# ---------- ARM - Raspberry Pi 2 ---------- -# -# Besides the higher clock speed, the Raspberry Pi 2 has the NEON instruction set -# which which should make things considerably faster. -# -# -# seconds options comments -# ------ ------- --------- -# 426 -O3 -ffast-math (already more than twice as fast) -# 429 -O3 -mfpu=neon -# 419 -O3 -mfpu=neon -funsafe-math-optimizations -# 412 -O3 -ffast-math -mfpu=neon -# 413 -O3 -ffast-math -mfpu=neon-vfpv4 -# 430 -O3 -ffast-math -mfpu=neon-vfpv4 -march=armv7-a -# 412 -O3 -ffast-math -mfpu=neon-vfpv4 -mtune=arm7 -# 410 -O3 -ffast-math -mfpu=neon-vfpv4 -funsafe-math-optimizations - -# -# gcc -march=armv7-a -mfpu=neon-vfpv4 -# -# I expected the -mfpu=neon option to have a much larger impact. -# Adding -march=armv7-a makes it slower! - -# -# If you compile with the RPi 2 specific options above and try to run it on the RPi -# model B (pre version 2), it will die with "illegal instruction." -# -# Dire Wolf is known to work on the BeagleBone, CubieBoard2, CHIP, etc. -# The best compiler options will depend on the specific type of processor -# and the compiler target defaults. -# - -neon := $(shell cat /proc/cpuinfo | grep neon) -ifneq ($(neon),) -CFLAGS += -mfpu=neon -endif - - -# -# You would expect "-march=native" to produce the fastest code. -# Why don't I use it here? -# -# 1. In my benchmarks, above, it has a negligible impact if any at all. -# 2. Some older versions of gcc don't recognize "native" as a valid choice. -# 3. Results are less portable. Not a consideration if you are -# building only for your own use but very important for anyone -# redistributing a "binary" version. -# -# If you are planning to distribute the binary version to other -# people (in some ham radio software collection, RPM, or DEB package), -# avoid fine tuning it for your particular computer. It could -# cause compatibility issues for those with older computers. -# - -# ---------- How does clang compare? - ARM - Raspberry Pi 2 ---------- -# -# I keep hearing a lot about "clang." Let's see how it compares with gcc. -# Simply use different compiler and keep all options the same. -# -# test case: atest 02_Track_2.wav -# -# gcc 4.9.2-10: 988 packets decoded in 353.025 seconds. 4.4 x realtime -# 988 packets decoded in 352.752 seconds. 4.4 x realtime -# -# clang 3.5.0-10: 988 packets decoded in 825.879 seconds. 1.9 x realtime -# 988 packets decoded in 831.469 seconds. 1.9 x realtime -# -# There might be a different set of options which produce faster code -# but the initial test doesn't look good. About 2.3 times as slow. - -# If you want to use OSS (for FreeBSD, OpenBSD) instead of -# ALSA (for Linux), comment out (or remove) the line below. -# TODO: Can we automate this somehow? - -alsa = 1 - -ifeq ($(wildcard /usr/include/pthread.h),) -$(error /usr/include/pthread.h does not exist. Install it with "sudo apt-get install libc6-dev" or "sudo yum install glibc-headers" ) -endif - -ifneq ($(alsa),) -CFLAGS += -DUSE_ALSA -LDFLAGS += -lasound -ifeq ($(wildcard /usr/include/alsa/asoundlib.h),) -$(error /usr/include/alsa/asoundlib.h does not exist. Install it with "sudo apt-get install libasound2-dev" or "sudo yum install alsa-lib-devel" ) -endif -endif - - -# Enable GPS if header file is present. -# Finding libgps.so* is more difficult because it -# is in different places on different operating systems. - -enable_gpsd := $(wildcard /usr/include/gps.h) -ifneq ($(enable_gpsd),) -CFLAGS += -DENABLE_GPSD -LDFLAGS += -lgps -endif - - -# Enable hamlib support if header file is present. - -enable_hamlib := $(wildcard /usr/include/hamlib/rig.h /usr/local/include/hamlib/rig.h) -ifneq ($(enable_hamlib),) -CFLAGS += -DUSE_HAMLIB -LDFLAGS += -lhamlib -endif - - -# Should enabling of this feature be strongly encouraged or -# is it quite specialized and of interest to a small audience? -# If, for some reason, can obtain the libudev-dev package, or -# don't want to install it, comment out the next 3 lines. - -#ifeq ($(wildcard /usr/include/libudev.h),) -#$(error /usr/include/libudev.h does not exist. Install it with "sudo apt-get install libudev-dev" or "sudo yum install libudev-devel" ) -#endif - - -# Enable cm108 PTT support if libudev header file is present. - -enable_cm108 := $(wildcard /usr/include/libudev.h) -ifneq ($(enable_cm108),) -CFLAGS += -DUSE_CM108 -LDFLAGS += -ludev -endif - - -# Name of current directory. -# Used to generate zip file name for distribution. - -z := $(notdir ${CURDIR}) - - - -# -------------------------------- Main application ----------------------------------------- - - - -direwolf : direwolf.o config.o recv.o demod.o dsp.o demod_afsk.o demod_psk.o demod_9600.o hdlc_rec.o \ - hdlc_rec2.o multi_modem.o rdq.o rrbb.o dlq.o \ - fcs_calc.o ax25_pad.o ax25_pad2.o xid.o \ - decode_aprs.o symbols.o server.o kiss.o kissserial.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \ - gen_tone.o audio.o audio_stats.o digipeater.o cdigipeater.o pfilter.o dedupe.o tq.o xmit.o morse.o \ - ptt.o beacon.o encode_aprs.o latlong.o encode_aprs.o latlong.o textcolor.o \ - dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o waypoint.o serial_port.o log.o telemetry.o \ - dwgps.o dwgpsnmea.o dwgpsd.o dtime_now.o mheard.o ax25_link.o cm108.o \ - misc.a geotranz.a - $(CC) -o $@ $^ $(LDFLAGS) - @echo " " -ifneq ($(enable_gpsd),) - @echo "\t>\tThis includes support for gpsd." -else - @echo "\t>\tThis does NOT include support for gpsd." -endif -ifneq ($(enable_hamlib),) - @echo "\t>\tThis includes support for hamlib." -else - @echo "\t>\tThis does NOT include support for hamlib." -endif -ifneq ($(enable_cm108),) - @echo "\t>\tThis includes support for CM108/CM119 PTT." -else - @echo "\t>\tThis does NOT include support for CM108/CM119 PTT." -endif - @echo " " - -# Optimization for slow processors. - -demod.o : fsk_fast_filter.h - -demod_afsk.o : fsk_fast_filter.h - - -fsk_fast_filter.h : gen_fff - ./gen_fff > fsk_fast_filter.h - -gen_fff : demod_afsk.c dsp.c textcolor.c - echo " " > tune.h - $(CC) $(CFLAGS) -DGEN_FFF -o $@ $^ $(LDFLAGS) - - -# -# The destination field is often used to identify the manufacturer/model. -# These are not hardcoded into Dire Wolf. Instead they are read from -# a file called tocalls.txt at application start up time. -# -# The original permanent symbols are built in but the "new" symbols, -# using overlays, are often updated. These are also read from files. -# -# You can obtain an updated copy by typing "make tocalls-symbols". -# This is not part of the normal build process. You have to do this explicitly. -# -# The locations below appear to be the most recent. -# The copy at http://www.aprs.org/tocalls.txt is out of date. -# - -.PHONY: tocalls-symbols -tocalls-symbols : - cp tocalls.txt tocalls.txt~ - wget http://www.aprs.org/aprs11/tocalls.txt -O tocalls.txt - -diff -Z tocalls.txt~ tocalls.txt - cp symbols-new.txt symbols-new.txt~ - wget http://www.aprs.org/symbols/symbols-new.txt -O symbols-new.txt - -diff -Z symbols-new.txt~ symbols-new.txt - cp symbolsX.txt symbolsX.txt~ - wget http://www.aprs.org/symbols/symbolsX.txt -O symbolsX.txt - -diff -Z symbolsX.txt~ symbolsX.txt - - -# ---------------------------------------- Other utilities included ------------------------------ - - -# Separate application to decode raw data. - -# First three use .c rather than .o because they depend on DECAMAIN definition. - -decode_aprs : decode_aprs.c kiss_frame.c ax25_pad.c dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o symbols.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o misc.a - $(CC) $(CFLAGS) -DDECAMAIN -o $@ $^ $(LDFLAGS) - - - -# Convert between text and touch tone representation. - -text2tt : tt_text.c misc.a - $(CC) $(CFLAGS) -DENC_MAIN -o $@ $^ $(LDFLAGS) - -tt2text : tt_text.c misc.a - $(CC) $(CFLAGS) -DDEC_MAIN -o $@ $^ $(LDFLAGS) - - -# Convert between Latitude/Longitude and UTM coordinates. - -ll2utm : ll2utm.c geotranz.a textcolor.o misc.a - $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) - -utm2ll : utm2ll.c geotranz.a textcolor.o misc.a - $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) - - -# Convert from log file to GPX. - -log2gpx : log2gpx.c textcolor.o misc.a - $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) - - -# Test application to generate sound. - -gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c dtmf.c textcolor.c dsp.c misc.a - $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) - -# Unit test for AFSK demodulator - -atest : atest.c demod.o demod_afsk.o demod_psk.o demod_9600.o \ - dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \ - fcs_calc.o ax25_pad.o decode_aprs.o dwgpsnmea.o \ - dwgps.o dwgpsd.o serial_port.o telemetry.o dtime_now.o latlong.o symbols.o tt_text.o textcolor.o \ - misc.a - $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) - - -# Multiple AGWPE network or serial port clients to test TNCs side by side. - -aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.o misc.a - $(CC) $(CFLAGS) -g -o $@ $^ - - -# Talk to a KISS TNC. -# Note: kiss_frame.c has conditional compilation on KISSUTIL. - -kissutil : kissutil.c kiss_frame.c ax25_pad.o fcs_calc.o textcolor.o serial_port.o dtime_now.o sock.o misc.a - $(CC) $(CFLAGS) -g -DKISSUTIL -o $@ $^ $(LDFLAGS) - - -# List USB audio adapters than can use GPIO for PTT. - -cm108 : cm108.c textcolor.o misc.a - $(CC) $(CFLAGS) -g -DCM108_MAIN -o $@ $^ $(LDFLAGS) - - -# Touch Tone to Speech sample application. - -ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o misc.a - $(CC) $(CFLAGS) -g -o $@ $^ - - -# ----------------------------------------- Libraries -------------------------------------------- - -# UTM, USNG, MGRS conversions. - -geotranz.a : error_string.o mgrs.o polarst.o tranmerc.o ups.o usng.o utm.o - ar -cr $@ $^ - -error_string.o : geotranz/error_string.c - $(CC) $(CFLAGS) -c -o $@ $^ - -mgrs.o : geotranz/mgrs.c - $(CC) $(CFLAGS) -c -o $@ $^ - -polarst.o : geotranz/polarst.c - $(CC) $(CFLAGS) -c -o $@ $^ - -tranmerc.o : geotranz/tranmerc.c - $(CC) $(CFLAGS) -c -o $@ $^ - -ups.o : geotranz/ups.c - $(CC) $(CFLAGS) -c -o $@ $^ - -usng.o : geotranz/usng.c - $(CC) $(CFLAGS) -c -o $@ $^ - -utm.o : geotranz/utm.c - $(CC) $(CFLAGS) -c -o $@ $^ - - -# Provide our own copy of strlcpy and strlcat because they are not included with Linux. -# We don't need the others in that same directory. - -misc.a : strlcpy.o strlcat.o - ar -cr $@ $^ - -strlcpy.o : misc/strlcpy.c - $(CC) $(CFLAGS) -I. -c -o $@ $^ - -strlcat.o : misc/strlcat.c - $(CC) $(CFLAGS) -I. -c -o $@ $^ - - - -# ------------------------------------- Installation ---------------------------------- - - - -# Generate apprpriate sample configuration file for this platform. -# Originally, there was one sample for all platforms. It got too cluttered -# and confusing saying, this is for windows, and this is for Linux, and this ... -# Trying to maintain 3 different versions in parallel is error prone. -# We now have a single generic version which can be used to generate -# the various platform specific versions. - -# generic.conf should be checked into source control. -# direwolf.conf should NOT. It is generated when compiling on the target platform. - -direwolf.conf : generic.conf - egrep '^C|^L' generic.conf | cut -c2-999 > direwolf.conf - - -# Where should we install it? - -# Something built from source and installed locally would normally go in /usr/local/... -# If not specified on the make command line, this is our default. - -DESTDIR ?= /usr/local - -# However, if you are preparing a "binary" DEB or RPM package, the installation location -# would normally be /usr/... instead. In this case, use a command line like this: -# -# make DESTDIR=/usr install - - - -# Command to "install" to system directories. Use "ginstall" for Mac. - -INSTALL=install - -# direwolf.desktop was previously handcrafted for the Raspberry Pi. -# It was hardcoded with lxterminal, /home/pi, and so on. -# In version 1.2, try to customize this to match other situations better. - -# TODO: Test this better. - - -direwolf.desktop : - @echo "Generating customized direwolf.desktop ..." - @echo '[Desktop Entry]' > $@ - @echo 'Type=Application' >> $@ -ifneq ($(wildcard /usr/bin/lxterminal),) - @echo "Exec=lxterminal -t \"Dire Wolf\" -e \"$(DESTDIR)/bin/direwolf\"" >> $@ -else ifneq ($(wildcard /usr/bin/lxterm),) - @echo "Exec=lxterm -hold -title \"Dire Wolf\" -bg white -e \"$(DESTDIR)/bin/direwolf\"" >> $@ -else - @echo "Exec=xterm -hold -title \"Dire Wolf\" -bg white -e \"$(DESTDIR)/bin/direwolf\"" >> $@ -endif - @echo 'Name=Dire Wolf' >> $@ - @echo 'Comment=APRS Soundcard TNC' >> $@ - @echo 'Icon=$(DESTDIR)/share/direwolf/pixmaps/dw-icon.png' >> $@ - @echo "Path=$(HOME)" >> $@ - @echo '#Terminal=true' >> $@ - @echo 'Categories=HamRadio' >> $@ - @echo 'Keywords=Ham Radio;APRS;Soundcard TNC;KISS;AGWPE;AX.25' >> $@ - - -# Installation into $(DESTDIR), usually /usr/local/... or /usr/... -# Needs to be run as root or with sudo. - - -.PHONY: install -install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png direwolf.desktop -# -# Applications, not installed with package manager, normally go in /usr/local/bin. -# /usr/bin is used instead when installing from .DEB or .RPM package. -# - $(INSTALL) -D --mode=755 direwolf $(DESTDIR)/bin/direwolf - $(INSTALL) -D --mode=755 decode_aprs $(DESTDIR)/bin/decode_aprs - $(INSTALL) -D --mode=755 text2tt $(DESTDIR)/bin/text2tt - $(INSTALL) -D --mode=755 tt2text $(DESTDIR)/bin/tt2text - $(INSTALL) -D --mode=755 ll2utm $(DESTDIR)/bin/ll2utm - $(INSTALL) -D --mode=755 utm2ll $(DESTDIR)/bin/utm2ll - $(INSTALL) -D --mode=755 aclients $(DESTDIR)/bin/aclients - $(INSTALL) -D --mode=755 log2gpx $(DESTDIR)/bin/log2gpx - $(INSTALL) -D --mode=755 gen_packets $(DESTDIR)/bin/gen_packets - $(INSTALL) -D --mode=755 atest $(DESTDIR)/bin/atest - $(INSTALL) -D --mode=755 ttcalc $(DESTDIR)/bin/ttcalc - $(INSTALL) -D --mode=755 kissutil $(DESTDIR)/bin/kissutil - $(INSTALL) -D --mode=755 cm108 $(DESTDIR)/bin/cm108 - $(INSTALL) -D --mode=755 dwespeak.sh $(DESTDIR)/bin/dwspeak.sh -# -# Telemetry Toolkit executables. Other .conf and .txt files will go into doc directory. -# - $(INSTALL) -D --mode=755 telemetry-toolkit/telem-balloon.pl $(DESTDIR)/bin/telem-balloon.pl - $(INSTALL) -D --mode=755 telemetry-toolkit/telem-bits.pl $(DESTDIR)/bin/telem-bits.pl - $(INSTALL) -D --mode=755 telemetry-toolkit/telem-data.pl $(DESTDIR)/bin/telem-data.pl - $(INSTALL) -D --mode=755 telemetry-toolkit/telem-data91.pl $(DESTDIR)/bin/telem-data91.pl - $(INSTALL) -D --mode=755 telemetry-toolkit/telem-eqns.pl $(DESTDIR)/bin/telem-eqns.pl - $(INSTALL) -D --mode=755 telemetry-toolkit/telem-parm.pl $(DESTDIR)/bin/telem-parm.pl - $(INSTALL) -D --mode=755 telemetry-toolkit/telem-seq.sh $(DESTDIR)/bin/telem-seq.sh - $(INSTALL) -D --mode=755 telemetry-toolkit/telem-unit.pl $(DESTDIR)/bin/telem-unit.pl - $(INSTALL) -D --mode=755 telemetry-toolkit/telem-volts.py $(DESTDIR)/bin/telem-volts.py -# -# Misc. data such as "tocall" to system mapping. -# - $(INSTALL) -D --mode=644 tocalls.txt $(DESTDIR)/share/direwolf/tocalls.txt - $(INSTALL) -D --mode=644 symbols-new.txt $(DESTDIR)/share/direwolf/symbols-new.txt - $(INSTALL) -D --mode=644 symbolsX.txt $(DESTDIR)/share/direwolf/symbolsX.txt -# -# For desktop icon. -# - $(INSTALL) -D --mode=644 dw-icon.png $(DESTDIR)/share/direwolf/pixmaps/dw-icon.png - $(INSTALL) -D --mode=644 direwolf.desktop $(DESTDIR)/share/applications/direwolf.desktop -# -# Documentation. Various plain text files and PDF. -# - $(INSTALL) -D --mode=644 CHANGES.md $(DESTDIR)/share/doc/direwolf/CHANGES.md - $(INSTALL) -D --mode=644 LICENSE-dire-wolf.txt $(DESTDIR)/share/doc/direwolf/LICENSE-dire-wolf.txt - $(INSTALL) -D --mode=644 LICENSE-other.txt $(DESTDIR)/share/doc/direwolf/LICENSE-other.txt -# -# ./README.md is an overview for the project main page. -# Maybe we could stick it in some other place. -# doc/README.md contains an overview of the PDF file contents and is more useful here. -# - $(INSTALL) -D --mode=644 doc/README.md $(DESTDIR)/share/doc/direwolf/README.md - $(INSTALL) -D --mode=644 doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf $(DESTDIR)/share/doc/direwolf/2400-4800-PSK-for-APRS-Packet-Radio.pdf - $(INSTALL) -D --mode=644 doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf $(DESTDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf - $(INSTALL) -D --mode=644 doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf $(DESTDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf - $(INSTALL) -D --mode=644 doc/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf $(DESTDIR)/share/doc/direwolf/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf - $(INSTALL) -D --mode=644 doc/APRS-Telemetry-Toolkit.pdf $(DESTDIR)/share/doc/direwolf/APRS-Telemetry-Toolkit.pdf - $(INSTALL) -D --mode=644 doc/APRStt-Implementation-Notes.pdf $(DESTDIR)/share/doc/direwolf/APRStt-Implementation-Notes.pdf - $(INSTALL) -D --mode=644 doc/APRStt-interface-for-SARTrack.pdf $(DESTDIR)/share/doc/direwolf/APRStt-interface-for-SARTrack.pdf - $(INSTALL) -D --mode=644 doc/APRStt-Listening-Example.pdf $(DESTDIR)/share/doc/direwolf/APRStt-Listening-Example.pdf - $(INSTALL) -D --mode=644 doc/Bluetooth-KISS-TNC.pdf $(DESTDIR)/share/doc/direwolf/Bluetooth-KISS-TNC.pdf - $(INSTALL) -D --mode=644 doc/Going-beyond-9600-baud.pdf $(DESTDIR)/share/doc/direwolf/Going-beyond-9600-baud.pdf - $(INSTALL) -D --mode=644 doc/Raspberry-Pi-APRS.pdf $(DESTDIR)/share/doc/direwolf/Raspberry-Pi-APRS.pdf - $(INSTALL) -D --mode=644 doc/Raspberry-Pi-APRS-Tracker.pdf $(DESTDIR)/share/doc/direwolf/Raspberry-Pi-APRS-Tracker.pdf - $(INSTALL) -D --mode=644 doc/Raspberry-Pi-SDR-IGate.pdf $(DESTDIR)/share/doc/direwolf/Raspberry-Pi-SDR-IGate.pdf - $(INSTALL) -D --mode=644 doc/Successful-APRS-IGate-Operation.pdf $(DESTDIR)/share/doc/direwolf/Successful-APRS-IGate-Operation.pdf - $(INSTALL) -D --mode=644 doc/User-Guide.pdf $(DESTDIR)/share/doc/direwolf/User-Guide.pdf - $(INSTALL) -D --mode=644 doc/WA8LMF-TNC-Test-CD-Results.pdf $(DESTDIR)/share/doc/direwolf/WA8LMF-TNC-Test-CD-Results.pdf -# -# Various sample config and other files go into examples under the doc directory. -# When building from source, these can be put in home directory with "make install-conf". -# When installed from .DEB or .RPM package, the user will need to copy these to -# the home directory or other desired location. -# - $(INSTALL) -D --mode=644 direwolf.conf $(DESTDIR)/share/doc/direwolf/examples/direwolf.conf - $(INSTALL) -D --mode=755 dw-start.sh $(DESTDIR)/share/doc/direwolf/examples/dw-start.sh - $(INSTALL) -D --mode=644 sdr.conf $(DESTDIR)/share/doc/direwolf/examples/sdr.conf - $(INSTALL) -D --mode=644 telemetry-toolkit/telem-m0xer-3.txt $(DESTDIR)/share/doc/direwolf/examples/telem-m0xer-3.txt - $(INSTALL) -D --mode=644 telemetry-toolkit/telem-balloon.conf $(DESTDIR)/share/doc/direwolf/examples/telem-balloon.conf - $(INSTALL) -D --mode=644 telemetry-toolkit/telem-volts.conf $(DESTDIR)/share/doc/direwolf/examples/telem-volts.conf -# -# "man" pages -# - $(INSTALL) -D --mode=644 man1/aclients.1 $(DESTDIR)/share/man/man1/aclients.1 - $(INSTALL) -D --mode=644 man1/atest.1 $(DESTDIR)/share/man/man1/atest.1 - $(INSTALL) -D --mode=644 man1/decode_aprs.1 $(DESTDIR)/share/man/man1/decode_aprs.1 - $(INSTALL) -D --mode=644 man1/direwolf.1 $(DESTDIR)/share/man/man1/direwolf.1 - $(INSTALL) -D --mode=644 man1/gen_packets.1 $(DESTDIR)/share/man/man1/gen_packets.1 - $(INSTALL) -D --mode=644 man1/kissutil.1 $(DESTDIR)/share/man/man1/kissutil.1 - $(INSTALL) -D --mode=644 man1/ll2utm.1 $(DESTDIR)/share/man/man1/ll2utm.1 - $(INSTALL) -D --mode=644 man1/log2gpx.1 $(DESTDIR)/share/man/man1/log2gpx.1 - $(INSTALL) -D --mode=644 man1/text2tt.1 $(DESTDIR)/share/man/man1/text2tt.1 - $(INSTALL) -D --mode=644 man1/tt2text.1 $(DESTDIR)/share/man/man1/tt2text.1 - $(INSTALL) -D --mode=644 man1/utm2ll.1 $(DESTDIR)/share/man/man1/utm2ll.1 -# -# Set group and mode of HID devices corresponding to C-Media USB Audio adapters. -# This will allow us to use the CM108/CM119 GPIO pins for PTT. -# - $(INSTALL) -D --mode=644 99-direwolf-cmedia.rules /etc/udev/rules.d/99-direwolf-cmedia.rules -# - @echo " " - @echo "If this is your first install, not an upgrade, type this to put a copy" - @echo "of the sample configuration file (direwolf.conf) in your home directory:" - @echo " " - @echo " make install-conf" - @echo " " - - -# Put sample configuration & startup files in home directory. -# This step would be done as ordinary user. -# Some people like to put the direwolf config file in /etc/ax25. -# Note that all of these are also in $(DESTDIR)/share/doc/direwolf/examples/. - -# The Raspberry Pi has ~/Desktop but Ubuntu does not. - -# TODO: Handle Linux variations correctly. - -# Version 1.4 - Add "-n" option to avoid clobbering existing, probably customized, config files. - -# dw-start.sh is greatly improved in version 1.4. -# It was moved from isntall-rpi to install-conf because it is not just for the RPi. - -.PHONY: install-conf -install-conf : direwolf.conf - cp -n direwolf.conf ~ - cp -n sdr.conf ~ - cp -n telemetry-toolkit/telem-m0xer-3.txt ~ - cp -n telemetry-toolkit/telem-*.conf ~ - chmod +x dw-start.sh - cp -n dw-start.sh ~ -ifneq ($(wildcard $(HOME)/Desktop),) - @echo " " - @echo "This will add a desktop icon on some systems." - @echo "This is known to work on Raspberry Pi but might not be compatible with other desktops." - @echo " " - @echo " make install-rpi" - @echo " " -endif - - -.PHONY: install-rpi -install-rpi : - ln -f -s $(DESTDIR)/share/applications/direwolf.desktop ~/Desktop/direwolf.desktop - - - -# ---------------------------------- Automated Smoke Test -------------------------------- - - - -# Combine some unit tests into a single regression sanity check. - - -check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test xidtest dtmftest check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem4800 - -# Can we encode and decode at popular data rates? - -check-modem1200 : gen_packets atest - ./gen_packets -n 100 -o /tmp/test12.wav - ./atest -F0 -PE -L63 -G71 /tmp/test12.wav - ./atest -F1 -PE -L70 -G75 /tmp/test12.wav - rm /tmp/test12.wav - -check-modem300 : gen_packets atest - ./gen_packets -B300 -n 100 -o /tmp/test3.wav - ./atest -B300 -F0 -L68 -G69 /tmp/test3.wav - ./atest -B300 -F1 -L73 -G75 /tmp/test3.wav - rm /tmp/test3.wav - -check-modem9600 : gen_packets atest - ./gen_packets -B9600 -n 100 -o /tmp/test96.wav - ./atest -B9600 -F0 -L50 -G54 /tmp/test96.wav - ./atest -B9600 -F1 -L55 -G59 /tmp/test96.wav - rm /tmp/test96.wav - -check-modem19200 : gen_packets atest - ./gen_packets -r 96000 -B19200 -n 100 -o /tmp/test19.wav - ./atest -B19200 -F0 -L55 -G59 /tmp/test19.wav - ./atest -B19200 -F1 -L60 -G64 /tmp/test19.wav - rm /tmp/test19.wav - -check-modem2400 : gen_packets atest - ./gen_packets -B2400 -n 100 -o /tmp/test24.wav - ./atest -B2400 -F0 -L70 -G78 /tmp/test24.wav - ./atest -B2400 -F1 -L80 -G87 /tmp/test24.wav - rm /tmp/test24.wav - -check-modem4800 : gen_packets atest - ./gen_packets -B2400 -n 100 -o /tmp/test48.wav - ./atest -B2400 -F0 -L70 -G79 /tmp/test48.wav - ./atest -B2400 -F1 -L80 -G90 /tmp/test48.wav - rm /tmp/test48.wav - - -# Unit test for inner digipeater algorithm - -.PHONY : dtest -dtest : digipeater.c dedupe.c pfilter.c \ - ax25_pad.o fcs_calc.o tq.o textcolor.o \ - decode_aprs.o dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o misc.a - $(CC) $(CFLAGS) -DDIGITEST -o $@ $^ $(LDFLAGS) - ./dtest - rm dtest - - -# Unit test for APRStt tone sequence parsing. - -.PHONY : ttest -ttest : aprs_tt.c tt_text.c latlong.o textcolor.o misc.a geotranz.a misc.a - $(CC) $(CFLAGS) -DTT_MAIN -o $@ $^ $(LDFLAGS) - ./ttest - rm ttest - - -# Unit test for APRStt tone sequence / text conversions. - -.PHONY: tttexttest -tttexttest : tt_text.c textcolor.o misc.a - $(CC) $(CFLAGS) -DTTT_TEST -o $@ $^ $(LDFLAGS) - ./tttexttest - rm tttexttest - - -# Unit test for Packet Filtering. - -.PHONY: pftest -pftest : pfilter.c ax25_pad.o textcolor.o fcs_calc.o decode_aprs.o dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o latlong.o symbols.o telemetry.o tt_text.o misc.a - $(CC) $(CFLAGS) -DPFTEST -o $@ $^ $(LDFLAGS) - ./pftest - rm pftest - -# Unit test for telemetry decoding. - -.PHONY: tlmtest -tlmtest : telemetry.c ax25_pad.o fcs_calc.o textcolor.o misc.a - $(CC) $(CFLAGS) -DTEST -o $@ $^ $(LDFLAGS) - ./tlmtest - rm tlmtest - -# Unit test for location coordinate conversion. - -.PHONY: lltest -lltest : latlong.c textcolor.o misc.a - $(CC) $(CFLAGS) -DLLTEST -o $@ $^ $(LDFLAGS) - ./lltest - rm lltest - -# Unit test for encoding position & object report. - -.PHONY: enctest -enctest : encode_aprs.c latlong.c textcolor.c misc.a - $(CC) $(CFLAGS) -DEN_MAIN -o $@ $^ $(LDFLAGS) - ./enctest - rm enctest - - -# Unit test for KISS encapsulation. - -.PHONY: kisstest -kisstest : kiss_frame.c - $(CC) $(CFLAGS) -DKISSTEST -o $@ $^ $(LDFLAGS) - ./kisstest - rm kisstest - -# Unit test for constructing frames besides UI. - -.PHONY: pad2test -pad2test : ax25_pad2.c ax25_pad.c fcs_calc.o textcolor.o misc.a - $(CC) $(CFLAGS) -DPAD2TEST -o $@ $^ $(LDFLAGS) - ./pad2test - rm pad2test - - -# Unit Test for XID frame encode/decode. - -.PHONY: xidtest -xidtest : xid.c textcolor.o misc.a - $(CC) $(CFLAGS) -DXIDTEST -o $@ $^ $(LDFLAGS) - ./xidtest - rm xidtest - - -# Unit Test for DTMF encode/decode. - -.PHONY: dtmftest -dtmftest : dtmf.c textcolor.o - $(CC) $(CFLAGS) -DDTMF_TEST -o $@ $^ $(LDFLAGS) - ./dtmftest - rm dtmftest - - - -# ----------------------------- Manual tests and experiments --------------------------- - -# These are not included in a normal build. Might be broken. - -# Unit test for IGate - -itest : igate.c textcolor.c ax25_pad.c fcs_calc.c textcolor.o misc.a - $(CC) $(CFLAGS) -DITEST -o $@ $^ - ./itest - -# Unit test for UDP reception with AFSK demodulator. -# Temporary during development. Might not be useful anymore. - -udptest : udp_test.c demod.o dsp.o demod_afsk.o demod_psk.o demod_9600.o hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \ - fcs_calc.o ax25_pad.o decode_aprs.o symbols.o textcolor.o misc.a - $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) - ./udptest - -# For demodulator tweaking experiments. -# Dependencies of demod*.c, rather than .o, are intentional. - -demod.o : tune.h - -demod_afsk.o : tune.h - -demod_9600.o : tune.h - -demod_psk.o : tune.h - -tune.h : - echo " " > tune.h - - -testagc : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.o hdlc_rec2.o multi_modem.o rrbb.o \ - fcs_calc.o ax25_pad.o decode_aprs.o telemetry.o dtime_now.o latlong.o symbols.o tune.h textcolor.o misc.a - $(CC) $(CFLAGS) -o atest $^ $(LDFLAGS) - ./atest 02_Track_2.wav | grep "packets decoded in" > atest.out - - -testagc96 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c demod_9600.c \ - dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \ - rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \ - dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o latlong.o \ - symbols.o tt_text.o textcolor.o telemetry.o dtime_now.o \ - misc.a - rm -f atest96 - $(CC) $(CFLAGS) -o atest96 $^ $(LDFLAGS) - ./atest96 -B 9600 ../walkabout9600c.wav | grep "packets decoded in" >atest.out - #./atest96 -B 9600 noisy96.wav | grep "packets decoded in" >atest.out - #./atest96 -B 9600 19990303_0225_9600_8bis_22kHz.wav | grep "packets decoded in" >atest.out - #./atest96 -B 9600 19990303_0225_9600_16bit_22kHz.wav | grep "packets decoded in" >atest.out - #./atest96 -B 9600 -P + z8-22k.wav| grep "packets decoded in" >atest.out - #./atest96 -B 9600 test9600.wav | grep "packets decoded in" >atest.out - echo " " > tune.h - - - - -# ------------------------------- Source distribution --------------------------------- - -# probably obsolete and can be removed after move to github. - - - -.PHONY: dist-src -dist-src : README.md CHANGES.md - doc/User-Guide.pdf doc/Raspberry-Pi-APRS.pdf \ - doc/Raspberry-Pi-APRS-Tracker.pdf doc/APRStt-Implementation-Notes.pdf \ - dw-start.sh dwespeak.bat dwespeak.sh \ - tocalls.txt symbols-new.txt symbolsX.txt direwolf.spec - rm -f fsk_fast_filter.h - echo " " > tune.h - rm -f ../$z-src.zip - (cd .. ; zip $z-src.zip \ - $z/README.md \ - $z/CHANGES.md \ - $z/LICENSE* \ - $z/doc/User-Guide.pdf \ - $z/doc/Raspberry-Pi-APRS.pdf \ - $z/doc/Raspberry-Pi-APRS-Tracker.pdf \ - $z/doc/APRStt-Implementation-Notes.pdf \ - $z/doc/APRS-Telemetry-Toolkit.pdf \ - $z/Makefile* \ - $z/*.c $z/*.h \ - $z/regex/* $z/misc/* $z/geotranz/* \ - $z/man1/* \ - $z/generic.conf \ - $z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \ - $z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \ - $z/dw-start.sh $z/direwolf.spec \ - $z/dwespeak.bat $z/dwespeak.sh \ - $z/telemetry-toolkit/* ) - - -# ----------------------------------------------------------------------------------------- - - -.PHONY: clean -clean : - rm -f $(APPS) gen_fff tune.h fsk_fast_filter.h *.o *.a direwolf.desktop - - -depend : $(wildcard *.c) - makedepend -f $(lastword $(MAKEFILE_LIST)) -- $(CFLAGS) -- $^ - - -# -# The following is updated by "make depend" -# -# DO NOT DELETE - - diff --git a/Makefile.macosx b/Makefile.macosx deleted file mode 100644 index 70af2ea..0000000 --- a/Makefile.macosx +++ /dev/null @@ -1,555 +0,0 @@ -# -# Makefile for Macintosh 10.6+ version of Dire Wolf. -# - -# TODO: This is a modified version of Makefile.linux and it -# has fallen a little behind. For example, it is missing the check target. -# It would be more maintainable if we could use a single file for both. -# The differences are not that great. -# Maybe the most of the differences could go in to platform specific include -# files rather than cluttering it up with too many if blocks. - -# Changes: -# -# 16 Dec 2015 -# 1. Added condition check for gps/gpsd code. Commented out due to 32/64 bit -# library issues. Macports gpsd build problem. -# 2. SDK version checks are now performed by a bash script 'search_sdks.sh'. -# This should resolve the varied locations Apple stored the SDKs on the different -# Xcode/OS versions. Executing 'make' on the first pass asks the operator -# which SDK he/she wishes to use. Executing 'make clean' resets the SDK -# selection and operator intervention is once again required. Selected SDK -# information resides in a file named './use_this_sdk' in the current working -# directory. -# 3. Removed fsk_fast_filter.h from atest receipe, clang compiler was having -# a hissy fit. Not check with GCC. - -APPS := direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc kissutil - -all : $(APPS) direwolf.conf - @echo " " - @echo "Next step install with: " - @echo " " - @echo " sudo make install" - @echo " " - @echo " " - -SYS_LIBS := -SYS_MIN := - -SYS_LIBS := $(shell ./search_sdks.sh) -EXTRA_CFLAGS := -DARWIN_CC := $(shell which clang) -ifeq (${DARWIN_CC},) -DARWIN_CC := $(shell which gcc) -EXTRA_CFLAGS := -else -EXTRA_CFLAGS := -fvectorize -fslp-vectorize -fslp-vectorize-aggressive -pthread -endif - -# Change as required in support of the available libraries - -UNAME_M := $(shell uname -m) -ifeq (${UNAME_M},x86_64) -CC := $(DARWIN_CC) -m64 $(SYS_LIBS) $(SYS_MIN) -else -CC := $(DARWIN_CC) -m32 $(SYS_LIBS) $(SYS_MIN) -endif - -# _XOPEN_SOURCE=600 and _DEFAULT_SOURCE=1 are needed for glibc >= 2.24. -# Explanation here: https://github.com/wb2osz/direwolf/issues/62 - -CFLAGS := -Os -pthread -Igeotranz -D_XOPEN_SOURCE=600 -D_DEFAULT_SOURCE=1 $(EXTRA_CFLAGS) - -# That was fine for a recent Ubuntu and Raspbian Jessie. -# However, Raspbian wheezy was then missing declaration for strsep and definition of fd_set. - -CFLAGS += -D_BSD_SOURCE - - -# $(info $$CC is [${CC}]) - - -# If the compiler is generating code for a 32 bit target (-m32), we can -# get much better results by telling it we have at least a Pentium 3 -# which hass the SSE instructions. - -CFLAGS += -march=core2 -msse4.1 -std=gnu99 -#CFLAGS += -march=pentium3 -sse - - -# Add -ffastmath in only if compiler version recognizes it. - -useffast := $(shell gcc --help -v 2>/dev/null | grep ffast-math) -ifneq ($(useffast),) -CFLAGS += -ffast-math -endif - -#CFLAGS += -D_FORTIFY_SOURCE - -# Use PortAudio Library - -# Force static linking of portaudio if the static library is available. -PA_LIB_STATIC := $(shell find /opt/local/lib -maxdepth 1 -type f -name "libportaudio.a") -#$(info $$PA_LIB_STATIC is [${PA_LIB_STATIC}]) -ifeq (${PA_LIB_STATIC},) -LDLIBS += -L/opt/local/lib -lportaudio -else -LDLIBS += /opt/local/lib/libportaudio.a -endif - -# Include libraries portaudio requires. -LDLIBS += -framework CoreAudio -framework AudioUnit -framework AudioToolbox -LDLIBS += -framework Foundation -framework CoreServices - -CFLAGS += -DUSE_PORTAUDIO -I/opt/local/include - -# Uncomment following lines to enable GPS interface & tracker function. -# Not available for MacOSX (as far as I know). -# Although MacPorts has gpsd, wonder if it's the same thing. Add the check -# just in case it works. -# Well never mind, issue with Macports with 64bit libs ;-( leave the check in -# until (if ever) Macports fixes the issue. - -#GPS_HEADER := $(shell find /opt/local/include -maxdepth 1 -type f -name "gps.h") -#ifeq (${GPS_HEADER},) -#GPS_OBJS := -#else -#CFLAGS += -DENABLE_GPSD -#LDLIBS += -L/opt/local/lib -lgps -lgpsd -#GPS_OBJS := dwgps.o dwgpsnmea.o dwgpsd.o -#endif - -# Name of current directory. -# Used to generate zip file name for distribution. - -z := $(notdir ${CURDIR}) - - -# Main application. - -direwolf : direwolf.o aprs_tt.o audio_portaudio.o audio_stats.o ax25_link.o ax25_pad.o ax25_pad2.o beacon.o \ - config.o decode_aprs.o dedupe.o demod_9600.o demod_afsk.o demod_psk.o \ - demod.o digipeater.o cdigipeater.o dlq.o dsp.o dtime_now.o dtmf.o dwgps.o \ - encode_aprs.o encode_aprs.o fcs_calc.o fcs_calc.o gen_tone.o \ - geotranz.a hdlc_rec.o hdlc_rec2.o hdlc_send.o igate.o kiss_frame.o \ - kiss.o kissserial.o kissnet.o latlong.o latlong.o log.o morse.o multi_modem.o \ - waypoint.o serial_port.o pfilter.o ptt.o rdq.o recv.o rrbb.o server.o \ - symbols.o telemetry.o textcolor.o tq.o tt_text.o tt_user.o xid.o xmit.o \ - dwgps.o dwgpsnmea.o mheard.o - $(CC) $(CFLAGS) -o $@ $^ -lpthread $(LDLIBS) -lm - - -# Optimization for slow processors. - -demod.o : fsk_fast_filter.h - -demod_afsk.o : fsk_fast_filter.h - - -fsk_fast_filter.h : gen_fff - ./gen_fff > fsk_fast_filter.h - -gen_fff : demod_afsk.c dsp.c textcolor.c - echo " " > tune.h - $(CC) $(CFLAGS) -DGEN_FFF -o $@ $^ $(LDFLAGS) - - - -# UTM, USNG, MGRS conversions. - -geotranz.a : error_string.o mgrs.o polarst.o tranmerc.o ups.o usng.o utm.o - ar -cr $@ $^ - -error_string.o : geotranz/error_string.c - $(CC) $(CFLAGS) -c -o $@ $^ - -mgrs.o : geotranz/mgrs.c - $(CC) $(CFLAGS) -c -o $@ $^ - -polarst.o : geotranz/polarst.c - $(CC) $(CFLAGS) -c -o $@ $^ - -tranmerc.o : geotranz/tranmerc.c - $(CC) $(CFLAGS) -c -o $@ $^ - -ups.o : geotranz/ups.c - $(CC) $(CFLAGS) -c -o $@ $^ - -usng.o : geotranz/usng.c - $(CC) $(CFLAGS) -c -o $@ $^ - -utm.o : geotranz/utm.c - $(CC) $(CFLAGS) -c -o $@ $^ - - - -# Generate apprpriate sample configuration file for this platform. - -direwolf.conf : generic.conf - egrep '^C|^M' generic.conf | cut -c2-999 > direwolf.conf - - -# Where should we install it? -# Macports typically installs in /opt/local so maybe you want to use that instead. - -INSTALLDIR := /usr/local -#INSTALLDIR := /opt/local - -# TODO: Test this better. - -# Optional installation into INSTALLDIR. -# Needs to be run as root or with sudo. - -# Command to "install" to system directories. "install" for Linux. "ginstall" for Mac. - -INSTALL=ginstall - -.PHONY: install -install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png -# -# Applications. -# - $(INSTALL) direwolf $(INSTALLDIR)/bin - $(INSTALL) decode_aprs $(INSTALLDIR)/bin - $(INSTALL) text2tt $(INSTALLDIR)/bin - $(INSTALL) tt2text $(INSTALLDIR)/bin - $(INSTALL) ll2utm $(INSTALLDIR)/bin - $(INSTALL) utm2ll $(INSTALLDIR)/bin - $(INSTALL) aclients $(INSTALLDIR)/bin - $(INSTALL) log2gpx $(INSTALLDIR)/bin - $(INSTALL) gen_packets $(INSTALLDIR)/bin - $(INSTALL) atest $(INSTALLDIR)/bin - $(INSTALL) ttcalc $(INSTALLDIR)/bin - $(INSTALL) kissutil $(INSTALLDIR)/bin - $(INSTALL) dwespeak.sh $(INSTALLDIR)/bin -# -# Telemetry Toolkit executables. Other .conf and .txt files will go into doc directory. -# - $(INSTALL) telemetry-toolkit/telem-balloon.pl $(INSTALLDIR)/bin - $(INSTALL) telemetry-toolkit/telem-bits.pl $(INSTALLDIR)/bin - $(INSTALL) telemetry-toolkit/telem-data.pl $(INSTALLDIR)/bin - $(INSTALL) telemetry-toolkit/telem-data91.pl $(INSTALLDIR)/bin - $(INSTALL) telemetry-toolkit/telem-eqns.pl $(INSTALLDIR)/bin - $(INSTALL) telemetry-toolkit/telem-parm.pl $(INSTALLDIR)/bin - $(INSTALL) telemetry-toolkit/telem-unit.pl $(INSTALLDIR)/bin - $(INSTALL) telemetry-toolkit/telem-volts.py $(INSTALLDIR)/bin -# -# Misc. data such as "tocall" to system mapping. -# - $(INSTALL) -D --mode=644 tocalls.txt $(INSTALLDIR)/share/direwolf/tocalls.txt - $(INSTALL) -D --mode=644 symbols-new.txt $(INSTALLDIR)/share/direwolf/symbols-new.txt - $(INSTALL) -D --mode=644 symbolsX.txt $(INSTALLDIR)/share/direwolf/symbolsX.txt - $(INSTALL) -D --mode=644 dw-icon.png $(INSTALLDIR)/share/direwolf/dw-icon.png - -# -# Documentation. Various plain text files and PDF. -# - $(INSTALL) -D --mode=644 README.md $(INSTALLDIR)/share/doc/direwolf/README.md - $(INSTALL) -D --mode=644 CHANGES.md $(INSTALLDIR)/share/doc/direwolf/CHANGES.md - $(INSTALL) -D --mode=644 LICENSE-dire-wolf.txt $(INSTALLDIR)/share/doc/direwolf/LICENSE-dire-wolf.txt - $(INSTALL) -D --mode=644 LICENSE-other.txt $(INSTALLDIR)/share/doc/direwolf/LICENSE-other.txt -# -# ./README.md is an overview for the project main page. -# doc/README.md contains an overview of the PDF file contents and is more useful here. -# - $(INSTALL) -D --mode=644 doc/README.md $(INSTALLDIR)/share/doc/direwolf/README.md - $(INSTALL) -D --mode=644 doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf $(INSTALLDIR)/share/doc/direwolf/2400-4800-PSK-for-APRS-Packet-Radio.pdf - $(INSTALL) -D --mode=644 doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf $(INSTALLDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf - $(INSTALL) -D --mode=644 doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf $(INSTALLDIR)/share/doc/direwolf/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf - $(INSTALL) -D --mode=644 doc/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf $(INSTALLDIR)/share/doc/direwolf/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf - $(INSTALL) -D --mode=644 doc/APRS-Telemetry-Toolkit.pdf $(INSTALLDIR)/share/doc/direwolf/APRS-Telemetry-Toolkit.pdf - $(INSTALL) -D --mode=644 doc/APRStt-Implementation-Notes.pdf $(INSTALLDIR)/share/doc/direwolf/APRStt-Implementation-Notes.pdf - $(INSTALL) -D --mode=644 doc/APRStt-interface-for-SARTrack.pdf $(INSTALLDIR)/share/doc/direwolf/APRStt-interface-for-SARTrack.pdf - $(INSTALL) -D --mode=644 doc/APRStt-Listening-Example.pdf $(INSTALLDIR)/share/doc/direwolf/APRStt-Listening-Example.pdf - $(INSTALL) -D --mode=644 doc/Bluetooth-KISS-TNC.pdf $(INSTALLDIR)/share/doc/direwolf/Bluetooth-KISS-TNC.pdf - $(INSTALL) -D --mode=644 doc/Going-beyond-9600-baud.pdf $(INSTALLDIR)/share/doc/direwolf/Going-beyond-9600-baud.pdf - $(INSTALL) -D --mode=644 doc/Raspberry-Pi-APRS.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS.pdf - $(INSTALL) -D --mode=644 doc/Raspberry-Pi-APRS-Tracker.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS-Tracker.pdf - $(INSTALL) -D --mode=644 doc/Raspberry-Pi-SDR-IGate.pdf $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-SDR-IGate.pdf - $(INSTALL) -D --mode=644 doc/Successful-APRS-IGate-Operation.pdf $(INSTALLDIR)/share/doc/direwolf/Successful-APRS-IGate-Operation.pdf - $(INSTALL) -D --mode=644 doc/User-Guide.pdf $(INSTALLDIR)/share/doc/direwolf/User-Guide.pdf - $(INSTALL) -D --mode=644 doc/WA8LMF-TNC-Test-CD-Results.pdf $(INSTALLDIR)/share/doc/direwolf/WA8LMF-TNC-Test-CD-Results.pdf -# -# Sample config files also go into the doc directory. -# When building from source, these can be put in home directory with "make install-conf". -# When installed from .DEB or .RPM package, the user will need to copy these to -# the home directory or other desired location. -# Someone suggested that these could go into an "examples" subdirectory under doc. -# - $(INSTALL) -D --mode=644 direwolf.conf $(INSTALLDIR)/share/doc/direwolf/direwolf.conf - $(INSTALL) -D --mode=644 telemetry-toolkit/telem-m0xer-3.txt $(INSTALLDIR)/share/doc/direwolf/telem-m0xer-3.txt - $(INSTALL) -D --mode=644 telemetry-toolkit/telem-balloon.conf $(INSTALLDIR)/share/doc/direwolf/telem-balloon.conf - $(INSTALL) -D --mode=644 telemetry-toolkit/telem-volts.conf $(INSTALLDIR)/share/doc/direwolf/telem-volts.conf -# -# "man" pages -# - $(INSTALL) -D --mode=644 man1/aclients.1 $(INSTALLDIR)/man/man1/aclients.1 - $(INSTALL) -D --mode=644 man1/atest.1 $(INSTALLDIR)/man/man1/atest.1 - $(INSTALL) -D --mode=644 man1/decode_aprs.1 $(INSTALLDIR)/man/man1/decode_aprs.1 - $(INSTALL) -D --mode=644 man1/direwolf.1 $(INSTALLDIR)/man/man1/direwolf.1 - $(INSTALL) -D --mode=644 man1/gen_packets.1 $(INSTALLDIR)/man/man1/gen_packets.1 - $(INSTALL) -D --mode=644 man1/ll2utm.1 $(INSTALLDIR)/man/man1/ll2utm.1 - $(INSTALL) -D --mode=644 man1/log2gpx.1 $(INSTALLDIR)/man/man1/log2gpx.1 - $(INSTALL) -D --mode=644 man1/text2tt.1 $(INSTALLDIR)/man/man1/text2tt.1 - $(INSTALL) -D --mode=644 man1/tt2text.1 $(INSTALLDIR)/man/man1/tt2text.1 - $(INSTALL) -D --mode=644 man1/utm2ll.1 $(INSTALLDIR)/man/man1/utm2ll.1 -# - @echo " " - @echo "If this is your first install, not an upgrade, type this to put a copy" - @echo "of the sample configuration file (direwolf.conf) in your home directory:" - @echo " " - @echo " make install-conf" - @echo " " - - -# TODO: Should we put the sample direwolf.conf file somewhere like -# /usr/share/doc/direwolf/examples and add that to the -# end of the search path list? -# That would make it easy to see user customizations compared to the -# latest sample. - -# These would be done as ordinary user. - - -.PHONY: install-conf -install-conf : direwolf.conf - cp direwolf.conf ~ - cp telemetry-toolkit/telem-m0xer-3.txt ~ - cp telemetry-toolkit/telem-*.conf ~ - - -# Separate application to decode raw data. - -# First three use .c rather than .o because they depend on DECAMAIN definition. - -decode_aprs : decode_aprs.c kiss_frame.c ax25_pad.c dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o symbols.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o - $(CC) $(CFLAGS) -DDECAMAIN -o $@ $^ -lm - -# Convert between text and touch tone representation. - -text2tt : tt_text.c - $(CC) $(CFLAGS) -DENC_MAIN -o $@ $^ - -tt2text : tt_text.c - $(CC) $(CFLAGS) -DDEC_MAIN -o $@ $^ - - -# Convert between Latitude/Longitude and UTM coordinates. - -ll2utm : ll2utm.c geotranz.a - $(CC) $(CFLAGS) -o $@ $^ -lm - -utm2ll : utm2ll.c geotranz.a - $(CC) $(CFLAGS) -o $@ $^ -lm - - -# Convert from log file to GPX. - -log2gpx : log2gpx.c - $(CC) $(CFLAGS) -o $@ $^ -lm - - -# Test application to generate sound. - -gen_packets : gen_packets.c ax25_pad.c hdlc_send.c fcs_calc.c gen_tone.c morse.c dtmf.c textcolor.c dsp.c - $(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) -lm - -demod.o : tune.h - -demod_afsk.o : tune.h - -demod_9600.o : tune.h - -demod_psk.o : tune.h - -tune.h : - echo " " > tune.h - - -testagc : atest.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \ - fcs_calc.c ax25_pad.c decode_aprs.c telemetry.c dtime_now.o latlong.c symbols.c tune.h textcolor.c - $(CC) $(CFLAGS) -o atest $^ -lm - ./atest 02_Track_2.wav | grep "packets decoded in" > atest.out - - -# Unit test for demodulators - -atest : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \ - fcs_calc.c ax25_pad.c decode_aprs.c dwgpsnmea.o dwgps.o serial_port.o telemetry.c dtime_now.o latlong.c symbols.c textcolor.c tt_text.c - $(CC) $(CFLAGS) -o $@ $^ -lm -#atest : atest.c fsk_fast_filter.h demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.c hdlc_rec2.o multi_modem.o rrbb.o \ -# fcs_calc.c ax25_pad.c decode_aprs.c dwgpsnmea.o dwgps.o serial_port.o telemetry.c latlong.c symbols.c textcolor.c tt_text.c -# $(CC) $(CFLAGS) -o $@ $^ -lm - -# Unit test for inner digipeater algorithm - - -dtest : digipeater.c pfilter.o ax25_pad.o dedupe.o fcs_calc.o tq.o textcolor.o \ - decode_aprs.o dwgpsnmea.o dwgps.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o - $(CC) $(CFLAGS) -DTEST -o $@ $^ - ./dtest - - -# Unit test for APRStt. - -ttest : aprs_tt.c tt_text.c latlong.c geotranz.a - $(CC) $(CFLAGS) -DTT_MAIN -o $@ $^ - - -# Unit test for IGate - - -itest : igate.c textcolor.c ax25_pad.c fcs_calc.c - $(CC) $(CFLAGS) -DITEST -o $@ $^ - ./itest - - -# Unit test for UDP reception with AFSK demodulator - -udptest : udp_test.c demod.c dsp.c demod_afsk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c symbols.c textcolor.c - $(CC) $(CFLAGS) -o $@ $^ -lm - ./udptest - - -# Unit test for telemetry decoding. - - -tlmtest : telemetry.c ax25_pad.c fcs_calc.c textcolor.c - $(CC) $(CFLAGS) -o $@ $^ -lm - ./tlmtest - - -# Multiple AGWPE network or serial port clients to test TNCs side by side. - -aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.c - $(CC) $(CFLAGS) -g -o $@ $^ - - -# Talk to a KISS TNC. -# Note: kiss_frame.c has conditional compilation on KISSUTIL. - -kissutil : kissutil.c kiss_frame.c ax25_pad.o fcs_calc.o textcolor.o serial_port.o dtime_now.o sock.o - $(CC) $(CFLAGS) -g -DKISSUTIL -o $@ $^ - - - -# Touch Tone to Speech sample application. - -ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o - $(CC) $(CFLAGS) -g -o $@ $^ - - -depend : $(wildcard *.c) - makedepend -f $(lastword $(MAKEFILE_LIST)) -- $(CFLAGS) -- $^ - - -.PHONY: clean -clean : - rm -f $(APPS) gen_fff \ - fsk_fast_filter.h *.o *.a use_this_sdk - echo " " > tune.h - - -.PHONY: dist-mac -dist-mac: direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets \ - tocalls.txt symbols-new.txt symbolsX.txt dw-icon.png - rm -f ../direwolf_dist_bin.zip - (cd .. ; zip direwolf_dist_bin.zip \ - $(INSTALLDIR)/bin/direwolf \ - $(INSTALLDIR)/bin/decode_aprs \ - $(INSTALLDIR)/bin/text2tt \ - $(INSTALLDIR)/bin/tt2text \ - $(INSTALLDIR)/bin/ll2utm \ - $(INSTALLDIR)/bin/utm2ll \ - $(INSTALLDIR)/bin/aclients \ - $(INSTALLDIR)/bin/log2gpx \ - $(INSTALLDIR)/bin/gen_packets \ - $(INSTALLDIR)/bin/atest \ - $(INSTALLDIR)/bin/ttcalc \ - $(INSTALLDIR)/bin/kissutil \ - $(INSTALLDIR)/bin/dwespeak.sh \ - $(INSTALLDIR)/share/direwolf/tocalls.txt \ - $(INSTALLDIR)/share/direwolf/config/direwolf.conf \ - $(INSTALLDIR)/share/direwolf/symbols-new.txt \ - $(INSTALLDIR)/share/direwolf/symbolsX.txt \ - $(INSTALLDIR)/share/direwolf/dw-icon.png \ - $(INSTALLDIR)/share/doc/direwolf/README.md \ - $(INSTALLDIR)/share/doc/direwolf/CHANGES.md \ - $(INSTALLDIR)/share/doc/direwolf/LICENSE-dire-wolf.txt \ - $(INSTALLDIR)/share/doc/direwolf/LICENSE-other.txt \ - $(INSTALLDIR)/share/doc/direwolf/User-Guide.pdf \ - $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS.pdf \ - $(INSTALLDIR)/share/doc/direwolf/Raspberry-Pi-APRS-Tracker.pdf \ - $(INSTALLDIR)/share/doc/direwolf/APRStt-Implementation-Notes.pdf \ - $(INSTALLDIR)/share/doc/direwolf/APRS-Telemetry-Toolkit.pdf \ - $(INSTALLDIR)/man/man1/aclients.1 \ - $(INSTALLDIR)/man/man1/atest.1 \ - $(INSTALLDIR)/man/man1/decode_aprs.1 \ - $(INSTALLDIR)/man/man1/direwolf.1 \ - $(INSTALLDIR)/man/man1/gen_packets.1 \ - $(INSTALLDIR)/man/man1/kissutil.1 \ - $(INSTALLDIR)/man/man1/ll2utm.1 \ - $(INSTALLDIR)/man/man1/log2gpx.1 \ - $(INSTALLDIR)/man/man1/text2tt.1 \ - $(INSTALLDIR)/man/man1/tt2text.1 \ - $(INSTALLDIR)/man/man1/utm2ll.1 \ - ) - -# Package it up for distribution. - -.PHONY: dist-src -dist-src : README.md CHANGES.md \ - doc/User-Guide.pdf doc/Raspberry-Pi-APRS.pdf \ - doc/Raspberry-Pi-APRS-Tracker.pdf doc/APRStt-Implementation-Notes.pdf \ - dw-start.sh dwespeak.bat dwespeak.sh \ - tocalls.txt symbols-new.txt symbolsX.txt direwolf.spec - rm -f fsk_fast_filter.h - echo " " > tune.h - rm -f ../$z-src.zip - (cd .. ; zip $z-src.zip \ - $z/README.md \ - $z/CHANGES.md \ - $z/LICENSE* \ - $z/doc/User-Guide.pdf \ - $z/doc/Raspberry-Pi-APRS.pdf \ - $z/doc/Raspberry-Pi-APRS-Tracker.pdf \ - $z/doc/APRStt-Implementation-Notes.pdf \ - $z/Makefile* \ - $z/*.c $z/*.h \ - $z/regex/* $z/misc/* $z/geotranz/* \ - $z/man1/* \ - $z/generic.conf \ - $z/tocalls.txt $z/symbols-new.txt $z/symbolsX.txt \ - $z/dw-icon.png $z/dw-icon.rc $z/dw-icon.ico \ - $z/dw-start.sh $z/direwolf.spec \ - $z/dwespeak.bat $z/dwespeak.sh \ - $z/telemetry-toolkit/* ) - - -# -# The destination field is often used to identify the manufacturer/model. -# These are not hardcoded into Dire Wolf. Instead they are read from -# a file called tocalls.txt at application start up time. -# -# The original permanent symbols are built in but the "new" symbols, -# using overlays, are often updated. These are also read from files. -# -# You can obtain an updated copy by typing "make tocalls-symbols". -# This is not part of the normal build process. You have to do this explicitly. -# -# The locations below appear to be the most recent. -# The copy at http://www.aprs.org/tocalls.txt is out of date. -# - -.PHONY: tocalls-symbols -tocalls-symbols : - cp tocalls.txt tocalls.txt~ - wget http://www.aprs.org/aprs11/tocalls.txt -O tocalls.txt - -diff -Z tocalls.txt~ tocalls.txt - cp symbols-new.txt symbols-new.txt~ - wget http://www.aprs.org/symbols/symbols-new.txt -O symbols-new.txt - -diff -Z symbols-new.txt~ symbols-new.txt - cp symbolsX.txt symbolsX.txt~ - wget http://www.aprs.org/symbols/symbolsX.txt -O symbolsX.txt - -diff -Z symbolsX.txt~ symbolsX.txt diff --git a/Makefile.win b/Makefile.win deleted file mode 100644 index 671d1a0..0000000 --- a/Makefile.win +++ /dev/null @@ -1,695 +0,0 @@ -# -# Makefile for native Windows version of Dire Wolf. -# -# -# This is built in the Cygwin environment but with the -# compiler from http://www.mingw.org/ so there is no -# dependency on extra DLLs. -# -# The MinGW/bin directory must be in the PATH for the -# compiler. e.g. export PATH=/cygdrive/c/MinGW/bin:$PATH -# -# Failure to have the path set correctly will result in the -# obscure message: Makefile.win:... recipe for target ... failed. -# -# Type "which gcc" to make sure you are getting the right one! -# - - -all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets atest ttcalc tnctest kissutil - - -# People say we need -mthreads option for threads to work properly. -# They also say it creates a dependency on mingwm10.dll but I'm not seeing that. -# Maybe that is for pthreads. We are using the Windows threads. - -# -Ofast was added in gcc 4.6 which was the MinGW version back in 2012. - -CC := gcc -CFLAGS := -Ofast -march=pentium3 -msse -Iregex -Iutm -Igeotranz -mthreads -DUSE_REGEX_STATIC -Wall -Wlogical-op -AR := ar - -CFLAGS += -g -# TEMP EXPERIMENT - DO NOT RELEASE -#CFLAGS += -fsanitize=undefined - -# For version 1.4, we upgrade from gcc 4.6.2 to 4.9.3. - -# gcc 4.8 adds these. Try them just for fun. -# No, it needs libasan which is not on Windows. -#CFLAGS += -fsanitize=address -fno-omit-frame-pointer - -# Helpful for the demodulators. Overkill for non-hot spots. -#CFLAGS += -Wdouble-promotion - -# Don't have the patience for this right now. -#CFLAGS += -Wextra - -# Continue working on these. -CFLAGS += -Wsign-compare -CFLAGS += -Wuninitialized -CFLAGS += -Wold-style-declaration -# CFLAGS += -fdelete-null-pointer-checks -Wnull-dereference ---not recognized -#CFLAGS += -Wold-style-definition -#-Wmissing-prototypes - -# -# Let's see impact of various optimization levels. -# Benchmark results with MinGW gcc version 4.6.2. -# -# seconds options, comments -# ------ ----------------- -# 119.8 -O2 Used for version 0.8 -# 92.1 -O3 -# 88.7 -Ofast (should be same as -O3 -ffastmath) -# 87.5 -Ofast -march=pentium -# 74.1 -Ofast -msse -# 72.2 -Ofast -march=pentium -msse -# 62.0 -Ofast -march=pentium3 (this implies -msse) -# 61.9 -Ofast -march=pentium3 -msse -# -# A minimum of Windows XP is required due to some of the system -# features being used. XP requires a Pentium processor or later. -# The DSP filters can be sped up considerably with the SSE instructions. -# The SSE instructions were introduced in 1999 with the -# Pentium III series. -# SSE2 instructions, added in 2000, don't seem to offer any advantage. -# -# For version 0.9, a Pentium 3 or equivalent is now the minimum required -# for the prebuilt Windows distribution. -# If you insist on using a computer from the previous century, -# you can compile this yourself with different options. -# - - - - -# -------------------------------------- Main application -------------------------------- - -# Not sure why this is here. - -demod.o : fsk_demod_state.h - -demod_9600.o : fsk_demod_state.h - -demod_afsk.o : fsk_demod_state.h - -demod_psk.o : fsk_demod_state.h - - -direwolf : direwolf.o config.o recv.o demod.o dsp.o demod_afsk.o demod_psk.o demod_9600.o hdlc_rec.o \ - hdlc_rec2.o multi_modem.o rdq.o rrbb.o dlq.o \ - fcs_calc.o ax25_pad.o ax25_pad2.o xid.o \ - decode_aprs.o symbols.o server.o kiss.o kissserial.o kissnet.o kiss_frame.o hdlc_send.o fcs_calc.o \ - gen_tone.o morse.o audio_win.o audio_stats.o digipeater.o cdigipeater.o pfilter.o dedupe.o tq.o xmit.o \ - ptt.o beacon.o dwgps.o encode_aprs.o latlong.o textcolor.o \ - dtmf.o aprs_tt.o tt_user.o tt_text.o igate.o waypoint.o serial_port.o log.o telemetry.o \ - dwgps.o dwgpsnmea.o dtime_now.o mheard.o ax25_link.o cm108.c \ - dw-icon.o regex.a misc.a geotranz.a - $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32 - -dw-icon.o : dw-icon.rc dw-icon.ico - windres dw-icon.rc -o $@ - - -# Optimization for slow processors. - -demod.o : fsk_fast_filter.h - -demod_afsk.o : fsk_fast_filter.h - - -fsk_fast_filter.h : gen_fff - ./gen_fff > fsk_fast_filter.h - -gen_fff : demod_afsk.c dsp.c textcolor.c - echo " " > tune.h - $(CC) $(CFLAGS) -DGEN_FFF -o $@ $^ - - -# -# The destination field is often used to identify the manufacturer/model. -# These are not hardcoded into Dire Wolf. Instead they are read from -# a file called tocalls.txt at application start up time. -# -# The original permanent symbols are built in but the "new" symbols, -# using overlays, are often updated. These are also read from files. -# -# You can obtain an updated copy by typing "make tocalls-symbols". -# This is not part of the normal build process. You have to do this explicitly. -# -# The locations below appear to be the most recent. -# The copy at http://www.aprs.org/tocalls.txt is out of date. -# - -.PHONY: tocalls-symbols -tocalls-symbols : - cp tocalls.txt tocalls.txt~ - wget http://www.aprs.org/aprs11/tocalls.txt -O tocalls.txt - -diff tocalls.txt~ tocalls.txt - cp symbols-new.txt symbols-new.txt~ - wget http://www.aprs.org/symbols/symbols-new.txt -O symbols-new.txt - -diff symbols-new.txt~ symbols-new.txt - cp symbolsX.txt symbolsX.txt~ - wget http://www.aprs.org/symbols/symbolsX.txt -O symbolsX.txt - -diff symbolsX.txt~ symbolsX.txt - - - -# ---------------------------- Other utilities included with distribution ------------------------- - - -# Separate application to decode raw data. - -# First three use .c rather than .o because they depend on DECAMAIN definition. - -decode_aprs : decode_aprs.c kiss_frame.c ax25_pad.c dwgpsnmea.o dwgps.o serial_port.o symbols.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o regex.a misc.a geotranz.a - $(CC) $(CFLAGS) -DDECAMAIN -o decode_aprs $^ - - -# Convert between text and touch tone representation. - -text2tt : tt_text.c misc.a - $(CC) $(CFLAGS) -DENC_MAIN -o $@ $^ - -tt2text : tt_text.c misc.a - $(CC) $(CFLAGS) -DDEC_MAIN -o $@ $^ - - -# Convert between Latitude/Longitude and UTM coordinates. - -ll2utm : ll2utm.c textcolor.c geotranz.a misc.a - $(CC) $(CFLAGS) -o $@ $^ - -utm2ll : utm2ll.c textcolor.c geotranz.a misc.a - $(CC) $(CFLAGS) -o $@ $^ - - -# Convert from log file to GPX. - -log2gpx : log2gpx.c textcolor.o misc.a - $(CC) $(CFLAGS) -o $@ $^ - - -# Test application to generate sound. - -gen_packets : gen_packets.o ax25_pad.o hdlc_send.o fcs_calc.o gen_tone.o morse.o dtmf.o textcolor.o dsp.o misc.a regex.a - $(CC) $(CFLAGS) -o $@ $^ - - -# Connected mode packet application server. - -appserver : appserver.o textcolor.o ax25_pad.o fcs_calc.o misc.a - $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32 - - - -# ------------------------------------------- Libraries -------------------------------------------- - - - -# UTM, USNG, MGRS conversions. - -geotranz.a : error_string.o mgrs.o polarst.o tranmerc.o ups.o usng.o utm.o - ar -cr $@ $^ - -error_string.o : geotranz/error_string.c - $(CC) $(CFLAGS) -c -o $@ $^ - -mgrs.o : geotranz/mgrs.c - $(CC) $(CFLAGS) -c -o $@ $^ - -polarst.o : geotranz/polarst.c - $(CC) $(CFLAGS) -c -o $@ $^ - -tranmerc.o : geotranz/tranmerc.c - $(CC) $(CFLAGS) -c -o $@ $^ - -ups.o : geotranz/ups.c - $(CC) $(CFLAGS) -c -o $@ $^ - -usng.o : geotranz/usng.c - $(CC) $(CFLAGS) -c -o $@ $^ - -utm.o : geotranz/utm.c - $(CC) $(CFLAGS) -c -o $@ $^ - - -# -# When building for Linux, we use regular expression -# functions supplied by the gnu C library. -# For the native WIN32 version, we need to use our own copy. -# These were copied from http://gnuwin32.sourceforge.net/packages/regex.htm -# Consider upgrading from https://www.gnu.org/software/libc/sources.html - -regex.a : regex.o - ar -cr $@ $^ - -regex.o : regex/regex.c - $(CC) $(CFLAGS) -Dbool=int -Dtrue=1 -Dfalse=0 -c -o $@ $^ - - - -# There are several string functions found in Linux -# but not on Windows. Need to provide our own copy. - -misc.a : strsep.o strtok_r.o strcasestr.o strlcpy.o strlcat.o - ar -cr $@ $^ - -strsep.o : misc/strsep.c - $(CC) $(CFLAGS) -c -o $@ $^ - -strtok_r.o : misc/strtok_r.c - $(CC) $(CFLAGS) -c -o $@ $^ - -strcasestr.o : misc/strcasestr.c - $(CC) $(CFLAGS) -c -o $@ $^ - -strlcpy.o : misc/strlcpy.c - $(CC) $(CFLAGS) -I. -c -o $@ $^ - -strlcat.o : misc/strlcat.c - $(CC) $(CFLAGS) -I. -c -o $@ $^ - - -# --------------------------------- Automated Smoke Test -------------------------------- - - -# Combine some unit tests into a single regression sanity check. - -check : dtest ttest tttexttest pftest tlmtest lltest enctest kisstest pad2test xidtest dtmftest check-modem1200 check-modem300 check-modem9600 check-modem19200 check-modem2400 check-modem4800 - -# Can we encode and decode at popular data rates? -# Verify that single bit fixup increases the count. - -check-modem1200 : gen_packets atest - gen_packets -n 100 -o test12.wav - atest -F0 -PE -L64 -G72 test12.wav - atest -F1 -PE -L70 -G75 test12.wav - rm test12.wav - -check-modem300 : gen_packets atest - gen_packets -B300 -n 100 -o test3.wav - atest -B300 -F0 -L68 -G69 test3.wav - atest -B300 -F1 -L71 -G75 test3.wav - rm test3.wav - -#FIXME: test full amplitude. - -check-modem9600 : gen_packets atest - gen_packets -B9600 -a 170 -o test96.wav - sleep 1 - atest -B9600 -F0 -L4 -G4 test96.wav - sleep 1 - rm test96.wav - sleep 1 - gen_packets -B9600 -n 100 -o test96.wav - sleep 1 - atest -B9600 -F0 -L50 -G54 test96.wav - atest -B9600 -F1 -L55 -G59 test96.wav - sleep 1 - rm test96.wav - -check-modem19200 : gen_packets atest - gen_packets -r 96000 -B19200 -a 170 -o test19.wav - sleep 1 - atest -B19200 -F0 -L4 test19.wav - sleep 1 - rm test19.wav - sleep 1 - gen_packets -r 96000 -B19200 -n 100 -o test19.wav - sleep 1 - atest -B19200 -F0 -L55 -G59 test19.wav - atest -B19200 -F1 -L60 -G64 test19.wav - sleep 1 - rm test19.wav - -check-modem2400 : gen_packets atest - gen_packets -B2400 -n 100 -o test24.wav - sleep 1 - atest -B2400 -F0 -L70 -G78 test24.wav - atest -B2400 -F1 -L80 -G87 test24.wav - sleep 1 - rm test24.wav - -check-modem4800 : gen_packets atest - gen_packets -B4800 -n 100 -o test48.wav - sleep 1 - atest -B4800 -F0 -L70 -G74 test48.wav - atest -B4800 -F1 -L79 -G84 test48.wav - sleep 1 - rm test48.wav - - -# Unit test for demodulators - -atest : atest.c fsk_fast_filter.h demod.c demod_afsk.c demod_psk.c demod_9600.c \ - dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \ - rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \ - dwgpsnmea.o dwgps.o serial_port.o latlong.c \ - symbols.c tt_text.c textcolor.c telemetry.c dtime_now.o \ - decode_aprs.o log.o \ - misc.a regex.a - echo " " > tune.h - $(CC) $(CFLAGS) -o $@ $^ - #./atest ..\\direwolf-0.2\\02_Track_2.wav - #atest -B 9600 z9.wav - #atest za100.wav - -atest9 : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \ - rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c latlong.c symbols.c textcolor.c telemetry.c dtime_now.o misc.a regex.a \ - fsk_fast_filter.h - echo " " > tune.h - $(CC) $(CFLAGS) -o $@ $^ - ./atest9 -B 9600 ../walkabout9600.wav | grep "packets decoded in" >atest.out - #./atest9 -B 9600 noise96.wav - - -# Unit test for inner digipeater algorithm - -.PHONY: dtest -dtest : digipeater.c dedupe.c pfilter.c \ - ax25_pad.o fcs_calc.o tq.o textcolor.o \ - decode_aprs.o dwgpsnmea.o dwgps.o serial_port.o latlong.o telemetry.o symbols.o tt_text.o misc.a regex.a - $(CC) $(CFLAGS) -DDIGITEST -o $@ $^ - ./dtest - rm dtest.exe - -# Unit test for APRStt tone seqence parsing. - -.PHONTY: ttest -ttest : aprs_tt.c tt_text.c latlong.o textcolor.o geotranz.a misc.a - $(CC) $(CFLAGS) -Igeotranz -DTT_MAIN -o $@ $^ - ./ttest - rm ttest.exe - -# Unit test for APRStt tone sequence / text conversions. - -.PHONY: tttexttest -tttexttest : tt_text.c textcolor.o misc.a - $(CC) $(CFLAGS) -DTTT_TEST -o $@ $^ - ./tttexttest - rm tttexttest.exe - -# Unit test for Packet Filtering. - -.PHONY: pftest -pftest : pfilter.c ax25_pad.o textcolor.o fcs_calc.o decode_aprs.o dwgpsnmea.o dwgps.o serial_port.o latlong.o symbols.o telemetry.o tt_text.o misc.a regex.a - $(CC) $(CFLAGS) -DPFTEST -o $@ $^ - ./pftest - rm pftest.exe - - - -# Unit test for telemetry decoding. - -.PHONY: tlmtest -tlmtest : telemetry.c ax25_pad.o fcs_calc.o textcolor.o misc.a regex.a - $(CC) $(CFLAGS) -DTEST -o $@ $^ - ./tlmtest - rm tlmtest.exe - - -# Unit test for location coordinate conversion. - -.PHONY: lltest -lltest : latlong.c textcolor.o misc.a - $(CC) $(CFLAGS) -DLLTEST -o $@ $^ - ./lltest - rm lltest.exe - -# Unit test for encoding position & object report. - -.PHONY: enctest -enctest : encode_aprs.c latlong.c textcolor.c misc.a - $(CC) $(CFLAGS) -DEN_MAIN -o $@ $^ - ./enctest - rm enctest.exe - - -# Unit test for KISS encapsulation. - -.PHONY: kisstest -kisstest : kiss_frame.c - $(CC) $(CFLAGS) -DKISSTEST -o $@ $^ - ./kisstest - rm kisstest.exe - - -# Unit test for constructing frames besides UI. - -.PHONY: pad2test -pad2test : ax25_pad2.c ax25_pad.c fcs_calc.o textcolor.o regex.a misc.a - $(CC) $(CFLAGS) -DPAD2TEST -o $@ $^ - ./pad2test - rm pad2test.exe - -# Unit Test for XID frame encode/decode. - -.PHONY: xidtest -xidtest : xid.c textcolor.o misc.a - $(CC) $(CFLAGS) -DXIDTEST -o $@ $^ - ./xidtest - rm xidtest.exe - -# Unit Test for DTMF encode/decode. - -.PHONY: dtmftest -dtmftest : dtmf.c textcolor.o - $(CC) $(CFLAGS) -DDTMF_TEST -o $@ $^ - ./dtmftest - rm dtmftest.exe - - -# ------------------------------ Other manual testing & experimenting ------------------------------- - - -tnctest : tnctest.c textcolor.o dtime_now.o serial_port.o misc.a - $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32 - - -# For tweaking the demodulator. - -demod.o : tune.h -demod_9600.o : tune.h -demod_afsk.o : tune.h -demod_psk.o : tune.h - -testagc : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.o fsk_demod_agc.h \ - hdlc_rec.o hdlc_rec2.o multi_modem.o \ - rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o latlong.o symbols.o textcolor.o telemetry.o \ - dwgpsnmea.o dwgps.o serial_port.o tt_text.o dtime_now.o regex.a misc.a - rm -f atest.exe - $(CC) $(CFLAGS) -o atest $^ - ./atest -P GGG- -F 0 ../02_Track_2.wav | grep "packets decoded in" >atest.out - echo " " > tune.h - - -noisy3.wav : gen_packets - ./gen_packets -B 300 -n 100 -o noisy3.wav - -testagc3 : atest.c demod.c dsp.c demod_afsk.c demod_psk.c demod_9600.c hdlc_rec.c hdlc_rec2.c multi_modem.c \ - rrbb.c fcs_calc.c ax25_pad.c decode_aprs.c latlong.c symbols.c textcolor.c telemetry.c dtime_now.o regex.a misc.a \ - tune.h - rm -f atest3.exe - $(CC) $(CFLAGS) -o atest3 $^ - ./atest3 -B 300 -P D -D 3 noisy3.wav | grep "packets decoded in" >atest.out - echo " " > tune.h - - -noisy96.wav : gen_packets - ./gen_packets -B 9600 -n 100 -o noisy96.wav - -testagc96 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c demod_9600.c \ - dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \ - rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \ - dwgpsnmea.o dwgps.o serial_port.o latlong.o \ - symbols.o tt_text.o textcolor.o telemetry.o dtime_now.o \ - misc.a regex.a - rm -f atest96.exe - $(CC) $(CFLAGS) -o atest96 $^ - ./atest96 -B 9600 ../walkabout9600c.wav | grep "packets decoded in" >atest.out - #./atest96 -B 9600 noisy96.wav | grep "packets decoded in" >atest.out - #./atest96 -B 9600 19990303_0225_9600_8bis_22kHz.wav | grep "packets decoded in" >atest.out - #./atest96 -B 9600 19990303_0225_9600_16bit_22kHz.wav | grep "packets decoded in" >atest.out - #./atest96 -B 9600 -P + z8-22k.wav| grep "packets decoded in" >atest.out - #./atest96 -B 9600 test9600.wav | grep "packets decoded in" >atest.out - echo " " > tune.h - -testagc24 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c demod_9600.c \ - dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \ - rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \ - dwgpsnmea.o dwgps.o serial_port.o latlong.o \ - symbols.o tt_text.o textcolor.o telemetry.o dtime_now.o \ - misc.a regex.a - rm -f atest24.exe - sleep 1 - $(CC) $(CFLAGS) -o atest24 $^ - ./atest24 -B 2400 test2400.wav | grep "packets decoded in" >atest.out - echo " " > tune.h - -testagc48 : atest.c fsk_fast_filter.h tune.h demod.c demod_afsk.c demod_psk.c demod_9600.c \ - dsp.o hdlc_rec.o hdlc_rec2.o multi_modem.o \ - rrbb.o fcs_calc.o ax25_pad.o decode_aprs.o \ - dwgpsnmea.o dwgps.o serial_port.o latlong.o \ - symbols.o tt_text.o textcolor.o telemetry.o dtime_now.o \ - misc.a regex.a - rm -f atest48.exe - sleep 1 - $(CC) $(CFLAGS) -o atest48 $^ - ./atest48 -B 4800 test4800.wav | grep "packets decoded in" >atest.out - #./atest48 -B 4800 test4800.wav - echo " " > tune.h - - -# Unit test for IGate - -itest : igate.c textcolor.c ax25_pad.c fcs_calc.c misc.a regex.a - $(CC) $(CFLAGS) -DITEST -o $@ $^ -lwinmm -lws2_32 - - - - - -# Multiple AGWPE network or serial port clients to test TNCs side by side. - -aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.c misc.a regex.a - $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32 - - -# Talk to a KISS TNC. - -# Note: kiss_frame.c has conditional compilation on KISSUTIL. - -kissutil : kissutil.c kiss_frame.c ax25_pad.o fcs_calc.o textcolor.o serial_port.o sock.o dtime_now.o misc.a regex.a - $(CC) $(CFLAGS) -DKISSUTIL -o $@ $^ -lwinmm -lws2_32 - - -# Touch Tone to Speech sample application. - -ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o misc.a regex.a - $(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32 - - -# Send GPS location to KISS TNC each second. - -walk96 : walk96.c dwgps.o dwgpsnmea.o kiss_frame.o \ - latlong.o encode_aprs.o serial_port.o textcolor.o \ - ax25_pad.o fcs_calc.o \ - xmit.o hdlc_send.o gen_tone.o ptt.o tq.o \ - hdlc_rec.o hdlc_rec2.o rrbb.o dsp.o audio_win.o \ - multi_modem.o demod.o demod_afsk.o demod_psk.c demod_9600.o rdq.o \ - server.o morse.o dtmf.o audio_stats.o dtime_now.o dlq.o \ - regex.a misc.a - $(CC) $(CFLAGS) -DWALK96 -o $@ $^ -lwinmm -lws2_32 - - - -#-------------------------------------------------------------- - - -.PHONY: depend -depend : $(wildcard *.c) - makedepend -f $(lastword $(MAKEFILE_LIST)) -- $(CFLAGS) -- $^ - -.PHONY: clean -clean : - rm -f *.o *.a *.exe fsk_fast_filter.h noisy96.wav - echo " " > tune.h - - - -# ------------------------------- Packaging for distribution ---------------------- - -# Name of zip file for distribution. - -z := $(notdir ${CURDIR}) - - -.PHONY: dist-win -dist-win : direwolf.exe decode_aprs.exe text2tt.exe tt2text.exe ll2utm.exe utm2ll.exe \ - aclients.exe log2gpx.exe gen_packets.exe atest.exe ttcalc.exe kissutil.exe \ - generic.conf dwespeak.bat \ - README.md CHANGES.md \ - doc/User-Guide.pdf \ - doc/Raspberry-Pi-APRS.pdf \ - doc/APRStt-Implementation-Notes.pdf - rm -f ../$z-win.zip - egrep '^C|^W' generic.conf | cut -c2-999 > direwolf.conf - unix2dos direwolf.conf - cp doc/README.md README-doc.md - zip --junk-paths ../$z-win.zip \ - README.md \ - CHANGES.md \ - README-doc.md \ - doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf \ - doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf \ - doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf \ - doc/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf \ - doc/APRS-Telemetry-Toolkit.pdf \ - doc/APRStt-Implementation-Notes.pdf \ - doc/APRStt-interface-for-SARTrack.pdf \ - doc/APRStt-Listening-Example.pdf \ - doc/Bluetooth-KISS-TNC.pdf \ - doc/Going-beyond-9600-baud.pdf \ - doc/Raspberry-Pi-APRS.pdf \ - doc/Raspberry-Pi-APRS-Tracker.pdf \ - doc/Raspberry-Pi-SDR-IGate.pdf \ - doc/Successful-APRS-IGate-Operation.pdf \ - doc/User-Guide.pdf \ - doc/WA8LMF-TNC-Test-CD-Results.pdf \ - LICENSE* \ - direwolf.conf \ - direwolf.exe \ - decode_aprs.exe \ - tocalls.txt symbols-new.txt symbolsX.txt \ - text2tt.exe tt2text.exe \ - ll2utm.exe utm2ll.exe \ - aclients.exe \ - log2gpx.exe \ - gen_packets.exe \ - atest.exe \ - ttcalc.exe \ - kissutil.exe \ - dwespeak.bat \ - telemetry-toolkit/* - rm README-doc.md - - -# Reminders if pdf files are not up to date. - -doc/User-Guide.pdf : doc/User-Guide.docx - echo "***** User-Guide.pdf is out of date *****" - -doc/Raspberry-Pi-APRS.pdf : doc/Raspberry-Pi-APRS.docx - echo "***** Raspberry-Pi-APRS.pdf is out of date *****" - -doc/Raspberry-Pi-APRS-Tracker.pdf : doc/Raspberry-Pi-APRS-Tracker.docx - echo "***** Raspberry-Pi-APRS-Tracker.pdf is out of date *****" - -doc/APRStt-Implementation-Notes.pdf : doc/APRStt-Implementation-Notes.docx - echo "***** APRStt-Implementation-Notes.pdf is out of date *****" - -doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf : doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.docx - echo "***** A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf is out of date *****" - -doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf : doc/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.docx - echo "***** A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf is out of date *****" - -doc/APRS-Telemetry-Toolkit.pdf : doc/APRS-Telemetry-Toolkit.docx - echo "***** APRS-Telemetry-Toolkit.pdf is out of date *****" - - - -.PHONY: backup -backup : - mkdir /cygdrive/e/backup-cygwin-home/`date +"%Y-%m-%d"` - cp -r . /cygdrive/e/backup-cygwin-home/`date +"%Y-%m-%d"` - - -# -# The following is updated by "make depend" -# -# DO NOT DELETE - - diff --git a/README.md b/README.md index 110acd1..51b2901 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,16 @@ ### Decoded Information from Radio Emissions for Windows Or Linux Fans ### -In the early days of Amateur Packet Radio, it was necessary to use an expensive “Terminal Node Controller” (TNC) with specialized hardware. Those days are gone. You can now get better results at lower cost by connecting your radio to the “soundcard” interface of a computer and using software to decode the signals. +In the early days of Amateur Packet Radio, it was necessary to use an expensive "Terminal Node Controller" (TNC) with specialized hardware. Those days are gone. You can now get better results at lower cost by connecting your radio to the "soundcard" interface of a computer and using software to decode the signals. -Why settle for mediocre receive performance from a 1980's technology TNC using an old modem chip? Dire Wolf decodes over 1000 error-free frames from Track 2 of the [WA8LMF TNC Test CD](https://github.com/wb2osz/direwolf/tree/dev/doc/WA8LMF-TNC-Test-CD-Results.pdf), leaving all the hardware TNCs, and first generation "soundcard" modems, behind in the dust. +Why waste $200 and settle for mediocre receive performance from a 1980's technology TNC using an old modem chip? Dire Wolf decodes over 1000 error-free frames from Track 2 of the [WA8LMF TNC Test CD](https://github.com/wb2osz/direwolf/tree/dev/doc/WA8LMF-TNC-Test-CD-Results.pdf), leaving all the hardware TNCs, and first generation "soundcard" modems, behind in the dust. ![](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. + +![](fx25.png) + 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: @@ -20,7 +23,7 @@ Without any additional software, it can perform as: - [APRStt](http://www.aprs.org/aprstt.html) gateway -It can also be used as a virtual TNC for other applications such as [APRSIS32](http://aprsisce.wikidot.com/), [UI-View32](http://www.ui-view.net/), [Xastir](http://xastir.org/index.php/Main_Page), [APRS-TW](http://aprstw.blandranch.net/), [YAAC](http://www.ka2ddo.org/ka2ddo/YAAC.html), [UISS](http://users.belgacom.net/hamradio/uiss.htm), [Linux AX25](http://www.linux-ax25.org/wiki/Main_Page), [SARTrack](http://www.sartrack.co.nz/index.html), [Winlink Express (formerly known as RMS Express, formerly known as Winlink 2000 or WL2K)](http://www.winlink.org/RMSExpress), [BPQ32](http://www.cantab.net/users/john.wiseman/Documents/BPQ32.html), [Outpost PM](http://www.outpostpm.org/), and many others. +It can also be used as a virtual TNC for other applications such as [APRSIS32](http://aprsisce.wikidot.com/), [Xastir](http://xastir.org/index.php/Main_Page), [APRS-TW](http://aprstw.blandranch.net/), [YAAC](http://www.ka2ddo.org/ka2ddo/YAAC.html), [PinPoint APRS](http://www.pinpointaprs.com/), [UI-View32](http://www.ui-view.net/),[UISS](http://users.belgacom.net/hamradio/uiss.htm), [Linux AX25](http://www.linux-ax25.org/wiki/Main_Page), [SARTrack](http://www.sartrack.co.nz/index.html), [Winlink Express (formerly known as RMS Express, formerly known as Winlink 2000 or WL2K)](http://www.winlink.org/RMSExpress), [BPQ32](http://www.cantab.net/users/john.wiseman/Documents/BPQ32.html), [Outpost PM](http://www.outpostpm.org/), [Ham Radio of Things](https://github.com/wb2osz/hrot), [Packet Compressed Sensing Imaging (PCSI)](https://maqifrnswa.github.io/PCSI/), and many others. ## Features & Benefits ## @@ -49,6 +52,11 @@ It can also be used as a virtual TNC for other applications such as [APRSIS32](h IGate stations allow communication between disjoint radio networks by allowing some content to flow between them over the Internet. +- **Ham Radio of Things (HRoT).** + + There have been occasional mentions of merging Ham Radio with the Internet of Things but only ad hoc incompatible narrowly focused applications. Here is a proposal for a standardized more flexible method so different systems can communicate with each other. + + [Ham Radio of Things - IoT over Ham Radio](https://github.com/wb2osz/hrot) - **AX.25 v2.2 Link Layer.** @@ -56,23 +64,23 @@ It can also be used as a virtual TNC for other applications such as [APRSIS32](h - **KISS Interface (TCP/IP, serial port, Bluetooth) & AGW network Interface (TCP/IP).** - Dire Wolf can be used as a virtual TNC for applications such as APRSIS32, UI-View32, Xastir, APRS-TW,YAAC, UISS, Linux AX25, SARTrack, Winlink / RMS Express, Outpost PM, and many others. + Dire Wolf can be used as a virtual TNC for applications such as [APRSIS32](http://aprsisce.wikidot.com/), [Xastir](http://xastir.org/index.php/Main_Page), [APRS-TW](http://aprstw.blandranch.net/), [YAAC](http://www.ka2ddo.org/ka2ddo/YAAC.html), [PinPoint APRS](http://www.pinpointaprs.com/), [UI-View32](http://www.ui-view.net/),[UISS](http://users.belgacom.net/hamradio/uiss.htm), [Linux AX25](http://www.linux-ax25.org/wiki/Main_Page), [SARTrack](http://www.sartrack.co.nz/index.html), [Winlink Express (formerly known as RMS Express, formerly known as Winlink 2000 or WL2K)](http://www.winlink.org/RMSExpress), [BPQ32](http://www.cantab.net/users/john.wiseman/Documents/BPQ32.html), [Outpost PM](http://www.outpostpm.org/), [Ham Radio of Things](https://github.com/wb2osz/hrot), [Packet Compressed Sensing Imaging (PCSI)](https://maqifrnswa.github.io/PCSI/), and many others. ### Radio Interfaces: ### -- **Uses computer’s “soundcard” and digital signal processing.** +- **Uses computer's "soundcard" and digital signal processing.** Lower cost and better performance than specialized hardware. - Compatible interfaces include [UDRC](https://nw-digital-radio.groups.io/g/udrc/wiki/UDRC%E2%84%A2-and-Direwolf-Packet-Modem), [SignaLink USB](http://www.tigertronics.com/slusbmain.htm), [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), and many others. + Compatible interfaces include [DRAWS](http://nwdigitalradio.com/draws/), [UDRC](https://nw-digital-radio.groups.io/g/udrc/wiki/UDRC%E2%84%A2-and-Direwolf-Packet-Modem), [SignaLink USB](http://www.tigertronics.com/slusbmain.htm), [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), [DINAH](https://hamprojects.info/dinah/), [SHARI](https://hamprojects.info/shari/), and many others. - **Standard 300, 1200 & 9600 bps modems and more.** -- **DTMF (“Touch Tone”) Decoding and Encoding.** +- **DTMF ("Touch Tone") Decoding and Encoding.** -- **Speech Synthesizer & Morse code generator.** +- **Speech Synthesizer interface & Morse code generator.** Transmit human understandable messages. @@ -101,59 +109,86 @@ It can also be used as a virtual TNC for other applications such as [APRSIS32](h Go to the [**releases** page](https://github.com/wb2osz/direwolf/releases). Download a zip file with "win" in its name, unzip it, and run direwolf.exe from a command window. -For more details see the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). +You can also build it yourself from source. For more details see the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). ### Linux - Using git clone (recommended) ### +***Note that this has changed for version 1.6. There are now a couple extra steps.*** + + +First you will need to install some software development packages using different commands depending on your flavor of Linux. +In most cases, the first few will already be there and the package installer will tell you that installation is not necessary. + +On Debian / Ubuntu / Raspbian / Raspberry Pi OS: + + sudo apt-get install git + sudo apt-get install gcc + sudo apt-get install g++ + sudo apt-get install make + sudo apt-get install cmake + sudo apt-get install libasound2-dev + sudo apt-get install libudev-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 alsa-lib-devel + sudo yum install libudev-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. + + sudo yum install epel-release + sudo rpm -e cmake + sudo yum install cmake3 + sudo ln -s /usr/bin/cmake3 /usr/bin/cmake + +Then on any flavor of Linux: + cd ~ git clone https://www.github.com/wb2osz/direwolf cd direwolf - make + git checkout dev + mkdir build && cd build + cmake .. + make -j4 sudo make install make install-conf -This should give you the most recent stable release. If you want the latest (possibly unstable) development version, use "git checkout dev" before the first "make" command. +This gives you the latest development version. Leave out the "git checkout dev" to get the most recent stable release. For more details see the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). Special considerations for the Raspberry Pi are found in **Raspberry-Pi-APRS.pdf** ### Linux - Using apt-get (Debian flavor operating systems) ### -Results will vary depending on your hardware platform and operating system version because it depends on various volunteers who perform the packaging. +Results will vary depending on your hardware platform and operating system version because it depends on various volunteers who perform the packaging. Expect the version to lag significantly behind development. sudo apt-get update - apt-cache showpkg direwolf + apt-cache showpkg direwolf sudo apt-get install direwolf ### Linux - Using yum (Red Hat flavor operating systems) ### -Results will vary depending on your hardware platform and operating system version because it depends on various volunteers who perform the packaging. +Results will vary depending on your hardware platform and operating system version because it depends on various volunteers who perform the packaging. Expect the version to lag significantly behind development. sudo yum check-update - sudo yum list direwolf + sudo yum list direwolf sudo yum install direwolf -### Linux - Download source in tar or zip file ### - -Go to the [releases page](https://github.com/wb2osz/direwolf/releases). Chose desired release and download the source as zip or compressed tar file. Unpack the files, with "unzip" or "tar xfz," and then: - - cd direwolf-* - make - sudo make install - make install-conf - -For more details see the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). Special considerations for the Raspberry Pi are found in **Raspberry-Pi-APRS.pdf** - ### Macintosh OS X ### -Read the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). It is a lot more complicated than Linux. +Read the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). It is more complicated than Linux. -If you have problems, post them to the [Dire Wolf packet TNC](https://groups.yahoo.com/neo/groups/direwolf_packet/info) discussion group. I don't have a Mac and probably won't be able to help you. I rely on others, in the user community, for the Mac version. +If you have problems, post them to the [Dire Wolf packet TNC](https://groups.io/g/direwolf) discussion group. @@ -161,7 +196,7 @@ If you have problems, post them to the [Dire Wolf packet TNC](https://groups.ya Here are some good places to ask questions and share your experience: -- [Dire Wolf packet TNC](https://groups.yahoo.com/neo/groups/direwolf_packet/info) +- [Dire Wolf Software TNC](https://groups.io/g/direwolf) - [Raspberry Pi 4 Ham Radio](https://groups.io/g/RaspberryPi-4-HamRadio) diff --git a/cmake/cpack/CMakeLists.txt b/cmake/cpack/CMakeLists.txt new file mode 100644 index 0000000..845c377 --- /dev/null +++ b/cmake/cpack/CMakeLists.txt @@ -0,0 +1 @@ +include(CPack) diff --git a/cmake/cpack/direwolf.desktop.in b/cmake/cpack/direwolf.desktop.in new file mode 100644 index 0000000..79c63aa --- /dev/null +++ b/cmake/cpack/direwolf.desktop.in @@ -0,0 +1,10 @@ +[Desktop Entry] +Name=@APPLICATION_NAME@ +Comment=APRS Soundcard TNC +Exec=@APPLICATION_DESKTOP_EXEC@ +Icon=@CMAKE_PROJECT_NAME@_icon.png +StartupNotify=true +Terminal=false +Type=Application +Categories=HamRadio +Keywords=Ham Radio;APRS;Soundcard TNC;KISS;AGWPE;AX.25 \ No newline at end of file diff --git a/cmake/cpack/direwolf.rc b/cmake/cpack/direwolf.rc new file mode 100644 index 0000000..99de6d9 --- /dev/null +++ b/cmake/cpack/direwolf.rc @@ -0,0 +1 @@ +MAINICON ICON "direwolf_icon.ico" \ No newline at end of file diff --git a/dw-icon.ico b/cmake/cpack/direwolf_icon.ico similarity index 100% rename from dw-icon.ico rename to cmake/cpack/direwolf_icon.ico diff --git a/dw-icon.png b/cmake/cpack/direwolf_icon.png similarity index 100% rename from dw-icon.png rename to cmake/cpack/direwolf_icon.png diff --git a/cmake/cpu_tests/test_arm_neon.cxx b/cmake/cpu_tests/test_arm_neon.cxx new file mode 100644 index 0000000..cb48159 --- /dev/null +++ b/cmake/cpu_tests/test_arm_neon.cxx @@ -0,0 +1,16 @@ +#include +#include +#include +#include + +void signalHandler(int signum) { + exit(signum); // SIGILL = 4 +} + +int main(int argc, char* argv[]) +{ + signal(SIGILL, signalHandler); + uint32x4_t x={0}; + x=veorq_u32(x,x); + return 0; +} diff --git a/cmake/cpu_tests/test_x86_avx.cxx b/cmake/cpu_tests/test_x86_avx.cxx new file mode 100644 index 0000000..2344fbc --- /dev/null +++ b/cmake/cpu_tests/test_x86_avx.cxx @@ -0,0 +1,15 @@ +#include +#include +#include + +void signalHandler(int signum) { + exit(signum); // SIGILL = 4 +} + +int main(int argc, char* argv[]) +{ + signal(SIGILL, signalHandler); + __m256d x = _mm256_setzero_pd(); + x=_mm256_addsub_pd(x,x); + return 0; +} diff --git a/cmake/cpu_tests/test_x86_avx2.cxx b/cmake/cpu_tests/test_x86_avx2.cxx new file mode 100644 index 0000000..369186d --- /dev/null +++ b/cmake/cpu_tests/test_x86_avx2.cxx @@ -0,0 +1,15 @@ +#include +#include +#include + +void signalHandler(int signum) { + exit(signum); // SIGILL = 4 +} + +int main(int argc, char* argv[]) +{ + signal(SIGILL, signalHandler); + __m256i x = _mm256_setzero_si256(); + x=_mm256_add_epi64 (x,x); + return 0; +} diff --git a/cmake/cpu_tests/test_x86_avx512.cxx b/cmake/cpu_tests/test_x86_avx512.cxx new file mode 100644 index 0000000..eed07d3 --- /dev/null +++ b/cmake/cpu_tests/test_x86_avx512.cxx @@ -0,0 +1,16 @@ +#include +#include +#include +#include + +void signalHandler(int signum) { + exit(signum); // SIGILL = 4 +} + +int main(int argc, char* argv[]) +{ + signal(SIGILL, signalHandler); + uint64_t x[8] = {0}; + __m512i y = _mm512_loadu_si512((__m512i*)x); + return 0; +} diff --git a/cmake/cpu_tests/test_x86_sse2.cxx b/cmake/cpu_tests/test_x86_sse2.cxx new file mode 100644 index 0000000..98eb27e --- /dev/null +++ b/cmake/cpu_tests/test_x86_sse2.cxx @@ -0,0 +1,15 @@ +#include +#include +#include + +void signalHandler(int signum) { + exit(signum); // SIGILL = 4 +} + +int main(int argc, char* argv[]) +{ + signal(SIGILL, signalHandler); + __m128i x = _mm_setzero_si128(); + x=_mm_add_epi64(x,x); + return 0; +} diff --git a/cmake/cpu_tests/test_x86_sse3.cxx b/cmake/cpu_tests/test_x86_sse3.cxx new file mode 100644 index 0000000..70a31e3 --- /dev/null +++ b/cmake/cpu_tests/test_x86_sse3.cxx @@ -0,0 +1,16 @@ +#include +#include +#include +#include + +void signalHandler(int signum) { + exit(signum); // SIGILL = 4 +} + +int main(int argc, char* argv[]) +{ + signal(SIGILL, signalHandler); + __m128d x = _mm_setzero_pd(); + x=_mm_addsub_pd(x,x); + return 0; +} diff --git a/cmake/cpu_tests/test_x86_sse41.cxx b/cmake/cpu_tests/test_x86_sse41.cxx new file mode 100644 index 0000000..e08697f --- /dev/null +++ b/cmake/cpu_tests/test_x86_sse41.cxx @@ -0,0 +1,18 @@ +#include +#include +#include +#include + +void signalHandler(int signum) { + exit(signum); // SIGILL = 4 +} + +int main(int argc, char* argv[]) +{ + signal(SIGILL, signalHandler); + __m128i x = _mm_setzero_si128(); + __m128i a = _mm_setzero_si128(); + __m128i b = _mm_setzero_si128(); + x=_mm_blend_epi16(a,b,4); + return 0; +} diff --git a/cmake/cpu_tests/test_x86_sse42.cxx b/cmake/cpu_tests/test_x86_sse42.cxx new file mode 100644 index 0000000..58032a5 --- /dev/null +++ b/cmake/cpu_tests/test_x86_sse42.cxx @@ -0,0 +1,15 @@ +#include +#include +#include + +void signalHandler(int signum) { + exit(signum); // SIGILL = 4 +} + +int main(int argc, char* argv[]) +{ + signal(SIGILL, signalHandler); + unsigned int x=32; + x=_mm_crc32_u8(x,4); + return 0; +} diff --git a/cmake/cpu_tests/test_x86_ssse3.cxx b/cmake/cpu_tests/test_x86_ssse3.cxx new file mode 100644 index 0000000..01688f4 --- /dev/null +++ b/cmake/cpu_tests/test_x86_ssse3.cxx @@ -0,0 +1,16 @@ +#include +#include +#include +#include + +void signalHandler(int signum) { + exit(signum); // SIGILL = 4 +} + +int main(int argc, char* argv[]) +{ + signal(SIGILL, signalHandler); + __m128i x = _mm_setzero_si128(); + x=_mm_alignr_epi8(x,x,2); + return 0; +} diff --git a/cmake/include/uninstall.cmake.in b/cmake/include/uninstall.cmake.in new file mode 100644 index 0000000..2037e36 --- /dev/null +++ b/cmake/include/uninstall.cmake.in @@ -0,0 +1,21 @@ +if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: @CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") +endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +foreach(file ${files}) + message(STATUS "Uninstalling $ENV{DESTDIR}${file}") + if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + exec_program( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + if(NOT "${rm_retval}" STREQUAL 0) + message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") + endif(NOT "${rm_retval}" STREQUAL 0) + else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + message(STATUS "File $ENV{DESTDIR}${file} does not exist.") + endif(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") +endforeach(file) diff --git a/cmake/modules/FindCPUflags.cmake b/cmake/modules/FindCPUflags.cmake new file mode 100644 index 0000000..802d1cc --- /dev/null +++ b/cmake/modules/FindCPUflags.cmake @@ -0,0 +1,379 @@ +# Clang or AppleClang (see CMP0025) +if(NOT DEFINED C_CLANG AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(C_CLANG 1) +elseif(NOT DEFINED C_GCC AND CMAKE_CXX_COMPILER_ID MATCHES "GNU") + set(C_GCC 1) +elseif(NOT DEFINED C_MSVC AND CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + set(C_MSVC 1) +endif() + +# Detect current compilation architecture and create standard definitions +include(CheckSymbolExists) +function(detect_architecture symbol arch) + if (NOT DEFINED ARCHITECTURE) + set(CMAKE_REQUIRED_QUIET 1) + check_symbol_exists("${symbol}" "" ARCHITECTURE_${arch}) + unset(CMAKE_REQUIRED_QUIET) + + # The output variable needs to be unique across invocations otherwise + # CMake's crazy scope rules will keep it defined + if (ARCHITECTURE_${arch}) + set(ARCHITECTURE "${arch}" PARENT_SCOPE) + set(ARCHITECTURE_${arch} 1 PARENT_SCOPE) + add_definitions(-DARCHITECTURE_${arch}=1) + endif() + endif() +endfunction() + +# direwolf versions thru 1.5 were available pre-built for 32 bit Windows targets. +# Research and experimentation revealed that the SSE instructions made a big +# difference in runtime speed but SSE2 and later were not significantly better +# for this application. I decided to build with only the SSE instructions making +# the Pentium 3 the minimum requirement. SSE2 would require at least a Pentium 4 +# and offered no significant performance advantage. +# These are ancient history - from the previous Century - but old computers, generally +# considered useless for anything else, often end up in the ham shack. +# +# When cmake was first used for direwolf, the default target became 64 bit and the +# SSE2, SSE3, SSE4.1, and SSE4.2 instructions were automatically enabled based on the +# build machine capabilities. This was fine until I tried running the application +# on a computer much older than where it was built. It did not have the SSE4 instructions +# and the application died without a clue for the reason. +# Just how much benefit do these new instructions provide for this application? +# +# These were all run on the same computer, but compiled in different ways. +# Times to run atest with Track 1 of the TNC test CD: +# +# direwolf 1.5 - 32 bit target - gcc 6.3.0 +# +# 60.4 sec. Pentium 3 with SSE +# +# direwolf 1.6 - 32 bit target - gcc 7.4.0 +# +# 81.0 sec. with no SIMD instructions enabled. +# 54.4 sec. with SSE +# 52.0 sec. with SSE2 +# 52.4 sec. with SSE2, SSE3 +# 52.3 sec. with SSE2, SSE3, SSE4.1, SSE4.2 +# 49.9 sec. Fedora standard: -m32 -march=i686 -mtune=generic -msse2 -mfpmath=sse +# 50.4 sec. sse not sse2: -m32 -march=i686 -mtune=generic -msse -mfpmath=sse +# +# That's what I found several years ago with a much older compiler. +# The original SSE helped a lot but SSE2 and later made little difference. +# +# direwolf 1.6 - 64 bit target - gcc 7.4.0 +# +# 34.8 sec. with no SIMD instructions enabled. +# 34.8 sec. with SSE +# 34.8 sec. with SSE2 +# 34.2 sec. with SSE2, SSE3 +# 33.5 sec. with SSE2, SSE3, SSE4.1, SSE4.2 +# 33.4 Fedora standard: -mtune=generic +# +# Why do we see such little variation? 64-bit target implies +# SSE, SSE2, SSE3 instructions are available. +# +# Building for a 64 bit target makes it run about 1.5x faster on the same hardware. +# +# 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, +# i.e. x86, x86_64, ARM, ARM64. That's why it did not go looking for the various +# SSE instructions. For x86, we would miss out on using SSE. + +if (NOT ENABLE_GENERIC) + if (C_MSVC) + detect_architecture("_M_AMD64" x86_64) + detect_architecture("_M_IX86" x86) + detect_architecture("_M_ARM" ARM) + detect_architecture("_M_ARM64" ARM64) + else() + detect_architecture("__x86_64__" x86_64) + detect_architecture("__i386__" x86) + detect_architecture("__arm__" ARM) + detect_architecture("__aarch64__" ARM64) + endif() +endif() +if (NOT DEFINED ARCHITECTURE) + set(ARCHITECTURE "GENERIC") + set(ARCHITECTURE_GENERIC 1) + add_definitions(-DARCHITECTURE_GENERIC=1) +endif() +message(STATUS "Target architecture: ${ARCHITECTURE}") + +set(TEST_DIR ${PROJECT_SOURCE_DIR}/cmake/cpu_tests) + +# flag that set the minimum cpu flag requirements +# used to create re-distribuitable binary + +if (${ARCHITECTURE} MATCHES "x86_64|x86" AND (FORCE_SSE OR FORCE_SSSE3 OR FORCE_SSE41)) + if (FORCE_SSE) + set(HAS_SSE ON CACHE BOOL "SSE SIMD enabled") + if(C_GCC OR C_CLANG) + if (${ARCHITECTURE} MATCHES "x86_64") + # All 64-bit capable chips support MMX, SSE, SSE2, and SSE3 + # so they are all enabled automatically. We don't want to use + # SSE4, based on build machine capabilites, because the application + # would not run properly on an older CPU. + set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mtune=generic" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=generic" ) + else() + # Fedora standard uses -msse2 here. + # I dropped it down to -msse for greater compatibility and little penalty. + set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32 -march=i686 -mtune=generic -msse -mfpmath=sse" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32 -march=i686 -mtune=generic -msse -mfpmath=sse" ) + endif() + message(STATUS "Use SSE SIMD instructions") + add_definitions(-DUSE_SSE) + elseif(C_MSVC) + set( CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /arch:SSE" ) + set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /arch:SSE" ) + set( CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Oi /GL /Ot /Ox" ) + set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oi /GL /Ot /Ox" ) + set( CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG" ) + message(STATUS "Use MSVC SSSE3 SIMD instructions") + add_definitions (/D "_CRT_SECURE_NO_WARNINGS") + add_definitions(-DUSE_SSSE3) + endif() + elseif (FORCE_SSSE3) + set(HAS_SSSE3 ON CACHE BOOL "SSSE3 SIMD enabled") + if(C_GCC OR C_CLANG) + set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mssse3" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mssse3" ) + message(STATUS "Use SSSE3 SIMD instructions") + add_definitions(-DUSE_SSSE3) + elseif(C_MSVC) + set( CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /arch:SSSE3" ) + set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /arch:SSSE3" ) + set( CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Oi /GL /Ot /Ox" ) + set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oi /GL /Ot /Ox" ) + set( CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG" ) + message(STATUS "Use MSVC SSSE3 SIMD instructions") + add_definitions (/D "_CRT_SECURE_NO_WARNINGS") + add_definitions(-DUSE_SSSE3) + endif() + elseif (FORCE_SSE41) + set(HAS_SSSE3 ON CACHE BOOL "SSSE3 SIMD enabled") + set(HAS_SSE4_1 ON CACHE BOOL "Architecture has SSE 4.1 SIMD enabled") + if(C_GCC OR C_CLANG) + set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse4.1" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.1" ) + message(STATUS "Use SSE 4.1 SIMD instructions") + add_definitions(-DUSE_SSSE3) + add_definitions(-DUSE_SSE4_1) + elseif(C_MSVC) + # seems that from MSVC 2015 comiler doesn't support those flags + set( CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /arch:SSE4_1" ) + set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /arch:SSE4_1" ) + set( CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Oi /GL /Ot /Ox" ) + set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oi /GL /Ot /Ox" ) + set( CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG" ) + message(STATUS "Use SSE 4.1 SIMD instructions") + add_definitions (/D "_CRT_SECURE_NO_WARNINGS") + add_definitions(-DUSE_SSSE3) + add_definitions(-DUSE_SSE4_1) + endif() + endif() +else () + if (${ARCHITECTURE} MATCHES "x86_64|x86") + if(C_MSVC) + try_run(RUN_SSE2 COMPILE_SSE2 "${CMAKE_BINARY_DIR}/tmp" "${TEST_DIR}/test_x86_sse2.cxx" COMPILE_DEFINITIONS /O0) + else() + try_run(RUN_SSE2 COMPILE_SSE2 "${CMAKE_BINARY_DIR}/tmp" "${TEST_DIR}/test_x86_sse2.cxx" COMPILE_DEFINITIONS -msse2 -O0) + endif() + if(COMPILE_SSE2 AND RUN_SSE2 EQUAL 0) + set(HAS_SSE2 ON CACHE BOOL "Architecture has SSSE2 SIMD enabled") + message(STATUS "Use SSE2 SIMD instructions") + if(C_GCC OR C_CLANG) + set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse2" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse2" ) + add_definitions(-DUSE_SSE2) + elseif(C_MSVC) + set( CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /arch:SSE2" ) + set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /arch:SSE2" ) + set( CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Oi /GL /Ot /Ox /arch:SSE2" ) + set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oi /GL /Ot /Ox /arch:SSE2" ) + set( CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG" ) + add_definitions (/D "_CRT_SECURE_NO_WARNINGS") + add_definitions(-DUSE_SSE2) + endif() + else() + set(HAS_SSE2 OFF CACHE BOOL "Architecture does not have SSSE2 SIMD enabled") + endif() + if(C_MSVC) + try_run(RUN_SSSE3 COMPILE_SSSE3 "${CMAKE_BINARY_DIR}/tmp" "${TEST_DIR}/test_x86_ssse3.cxx" COMPILE_DEFINITIONS /O0) + else() + try_run(RUN_SSSE3 COMPILE_SSSE3 "${CMAKE_BINARY_DIR}/tmp" "${TEST_DIR}/test_x86_ssse3.cxx" COMPILE_DEFINITIONS -mssse3 -O0) + endif() + if(COMPILE_SSSE3 AND RUN_SSSE3 EQUAL 0) + set(HAS_SSSE3 ON CACHE BOOL "Architecture has SSSE3 SIMD enabled") + message(STATUS "Use SSSE3 SIMD instructions") + if(C_GCC OR C_CLANG) + set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mssse3" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mssse3" ) + add_definitions(-DUSE_SSSE3) + elseif(C_MSVC) + # seems not present on MSVC 2017 + #set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oi /GL /Ot /Ox /arch:SSSE3" ) + set( CMAKE_C_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oi /GL /Ot /Ox" ) + set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oi /GL /Ot /Ox" ) + set( CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG" ) + add_definitions (/D "_CRT_SECURE_NO_WARNINGS") + add_definitions(-DUSE_SSSE3) + endif() + else() + set(HAS_SSSE3 OFF CACHE BOOL "Architecture does not have SSSE3 SIMD enabled") + endif() + if(C_MSVC) + try_run(RUN_SSE4_1 COMPILE_SSE4_1 "${CMAKE_BINARY_DIR}/tmp" "${TEST_DIR}/test_x86_sse41.cxx" COMPILE_DEFINITIONS /O0) + else() + try_run(RUN_SSE4_1 COMPILE_SSE4_1 "${CMAKE_BINARY_DIR}/tmp" "${TEST_DIR}/test_x86_sse41.cxx" COMPILE_DEFINITIONS -msse4.1 -O0) + endif() + if(COMPILE_SSE4_1 AND RUN_SSE4_1 EQUAL 0) + set(HAS_SSE4_1 ON CACHE BOOL "Architecture has SSE 4.1 SIMD enabled") + message(STATUS "Use SSE 4.1 SIMD instructions") + if(C_GCC OR C_CLANG) + set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse4.1" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.1" ) + add_definitions(-DUSE_SSE4_1) + elseif(C_MSVC) + # seems not present on MSVC 2017 + #set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /arch:SSE4_1" ) + #set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oi /GL /Ot /Ox /arch:SSE4_1" ) + set( CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Oi /GL /Ot /Ox" ) + set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oi /GL /Ot /Ox" ) + set( CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG" ) + add_definitions (/D "_CRT_SECURE_NO_WARNINGS") + add_definitions(-DUSE_SSE4_1) + endif() + else() + set(HAS_SSE4_1 OFF CACHE BOOL "Architecture does not have SSE 4.1 SIMD enabled") + endif() + if(C_MSVC) + try_run(RUN_SSE4_2 COMPILE_SSE4_2 "${CMAKE_BINARY_DIR}/tmp" "${TEST_DIR}/test_x86_sse42.cxx" COMPILE_DEFINITIONS /O0) + else() + try_run(RUN_SSE4_2 COMPILE_SSE4_2 "${CMAKE_BINARY_DIR}/tmp" "${TEST_DIR}/test_x86_sse42.cxx" COMPILE_DEFINITIONS -msse4.2 -O0) + endif() + if(COMPILE_SSE4_2 AND RUN_SSE4_2 EQUAL 0) + set(HAS_SSE4_2 ON CACHE BOOL "Architecture has SSE 4.2 SIMD enabled") + message(STATUS "Use SSE 4.2 SIMD instructions") + if(C_GCC OR C_CLANG) + set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse4.2" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.2" ) + add_definitions(-DUSE_SSE4_2) + elseif(C_MSVC) + # seems not present on MSVC 2017 + #set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /arch:SSE4_2" ) + #set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oi /GL /Ot /Ox /arch:SSE4_2" ) + set( CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Oi /GL /Ot /Ox" ) + set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Oi /GL /Ot /Ox" ) + set( CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG" ) + add_definitions (/D "_CRT_SECURE_NO_WARNINGS") + add_definitions(-DUSE_SSE4_2) + endif() + else() + set(HAS_SSE4_2 OFF CACHE BOOL "Architecture does not have SSE 4.2 SIMD enabled") + endif() + if(C_MSVC) + try_run(RUN_AVX COMPILE_AVX "${CMAKE_BINARY_DIR}/tmp" "${TEST_DIR}/test_x86_avx.cxx" COMPILE_DEFINITIONS /O0) + else() + try_run(RUN_AVX COMPILE_AVX "${CMAKE_BINARY_DIR}/tmp" "${TEST_DIR}/test_x86_avx.cxx" COMPILE_DEFINITIONS -mavx -O0) + endif() + if(COMPILE_AVX AND RUN_AVX EQUAL 0) + set(HAS_AVX ON CACHE BOOL "Architecture has AVX SIMD enabled") + message(STATUS "Use AVX SIMD instructions") + if(C_GCC OR C_CLANG) + set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mavx" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx" ) + add_definitions(-DUSE_AVX) + elseif(C_MSVC) + set( CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /arch:AVX" ) + set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /arch:AVX" ) + set( CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Oi /GL /Ot /Ox /arch:AVX" ) + set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oi /GL /Ot /Ox /arch:AVX" ) + set( CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG" ) + add_definitions (/D "_CRT_SECURE_NO_WARNINGS") + add_definitions(-DUSE_AVX) + endif() + else() + set(HAS_AVX OFF CACHE BOOL "Architecture does not have AVX SIMD enabled") + endif() + if(C_MSVC) + try_run(RUN_AVX2 COMPILE_AVX2 "${CMAKE_BINARY_DIR}/tmp" "${TEST_DIR}/test_x86_avx2.cxx" COMPILE_DEFINITIONS /O0) + else() + try_run(RUN_AVX2 COMPILE_AVX2 "${CMAKE_BINARY_DIR}/tmp" "${TEST_DIR}/test_x86_avx2.cxx" COMPILE_DEFINITIONS -mavx2 -O0) + endif() + if(COMPILE_AVX2 AND RUN_AVX2 EQUAL 0) + set(HAS_AVX2 ON CACHE BOOL "Architecture has AVX2 SIMD enabled") + message(STATUS "Use AVX2 SIMD instructions") + if(C_GCC OR C_CLANG) + set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mavx2" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx2" ) + add_definitions(-DUSE_AVX2) + elseif(C_MSVC) + set( CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /arch:AVX2" ) + set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /arch:AVX2" ) + set( CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Oi /GL /Ot /Ox /arch:AVX2" ) + set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oi /GL /Ot /Ox /arch:AVX2" ) + set( CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG" ) + add_definitions (/D "_CRT_SECURE_NO_WARNINGS") + add_definitions(-DUSE_AVX2) + endif() + else() + set(HAS_AVX2 OFF CACHE BOOL "Architecture does not have AVX2 SIMD enabled") + endif() + if(C_MSVC) + try_run(RUN_AVX512 COMPILE_AVX512 "${CMAKE_BINARY_DIR}/tmp" "${TEST_DIR}/test_x86_avx512.cxx" COMPILE_DEFINITIONS /O0) + else() + try_run(RUN_AVX512 COMPILE_AVX512 "${CMAKE_BINARY_DIR}/tmp" "${TEST_DIR}/test_x86_avx512.cxx" COMPILE_DEFINITIONS -mavx512f -O0) + endif() + if(COMPILE_AVX512 AND RUN_AVX512 EQUAL 0) + set(HAS_AVX512 ON CACHE BOOL "Architecture has AVX512 SIMD enabled") + message(STATUS "Use AVX512 SIMD instructions") + if(C_GCC OR C_CLANG) + set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mavx512f" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx512f" ) + add_definitions(-DUSE_AVX512) + elseif(C_MSVC) + set( CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /arch:AVX512" ) + set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /arch:AVX512" ) + set( CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Oi /GL /Ot /Ox /arch:AVX512" ) + set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oi /GL /Ot /Ox /arch:AVX512" ) + set( CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG" ) + add_definitions (/D "_CRT_SECURE_NO_WARNINGS") + add_definitions(-DUSE_AVX512) + endif() + else() + set(HAS_AVX512 OFF CACHE BOOL "Architecture does not have AVX512 SIMD enabled") + endif() +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) + endif() + if(COMPILE_NEON AND RUN_NEON EQUAL 0) + set(HAS_NEON ON CACHE BOOL "Architecture has NEON SIMD enabled") + message(STATUS "Use NEON SIMD instructions") + if(C_GCC OR C_CLANG) + set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mfpu=neon" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpu=neon" ) + add_definitions(-DUSE_NEON) + endif() + else() + set(HAS_NEON OFF CACHE BOOL "Architecture does not have NEON SIMD enabled") + endif() +elseif(ARCHITECTURE_ARM64) + # Advanced SIMD (aka NEON) is mandatory for AArch64 + set(HAS_NEON ON CACHE BOOL "Architecture has NEON SIMD enabled") + message(STATUS "Use NEON SIMD instructions") + add_definitions(-DUSE_NEON) +endif() +endif() + +# clear binary test folder +FILE(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/tmp) diff --git a/cmake/modules/FindCompiler.cmake b/cmake/modules/FindCompiler.cmake new file mode 100644 index 0000000..f339a73 --- /dev/null +++ b/cmake/modules/FindCompiler.cmake @@ -0,0 +1,13 @@ +# Clang or AppleClang (see CMP0025) +if(NOT DEFINED C_CLANG AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(C_CLANG 1) +elseif(NOT DEFINED C_GCC AND CMAKE_CXX_COMPILER_ID MATCHES "GNU") + set(C_GCC 1) +elseif(NOT DEFINED C_MSVC AND CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + set(C_MSVC 1) + if(MSVC_VERSION GREATER 1910 AND MSVC_VERSION LESS 1919) + set(VS2017 ON) + elseif(MSVC_VERSION GREATER 1899 AND MSVC_VERSION LESS 1910) + set(VS2015 ON) + endif() +endif() diff --git a/cmake/modules/FindGPSD.cmake b/cmake/modules/FindGPSD.cmake new file mode 100644 index 0000000..d21b331 --- /dev/null +++ b/cmake/modules/FindGPSD.cmake @@ -0,0 +1,88 @@ +# - Try to find GPSD +# Once done this will define +# +# GPSD_FOUND - system has GPSD +# GPSD_INCLUDE_DIRS - the GPSD include directory +# GPSD_LIBRARIES - Link these to use GPSD +# GPSD_DEFINITIONS - Compiler switches required for using GPSD +# +# Copyright (c) 2006 Andreas Schneider +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +set(GPSD_ROOT_DIR + "${GPSD_ROOT_DIR}" + CACHE + PATH + "Directory to search for gpsd") + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_GPSD libgps) +endif() + +if (GPSD_LIBRARIES AND GPSD_INCLUDE_DIRS) + # in cache already + set(GPSD_FOUND TRUE) +else (GPSD_LIBRARIES AND GPSD_INCLUDE_DIRS) + find_path(GPSD_INCLUDE_DIRS + NAMES + gps.h + PATHS + /usr/include + /usr/local/include + /opt/local/include + /sw/include + /usr/include/gps + /usr/local/include/gps + /opt/local/include/gps + /sw/include/gps + HINTS + ${PC_GPSD_INCLUDEDIR} + ${GPSD_ROOT_DIR} + ) + + # debian uses version suffixes + # add suffix evey new release + find_library(GPSD_LIBRARIES + NAMES + gps + PATHS + /usr/lib64 + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib + HINTS + ${PC_GPSD_LIBDIR} + ${GPSD_ROOT_DIR} + ) + + if (GPSD_INCLUDE_DIRS AND GPSD_LIBRARIES) + set(GPSD_FOUND TRUE) + endif (GPSD_INCLUDE_DIRS AND GPSD_LIBRARIES) + + if (GPSD_FOUND) + if (NOT GPSD_FIND_QUIETLY) + message(STATUS "Found GPSD: ${GPSD_LIBRARIES}") + endif (NOT GPSD_FIND_QUIETLY) + else (GPSD_FOUND) + if (GPSD_FIND_REQUIRED) + message(FATAL_ERROR "Could not find GPSD") + endif (GPSD_FIND_REQUIRED) + endif (GPSD_FOUND) + + # show the GPSD_INCLUDE_DIRS and GPSD_LIBRARIES variables only in the advanced view + mark_as_advanced(GPSD_INCLUDE_DIRS GPSD_LIBRARIES) + +endif (GPSD_LIBRARIES AND GPSD_INCLUDE_DIRS) + +# maybe on CYGWIN gpsd works +if (WIN32) + set(GPSD_FOUND FALSE) + set(GPSD_LIBRARIES "") + set(GPSD_INCLUDE_DIRS "") +endif (WIN32) diff --git a/cmake/modules/FindPortaudio.cmake b/cmake/modules/FindPortaudio.cmake new file mode 100644 index 0000000..9cda342 --- /dev/null +++ b/cmake/modules/FindPortaudio.cmake @@ -0,0 +1,64 @@ +# - Try to find Portaudio +# Once done this will define +# +# PORTAUDIO_FOUND - system has Portaudio +# PORTAUDIO_INCLUDE_DIRS - the Portaudio include directory +# PORTAUDIO_LIBRARIES - Link these to use Portaudio + +set(PORTAUDIO_ROOT_DIR + "${PORTAUDIO_ROOT_DIR}" + CACHE + PATH + "Directory to search for portaudio") + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_PORTAUDIO portaudio-2.0) +endif() + +find_path(PORTAUDIO_INCLUDE_DIRS + NAMES + portaudio.h + PATHS + /usr/local/include + /usr/include + /opt/local/include + HINTS + ${PC_PORTAUDIO_INCLUDEDIR} + ${PORTAUDIO_ROOT_DIR} + ) + +find_library(PORTAUDIO_LIBRARIES + NAMES + portaudio + PATHS + /usr/local/lib + /usr/lib + /usr/lib64 + /opt/local/lib + HINTS + ${PC_PORTAUDIO_LIBDIR} + ${PORTAUDIO_ROOT_DIR} + ) + +mark_as_advanced(PORTAUDIO_INCLUDE_DIRS PORTAUDIO_LIBRARIES) + +# Found PORTAUDIO, but it may be version 18 which is not acceptable. +if(EXISTS ${PORTAUDIO_INCLUDE_DIRS}/portaudio.h) + include(CheckCXXSourceCompiles) + set(CMAKE_REQUIRED_INCLUDES_SAVED ${CMAKE_REQUIRED_INCLUDES}) + set(CMAKE_REQUIRED_INCLUDES ${PORTAUDIO_INCLUDE_DIRS}) + CHECK_CXX_SOURCE_COMPILES( + "#include \nPaDeviceIndex pa_find_device_by_name(const char *name); int main () {return 0;}" + PORTAUDIO2_FOUND) + set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES_SAVED}) + unset(CMAKE_REQUIRED_INCLUDES_SAVED) + if(PORTAUDIO2_FOUND) + INCLUDE(FindPackageHandleStandardArgs) + FIND_PACKAGE_HANDLE_STANDARD_ARGS(PORTAUDIO DEFAULT_MSG PORTAUDIO_INCLUDE_DIRS PORTAUDIO_LIBRARIES) + else(PORTAUDIO2_FOUND) + message(STATUS + " portaudio.h not compatible (requires API 2.0)") + set(PORTAUDIO_FOUND FALSE) + endif(PORTAUDIO2_FOUND) +endif() diff --git a/cmake/modules/Findhamlib.cmake b/cmake/modules/Findhamlib.cmake new file mode 100644 index 0000000..2086a98 --- /dev/null +++ b/cmake/modules/Findhamlib.cmake @@ -0,0 +1,67 @@ +# - Try to find Hamlib +# +# HAMLIB_FOUND - system has Hamlib +# HAMLIB_LIBRARIES - location of the library for hamlib +# HAMLIB_INCLUDE_DIRS - location of the include files for hamlib +# +# Requires these CMake modules: +# FindPackageHandleStandardArgs (known included with CMake >=2.6.2) +# +# Original Author: +# 2019 Davide Gerhard + +set(HAMLIB_ROOT_DIR + "${HAMLIB_ROOT_DIR}" + CACHE + PATH + "Directory to search for hamlib") + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_HAMLIB hamlib) +endif() + +find_path(HAMLIB_INCLUDE_DIR + NAMES hamlib/rig.h + PATHS + /usr/include + /usr/local/include + /opt/local/include + HINTS + ${PC_HAMLIB_INCLUDEDIR} + ${HAMLIB_ROOT_DIR} + ) + +find_library(HAMLIB_LIBRARY + NAMES hamlib + PATHS + /usr/lib64/hamlib + /usr/lib/hamlib + /usr/lib64 + /usr/lib + /usr/local/lib64/hamlib + /usr/local/lib/hamlib + /usr/local/lib64 + /usr/local/lib + /opt/local/lib + /opt/local/lib/hamlib + HINTS + ${PC_HAMLIB_LIBDIR} + ${HAMLIB_ROOT_DIR} + + ) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(HAMLIB + DEFAULT_MSG + HAMLIB_LIBRARY + HAMLIB_INCLUDE_DIR + ) + +if(HAMLIB_FOUND) + list(APPEND HAMLIB_LIBRARIES ${HAMLIB_LIBRARY}) + list(APPEND HAMLIB_INCLUDE_DIRS ${HAMLIB_INCLUDE_DIR}) + mark_as_advanced(HAMLIB_ROOT_DIR) +endif() + +mark_as_advanced(HAMLIB_INCLUDE_DIR HAMLIB_LIBRARY) diff --git a/cmake/modules/Findudev.cmake b/cmake/modules/Findudev.cmake new file mode 100644 index 0000000..38ba2e2 --- /dev/null +++ b/cmake/modules/Findudev.cmake @@ -0,0 +1,85 @@ +# - try to find the udev library +# +# Cache Variables: (probably not for direct use in your scripts) +# UDEV_INCLUDE_DIR +# UDEV_SOURCE_DIR +# UDEV_LIBRARY +# +# Non-cache variables you might use in your CMakeLists.txt: +# UDEV_FOUND +# UDEV_INCLUDE_DIRS +# UDEV_LIBRARIES +# +# Requires these CMake modules: +# FindPackageHandleStandardArgs (known included with CMake >=2.6.2) +# +# Original Author: +# 2014 Kevin M. Godby +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +set(UDEV_ROOT_DIR + "${UDEV_ROOT_DIR}" + CACHE + PATH + "Directory to search for udev") + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_LIBUDEV libudev) +endif() + +find_library(UDEV_LIBRARY + NAMES + udev + PATHS + ${PC_LIBUDEV_LIBRARY_DIRS} + ${PC_LIBUDEV_LIBDIR} + /usr/lib64 + /usr/lib + /usr/local/lib + HINTS + "${UDEV_ROOT_DIR}" + PATH_SUFFIXES + lib + ) + +get_filename_component(_libdir "${UDEV_LIBRARY}" PATH) + +find_path(UDEV_INCLUDE_DIR + NAMES + libudev.h + PATHS + /usr/include + /usr/local/include + ${PC_LIBUDEV_INCLUDE_DIRS} + ${PC_LIBUDEV_INCLUDEDIR} + HINTS + "${_libdir}" + "${_libdir}/.." + "${UDEV_ROOT_DIR}" + PATH_SUFFIXES + include + ) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(UDEV + DEFAULT_MSG + UDEV_LIBRARY + UDEV_INCLUDE_DIR + ) + +if (UDEV_INCLUDE_DIR AND UDEV_LIBRARY) + set(UDEV_FOUND TRUE) +endif (UDEV_INCLUDE_DIR AND UDEV_LIBRARY) + +if(UDEV_FOUND) + list(APPEND UDEV_LIBRARIES ${UDEV_LIBRARY}) + list(APPEND UDEV_INCLUDE_DIRS ${UDEV_INCLUDE_DIR}) + mark_as_advanced(UDEV_ROOT_DIR) +endif() + +mark_as_advanced(UDEV_INCLUDE_DIR + UDEV_LIBRARY) diff --git a/99-direwolf-cmedia.rules b/conf/99-direwolf-cmedia.rules similarity index 100% rename from 99-direwolf-cmedia.rules rename to conf/99-direwolf-cmedia.rules diff --git a/conf/CMakeLists.txt b/conf/CMakeLists.txt new file mode 100644 index 0000000..d4a229d --- /dev/null +++ b/conf/CMakeLists.txt @@ -0,0 +1,48 @@ +# generate conf per platform +file(READ "${CUSTOM_CONF_DIR}/generic.conf" file_content) + +if(LINUX) + string(REGEX REPLACE "\n%W%[^\n]*" "" file_content "${file_content}") + string(REGEX REPLACE "\n%M%[^\n]*" "" file_content "${file_content}") + string(REGEX REPLACE "\n%L%([^\n]*)" "\n\\1" file_content "${file_content}") +elseif(WIN32 OR CYGWIN) + string(REGEX REPLACE "\n%M%[^\n]*" "" file_content "${file_content}") + string(REGEX REPLACE "\n%L%[^\n]*" "" file_content "${file_content}") + string(REGEX REPLACE "\n%W%([^\n]*)" "\n\\1" file_content "${file_content}") +else() # macOS FreeBSD OpenBSD + string(REGEX REPLACE "\n%W%[^\n]*" "" file_content "${file_content}") + string(REGEX REPLACE "\n%L%[^\n]*" "" file_content "${file_content}") + string(REGEX REPLACE "\n%M%([^\n]*)" "\n\\1" file_content "${file_content}") +endif() + +# remove remark +string(REGEX REPLACE "\n%R%[^\n]*" "" file_content "${file_content}") + +# clear common lines +string(REGEX REPLACE "\n%C%([^\n]*)" "\n\\1" file_content "${file_content}") +string(REGEX REPLACE "^%C%([^\n]*)" "\\1" file_content "${file_content}") + +file(WRITE "${CMAKE_BINARY_DIR}/direwolf.conf" "${file_content}") + +# install udev rules for CM108 +if(LINUX) + install(FILES "${CUSTOM_CONF_DIR}/99-direwolf-cmedia.rules" DESTINATION /etc/udev/rules.d/) +endif() + +install(FILES "${CMAKE_BINARY_DIR}/direwolf.conf" DESTINATION ${INSTALL_CONF_DIR}) +install(FILES "${CUSTOM_CONF_DIR}/sdr.conf" DESTINATION ${INSTALL_CONF_DIR}) + +# Put sample configuration & startup files in home directory. +# This step would be done as ordinary user. +# Some people like to put the direwolf config file in /etc/ax25. +# Note that all of these are also in $(DESTDIR)/share/doc/direwolf/examples/. +if(NOT (WIN32 OR CYGWIN)) + add_custom_target(install-conf + COMMAND ${CMAKE_COMMAND} + -DCUSTOM_BINARY_DIR="${CMAKE_BINARY_DIR}" + -DCUSTOM_CONF_DIR="${CUSTOM_CONF_DIR}" + -DCUSTOM_SCRIPTS_DIR="${CUSTOM_SCRIPTS_DIR}" + -DCUSTOM_TELEMETRY_DIR="${CUSTOM_TELEMETRY_DIR}" + -P "${CMAKE_SOURCE_DIR}/conf/install_conf.cmake" + ) +endif() diff --git a/conf/generic.conf b/conf/generic.conf new file mode 100644 index 0000000..b77d5df --- /dev/null +++ b/conf/generic.conf @@ -0,0 +1,586 @@ +%C%############################################################# +%C%# # +%C%# Configuration file for Dire Wolf # +%C%# # +%L%# Linux version # +%W%# Windows version # +%M%# Macintosh version # +%C%# # +%C%############################################################# +%R% +%R% +%R% The sample config file was getting pretty messy +%R% with the Windows and Linux differences. +%R% It would be a maintenance burden to keep most of +%R% two different versions in sync. +%R% This common source is now used to generate the +%R% two different variations while having only a single +%R% copy of the common parts. +%R% +%R% The first column contains one of the following: +%R% +%R% R remark which is discarded. +%R% C common to both versions. +%R% W Windows version only. +%R% L Linux version only. +%R% M Macintosh version and possibly others (portaudio used). +%R% +%C%# +%C%# Extensive documentation can be found here: +%C%# Stable release - https://github.com/wb2osz/direwolf/tree/master/doc +%C%# Latest development - https://github.com/wb2osz/direwolf/tree/dev/doc +%C%# +%W%# The complete documentation set can also be found in the doc folder. +%L%# The complete documentation set can also be found in +%L%# /usr/local/share/doc/direwolf/ or /usr/share/doc/direwolf/ +%L%# Concise "man" pages are also available for Linux. +%M%# /usr/local/share/doc/direwolf/ or /usr/share/doc/direwolf/ +%M%# Concise "man" pages are also available for Mac OSX. +%C%# +%C%# This sample file does not have examples for all of the possibilities. +%C%# Consult the User Guide for more details on configuration options.%C%# +%C%# +%C%# These are the most likely settings you might change: +%C%# +%C%# (1) MYCALL - call sign and SSID for your station. +%C%# +%C%# Look for lines starting with MYCALL and +%C%# change NOCALL to your own. +%C%# +%C%# (2) PBEACON - enable position beaconing. +%C%# +%C%# Look for lines starting with PBEACON and +%C%# modify for your call, location, etc. +%C%# +%C%# (3) DIGIPEATER - configure digipeating rules. +%C%# +%C%# Look for lines starting with DIGIPEATER. +%C%# Most people will probably use the given example. +%C%# Just remove the "#" from the start of the line +%C%# to enable it. +%C%# +%C%# (4) IGSERVER, IGLOGIN - IGate server and login +%C%# +%C%# Configure an IGate client to relay messages between +%C%# radio and internet servers. +%C%# +%C%# +%C%# The default location is "direwolf.conf" in the current working directory. +%L%# On Linux, the user's home directory will also be searched. +%C%# An alternate configuration file location can be specified with the "-c" command line option. +%C%# +%C%# As you probably guessed by now, # indicates a comment line. +%C%# +%C%# Remove the # at the beginning of a line if you want to use a sample +%C%# configuration that is currently commented out. +%C%# +%C%# Commands are a keyword followed by parameters. +%C%# +%C%# Command key words are case insensitive. i.e. upper and lower case are equivalent. +%C%# +%C%# Command parameters are generally case sensitive. i.e. upper and lower case are different. +%C%# +%C% +%C% +%C%############################################################# +%C%# # +%C%# FIRST AUDIO DEVICE PROPERTIES # +%C%# (Channel 0 + 1 if in stereo) # +%C%# # +%C%############################################################# +%C% +%C%# +%C%# Many people will simply use the default sound device. +%C%# Some might want to use an alternative device by choosing it here. +%C%# +%R% ---------- Windows ---------- +%R% +%W%# When the Windows version starts up, it displays something like +%W%# this with the available sound devices and capabilities: +%W%# +%W%# Available audio input devices for receive (*=selected): +%W%# * 0: Microphone (C-Media USB Headpho (channel 2) +%W%# 1: Microphone (Bluetooth SCO Audio +%W%# 2: Microphone (Bluetooth AV Audio) +%W%# * 3: Microphone (Realtek High Defini (channels 0 & 1) +%W%# Available audio output devices for transmit (*=selected): +%W%# * 0: Speakers (C-Media USB Headphone (channel 2) +%W%# 1: Speakers (Bluetooth SCO Audio) +%W%# 2: Realtek Digital Output(Optical) +%W%# 3: Speakers (Bluetooth AV Audio) +%W%# * 4: Speakers (Realtek High Definiti (channels 0 & 1) +%W%# 5: Realtek Digital Output (Realtek +%W%# +%W%# Example: To use the microphone and speaker connections on the +%W%# system board, either of these forms can be used: +%W% +%W%#ADEVICE High +%W%#ADEVICE 3 4 +%W% +%W% +%W%# Example: To use the USB Audio, use a command like this with +%W%# the input and output device numbers. (Remove the # comment character.) +%W%#ADEVICE USB +%W% +%W%# You can also use "-" or "stdin" to pipe stdout from +%W%# some other application such as a software defined radio. +%W%# "stdin" is not an audio device. Don't use this unless you +%W%# understand what this means. Read the User Guide. +%W%# You can also specify "UDP:" and an optional port for input. +%W%# Something different must be specified for output. +%W% +%W%# ADEVICE stdin 0 +%W%# ADEVICE UDP:7355 0 +%W% +%W%# The position in the list can change when devices (e.g. USB) are added and removed. +%W%# You can also specify devices by using part of the name. +%W%# Here is an example of specifying the USB Audio device. +%W%# This is case-sensitive. Upper and lower case are not treated the same. +%W% +%W%#ADEVICE USB +%W% +%W% +%R% ---------- Linux ---------- +%R% +%L%# Linux ALSA is complicated. See User Guide for discussion. +%L%# To use something other than the default, generally use plughw +%L%# and a card number reported by "arecord -l" command. Example: +%L% +%L%# ADEVICE plughw:1,0 +%L% +%L%# You can also use "-" or "stdin" to pipe stdout from +%L%# some other application such as a software defined radio. +%L%# "stdin" is not an audio device. Don't use this unless you +%L%# understand what this means. Read the User Guide. +%L%# You can also specify "UDP:" and an optional port for input. +%L%# Something different must be specified for output. +%L% +%L%# ADEVICE stdin plughw:1,0 +%L%# ADEVICE UDP:7355 default +%L% +%R% ---------- Mac ---------- +%R% +%M%# Macintosh Operating System uses portaudio driver for audio +%M%# input/output. Default device selection not available. User/OP +%M%# must configure the sound input/output option. Note that +%M%# the device names can contain spaces. In this case, the names +%M%# must be enclosed by quotes. +%M%# +%M%# Examples: +%M%# +%M%ADEVICE "Built-in Input" "Built-in Output" +%M% +%M%# ADEVICE "USB Audio Codec:6" "USB Audio Codec:5" +%M%# +%M%# +%M%# You can also use "-" or "stdin" to pipe stdout from +%M%# some other application such as a software defined radio. +%M%# "stdin" is not an audio device. Don't use this unless you +%M%# understand what this means. Read the User Guide. +%M%# You can also specify "UDP:" and an optional port for input. +%M%# Something different must be specified for output. +%M% +%M%# ADEVICE UDP:7355 default +%M%# +%C% +%C%# +%C%# Number of audio channels for this souncard: 1 (mono) or 2 (stereo). +%C%# 1 is the default so there is no need to specify it. +%C%# +%C% +%C%#ACHANNELS 2 +%C% +%C% +%C%############################################################# +%C%# # +%C%# SECOND AUDIO DEVICE PROPERTIES # +%C%# (Channel 2 + 3 if in stereo) # +%C%# # +%C%############################################################# +%C% +%C%#ADEVICE1 ... +%C% +%C% +%C%############################################################# +%C%# # +%C%# THIRD AUDIO DEVICE PROPERTIES # +%C%# (Channel 4 + 5 if in stereo) # +%C%# # +%C%############################################################# +%C% +%C%#ADEVICE2 ... +%C% +%C% +%C%############################################################# +%C%# # +%C%# CHANNEL 0 PROPERTIES # +%C%# # +%C%############################################################# +%C% +%C%CHANNEL 0 +%C% +%C%# +%C%# The following MYCALL, MODEM, PTT, etc. configuration items +%C%# apply to the most recent CHANNEL. +%C%# +%C% +%C%# +%C%# Station identifier for this channel. +%C%# Multiple channels can have the same or different names. +%C%# +%C%# It can be up to 6 letters and digits with an optional ssid. +%C%# The APRS specification requires that it be upper case. +%C%# +%C%# Example (don't use this unless you are me): MYCALL WB2OSZ-5 +%C%# +%C% +%C%MYCALL N0CALL +%C% +%C%# +%C%# Pick a suitable modem speed based on your situation. +%C%# 1200 Most common for VHF/UHF. Default if not specified. +%C%# 2400 QPSK compatible with MFJ-2400, and probably PK232-2400 & KPC-2400. +%C%# 300 Low speed for HF SSB. Default tones 1600 & 1800. +%C%# EAS Emergency Alert System (EAS) Specific Area Message Encoding (SAME). +%C%# 9600 G3RUH style - Can't use Microphone and Speaker connections. +%C%# AIS International system for tracking ships on VHF. +%C%# Also uses 9600 bps so Speaker connection won't work. +%C%# +%C%# In most cases you can just specify the speed. Examples: +%C%# +%C% +%C%MODEM 1200 +%C%#MODEM 9600 +%C% +%C%# +%C%# Many options are available for great flexibility. +%C%# See User Guide for details. +%C%# +%C% +%C%# +%C%# Uncomment line below to enable the DTMF decoder for this channel. +%C%# +%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! +%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. +%C%# Both can be used for interfaces that want them driven with opposite polarity. +%C%# +%L%# COM1 can be used instead of /dev/ttyS0, COM2 for /dev/ttyS1, and so on. +%L%# +%C% +%C%#PTT COM1 RTS +%C%#PTT COM1 RTS -DTR +%L%#PTT /dev/ttyUSB0 RTS +%C% +%L%# +%L%# On Linux, you can also use general purpose I/O pins if +%L%# your system is configured for user access to them. +%L%# This would apply mostly to microprocessor boards, not a regular PC. +%L%# See separate Raspberry Pi document for more details. +%L%# The number may be preceded by "-" to invert the signal. +%L%# +%L% +%L%#PTT GPIO 25 +%L% +%C%# The Data Carrier Detect (DCD) signal can be sent to the same places +%C%# as the PTT signal. This could be used to light up an LED like a normal TNC. +%C% +%C%#DCD COM1 -DTR +%L%#DCD GPIO 24 +%C% +%C% +%C%############################################################# +%C%# # +%C%# CHANNEL 1 PROPERTIES # +%C%# # +%C%############################################################# +%C% +%C%#CHANNEL 1 +%C% +%C%# +%C%# Specify MYCALL, MODEM, PTT, etc. configuration items for +%C%# CHANNEL 1. Repeat for any other channels. +%C% +%C% +%C%############################################################# +%C%# # +%C%# TEXT TO SPEECH COMMAND FILE # +%C%# # +%C%############################################################# +%C% +%W%#SPEECH dwespeak.bat +%L%#SPEECH dwespeak.sh +%C% +%C% +%C%############################################################# +%C%# # +%C%# VIRTUAL TNC SERVER PROPERTIES # +%C%# # +%C%############################################################# +%C% +%C%# +%C%# Dire Wolf acts as a virtual TNC and can communicate with +%C%# client applications by different protocols: +%C%# +%C%# - the "AGW TCPIP Socket Interface" - default port 8000 +%C%# - KISS protocol over TCP socket - default port 8001 +%W%# - KISS TNC via serial port +%L%# - KISS TNC via pseudo terminal (-p command line option) +%C%# +%C% +%C%AGWPORT 8000 +%C%KISSPORT 8001 +%C% +%W%# +%W%# Some applications are designed to operate with only a physical +%W%# TNC attached to a serial port. For these, we provide a virtual serial +%W%# port that appears to be connected to a TNC. +%W%# +%W%# Take a look at the User Guide for instructions to set up +%W%# two virtual serial ports named COM3 and COM4 connected by +%W%# a null modem. +%W%# +%W%# Using the configuration described, Dire Wolf will connect to +%W%# COM3 and the client application will use COM4. +%W%# +%W%# Uncomment following line to use this feature. +%W% +%W%#NULLMODEM COM3 +%W% +%W% +%C%# +%C%# It is sometimes possible to recover frames with a bad FCS. +%C%# This applies to all channels. +%C%# +%C%# 0 [NONE] - Don't try to repair. +%C%# 1 [SINGLE] - Attempt to fix single bit error. (default) +%C%# ... see User Guide for more values and in-depth discussion. +%C%# +%C% +%C%#FIX_BITS 0 +%C% +%C%# +%C%############################################################# +%C%# # +%C%# FIXED POSIION BEACONING PROPERTIES # +%C%# # +%C%############################################################# +%C% +%C% +%C%# +%C%# Beaconing is configured with these two commands: +%C%# +%C%# PBEACON - for a position report (usually yourself) +%C%# OBEACON - for an object report (usually some other entity) +%C%# +%C%# Each has a series of keywords and values for options. +%C%# See User Guide for details. +%C%# +%C%# Example: +%C%# +%C%# This results in a broadcast once every 10 minutes. +%C%# Every half hour, it can travel via two digipeater hops. +%C%# The others are kept local. +%C%# +%C% +%C%#PBEACON delay=1 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" via=WIDE1-1,WIDE2-1 +%C%#PBEACON delay=11 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" +%C%#PBEACON delay=21 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" +%C% +%C% +%C%# With UTM coordinates instead of latitude and longitude. +%C% +%C%#PBEACON delay=1 every=10 overlay=S symbol="digi" zone=19T easting=307477 northing=4720178 +%C% +%C% +%C%# +%C%# When the destination field is set to "SPEECH" the information part is +%C%# converted to speech rather than transmitted as a data frame. +%C%# +%C% +%C%#CBEACON dest="SPEECH" info="Club meeting tonight at 7 pm." +%C% +%C%# Similar for Morse code. If SSID is specified, it is multiplied +%C%# by 2 to get speed in words per minute (WPM). +%C% +%C%#CBEACON dest="MORSE-6" info="de MYCALL" +%C% +%C% +%C%# +%C%# Modify for your particular situation before removing +%C%# the # comment character from the beginning of appropriate lines above. +%C%# +%C% +%C% +%C%############################################################# +%C%# # +%C%# APRS DIGIPEATER PROPERTIES # +%C%# # +%C%############################################################# +%C% +%C%# +%C%# For most common situations, use something like this by removing +%C%# the "#" from the beginning of the line below. +%C%# +%C% +%C%#DIGIPEAT 0 0 ^WIDE[3-7]-[1-7]$|^TEST$ ^WIDE[12]-[12]$ TRACE +%C% +%C%# See User Guide for more explanation of what this means and how +%C%# it can be customized for your particular needs. +%C% +%C%# Filtering can be used to limit was is digipeated. +%C%# For example, only weather weather reports, received on channel 0, +%C%# will be retransmitted on channel 1. +%C%# +%C% +%C%#FILTER 0 1 t/wn +%C% +%C%# Traditional connected mode packet radio uses a different +%C%# type of digipeating. See User Guide for details. +%C% +%C%############################################################# +%C%# # +%C%# INTERNET GATEWAY # +%C%# # +%C%############################################################# +%C% +%C%# First you need to specify the name of a Tier 2 server. +%C%# The current preferred way is to use one of these regional rotate addresses: +%C% +%C%# noam.aprs2.net - for North America +%C%# soam.aprs2.net - for South America +%C%# euro.aprs2.net - for Europe and Africa +%C%# asia.aprs2.net - for Asia +%C%# aunz.aprs2.net - for Oceania +%C% +%C%#IGSERVER noam.aprs2.net +%C% +%C%# You also need to specify your login name and passcode. +%C%# Contact the author if you can't figure out how to generate the passcode. +%C% +%C%#IGLOGIN WB2OSZ-5 123456 +%C% +%C%# That's all you need for a receive only IGate which relays +%C%# messages from the local radio channel to the global servers. +%C% +%C%# Some might want to send an IGate client position directly to a server +%C%# without sending it over the air and relying on someone else to +%C%# forward it to an IGate server. This is done by using sendto=IG rather +%C%# than a radio channel number. Overlay R for receive only, T for two way. +%C% +%C%#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=R lat=42^37.14N long=071^20.83W +%C%#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=T lat=42^37.14N long=071^20.83W +%C% +%C% +%C%# To relay messages from the Internet to radio, you need to add +%C%# one more option with the transmit channel number and a VIA path. +%C% +%C%#IGTXVIA 0 WIDE1-1 +%C% +%C% +%C%# Finally, we don't want to flood the radio channel. +%C%# The IGate function will limit the number of packets transmitted +%C%# during 1 minute and 5 minute intervals. If a limit would +%C%# be exceeded, the packet is dropped and message is displayed in red. +%C% +%C%IGTXLIMIT 6 10 +%C% +%C% +%C%############################################################# +%C%# # +%C%# APRStt GATEWAY # +%C%# # +%C%############################################################# +%C% +%C%# +%C%# Dire Wolf can receive DTMF (commonly known as Touch Tone) +%C%# messages and convert them to packet objects. +%C%# +%C%# See separate "APRStt-Implementation-Notes" document for details. +%C%# +%C% +%C%# +%C%# Sample gateway configuration based on: +%C%# +%C%# http://www.aprs.org/aprstt/aprstt-coding24.txt +%C%# http://www.aprs.org/aprs-jamboree-2013.html +%C%# +%C% +%C%# Define specific points. +%C% +%C%TTPOINT B01 37^55.37N 81^7.86W +%C%TTPOINT B7495088 42.605237 -71.34456 +%C%TTPOINT B934 42.605237 -71.34456 +%C% +%C%TTPOINT B901 42.661279 -71.364452 +%C%TTPOINT B902 42.660411 -71.364419 +%C%TTPOINT B903 42.659046 -71.364452 +%C%TTPOINT B904 42.657578 -71.364602 +%C% +%C% +%C%# For location at given bearing and distance from starting point. +%C% +%C%TTVECTOR B5bbbddd 37^55.37N 81^7.86W 0.01 mi +%C% +%C%# For location specified by x, y coordinates. +%C% +%C%TTGRID Byyyxxx 37^50.00N 81^00.00W 37^59.99N 81^09.99W +%C% +%C%# UTM location for Lowell-Dracut-Tyngsborough State Forest. +%C% +%C%TTUTM B6xxxyyy 19T 10 300000 4720000 +%C% +%C% +%C% +%C%# Location for the corral. +%C% +%C%TTCORRAL 37^55.50N 81^7.00W 0^0.02N +%C% +%C%# Compact messages - Fixed locations xx and object yyy where +%C%# Object numbers 100 - 199 = bicycle +%C%# Object numbers 200 - 299 = fire truck +%C%# Others = dog +%C% +%C%TTMACRO xx1yy B9xx*AB166*AA2B4C5B3B0A1yy +%C%TTMACRO xx2yy B9xx*AB170*AA3C4C7C3B0A2yy +%C%TTMACRO xxyyy B9xx*AB180*AA3A6C4A0Ayyy +%C% +%C%TTMACRO z Cz +%C% +%C%# Receive on channel 0, Transmit object reports on channel 1 with optional via path. +%C%# You probably want to put in a transmit delay on the APRStt channel so it +%C%# it doesn't start sending a response before the user releases PTT. +%C%# This is in 10 ms units so 100 means 1000 ms = 1 second. +%C% +%C%#TTOBJ 0 1 WIDE1-1 +%C%#CHANNEL 0 +%C%#DWAIT 100 +%C% +%C%# Advertise gateway position with beacon. +%C% +%C%# OBEACON DELAY=0:15 EVERY=10:00 VIA=WIDE1-1 OBJNAME=WB2OSZ-tt SYMBOL=APRStt LAT=42^37.14N LONG=71^20.83W COMMENT="APRStt Gateway" +%C% +%C% +%C%# Sample speech responses. +%C%# Default is Morse code "R" for received OK and "?" for all errors. +%C% +%C%#TTERR OK SPEECH Message Received. +%C%#TTERR D_MSG SPEECH D not implemented. +%C%#TTERR INTERNAL SPEECH Internal error. +%C%#TTERR MACRO_NOMATCH SPEECH No definition for digit sequence. +%C%#TTERR BAD_CHECKSUM SPEECH Bad checksum on call. +%C%#TTERR INVALID_CALL SPEECH Invalid callsign. +%C%#TTERR INVALID_OBJNAME SPEECH Invalid object name. +%C%#TTERR INVALID_SYMBOL SPEECH Invalid symbol. +%C%#TTERR INVALID_LOC SPEECH Invalid location. +%C%#TTERR NO_CALL SPEECH No call or object name. +%C%#TTERR SATSQ SPEECH Satellite square must be 4 digits. +%C%#TTERR SUFFIX_NO_CALL SPEECH Send full call before using suffix. +%C% \ No newline at end of file diff --git a/conf/install_conf.cmake b/conf/install_conf.cmake new file mode 100644 index 0000000..af111a7 --- /dev/null +++ b/conf/install_conf.cmake @@ -0,0 +1,23 @@ +if(NOT EXISTS $ENV{HOME}/direwolf.conf) + configure_file("${CUSTOM_BINARY_DIR}/direwolf.conf" $ENV{HOME}) +endif() + +if(NOT EXISTS $ENV{HOME}/sdr.conf) + configure_file("${CUSTOM_CONF_DIR}/sdr.conf" $ENV{HOME}) +endif() + +if(NOT EXISTS $ENV{HOME}/dw-start.sh) + configure_file("${CUSTOM_SCRIPTS_DIR}/dw-start.sh" $ENV{HOME}) +endif() + +if(NOT EXISTS $ENV{HOME}/telem-m0xer-3.txt) + configure_file("${CUSTOM_TELEMETRY_DIR}/telem-m0xer-3.txt" $ENV{HOME}) +endif() + +if(NOT EXISTS $ENV{HOME}/telem-balloon.conf) + configure_file("${CUSTOM_TELEMETRY_DIR}/telem-balloon.conf" $ENV{HOME}) +endif() + +if(NOT EXISTS $ENV{HOME}/telem-volts.conf) + configure_file("${CUSTOM_TELEMETRY_DIR}/telem-volts.conf" $ENV{HOME}) +endif() diff --git a/sdr.conf b/conf/sdr.conf similarity index 100% rename from sdr.conf rename to conf/sdr.conf diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt new file mode 100644 index 0000000..9f7c40e --- /dev/null +++ b/data/CMakeLists.txt @@ -0,0 +1,94 @@ +# +# The destination field is often used to identify the manufacturer/model. +# These are not hardcoded into Dire Wolf. Instead they are read from +# a file called tocalls.txt at application start up time. +# +# The original permanent symbols are built in but the "new" symbols, +# using overlays, are often updated. These are also read from files. +# +# You can obtain an updated copy by typing "make data-update". +# This is not part of the normal build process. You have to do this explicitly. +# +# The locations below appear to be the most recent. +# The copy at http://www.aprs.org/tocalls.txt is out of date. +# + +include(ExternalProject) + +set(TOCALLS_TXT "tocalls.txt") +set(TOCALLS_TXT_BKP "tocalls.txt.old") +set(TOCALLS_URL "http://www.aprs.org/aprs11/tocalls.txt") +set(SYMBOLS-NEW_TXT "symbols-new.txt") +set(SYMBOLS-NEW_TXT_BKP "symbols-new.txt.old") +set(SYMBOLS-NEW_URL "http://www.aprs.org/symbols/symbols-new.txt") +set(SYMBOLSX_TXT "symbolsX.txt") +set(SYMBOLSX_TXT_BKP "symbolsX.txt.old") +set(SYMBOLSX_URL "http://www.aprs.org/symbols/symbolsX.txt") +set(CUSTOM_BINARY_DATA_DIR "${CMAKE_BINARY_DIR}/data") + +# we can also move to a separate cmake file and use file(download) +# see conf/install_conf.cmake as example +file(COPY "${CUSTOM_DATA_DIR}/${TOCALLS_TXT}" DESTINATION "${CUSTOM_BINARY_DATA_DIR}") +file(COPY "${CUSTOM_DATA_DIR}/${SYMBOLS-NEW_TXT}" DESTINATION "${CUSTOM_BINARY_DATA_DIR}") +file(COPY "${CUSTOM_DATA_DIR}/${SYMBOLSX_TXT}" DESTINATION "${CUSTOM_BINARY_DATA_DIR}") + +add_custom_target(data_rename + COMMAND ${CMAKE_COMMAND} -E rename "${CUSTOM_BINARY_DATA_DIR}/${TOCALLS_TXT}" "${CUSTOM_BINARY_DATA_DIR}/${TOCALLS_TXT_BKP}" + COMMAND ${CMAKE_COMMAND} -E rename "${CUSTOM_BINARY_DATA_DIR}/${SYMBOLS-NEW_TXT}" "${CUSTOM_BINARY_DATA_DIR}/${SYMBOLS-NEW_TXT_BKP}" + COMMAND ${CMAKE_COMMAND} -E rename "${CUSTOM_BINARY_DATA_DIR}/${SYMBOLSX_TXT}" "${CUSTOM_BINARY_DATA_DIR}/${SYMBOLSX_TXT_BKP}" + ) + +ExternalProject_Add(download_tocalls + DEPENDS data_rename + URL ${TOCALLS_URL} + PREFIX "" + DOWNLOAD_DIR "${CUSTOM_BINARY_DATA_DIR}" + DOWNLOAD_NAME "${TOCALLS_TXT}" + DOWNLOAD_NO_EXTRACT 0 + EXCLUDE_FROM_ALL 1 + UPDATE_COMMAND "" + PATCH_COMMAND "" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" + ) + +ExternalProject_Add(download_symbols-new + DEPENDS data_rename + URL ${SYMBOLS-NEW_URL} + PREFIX "" + DOWNLOAD_DIR "${CUSTOM_BINARY_DATA_DIR}" + DOWNLOAD_NAME "${SYMBOLS-NEW_TXT}" + DOWNLOAD_NO_EXTRACT 0 + EXCLUDE_FROM_ALL 1 + UPDATE_COMMAND "" + PATCH_COMMAND "" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" + ) + +ExternalProject_Add(download_symbolsx + DEPENDS data_rename + URL ${SYMBOLSX_URL} + PREFIX "" + DOWNLOAD_DIR "${CUSTOM_BINARY_DATA_DIR}" + DOWNLOAD_NAME "${SYMBOLSX_TXT}" + DOWNLOAD_NO_EXTRACT 0 + EXCLUDE_FROM_ALL 1 + UPDATE_COMMAND "" + PATCH_COMMAND "" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" + ) + +add_custom_target(update-data) +add_dependencies(update-data data_rename download_tocalls download_symbols-new download_symbolsx) + +install(FILES "${CUSTOM_BINARY_DATA_DIR}/${TOCALLS_TXT}" DESTINATION ${INSTALL_DATA_DIR}) +install(FILES "${CUSTOM_BINARY_DATA_DIR}/${SYMBOLS-NEW_TXT}" DESTINATION ${INSTALL_DATA_DIR}) +install(FILES "${CUSTOM_BINARY_DATA_DIR}/${SYMBOLSX_TXT}" DESTINATION ${INSTALL_DATA_DIR}) diff --git a/symbols-new.txt b/data/symbols-new.txt similarity index 92% rename from symbols-new.txt rename to data/symbols-new.txt index 44e2cba..44dcb6b 100644 --- a/symbols-new.txt +++ b/data/symbols-new.txt @@ -1,11 +1,15 @@ -APRS SYMBOL OVERLAY and EXTENSION TABLES in APRS 1.2 3 Apr 2017 ---------------------------------------------------------------------- +APRS SYMBOL OVERLAY and EXTENSION TABLES in APRS 1.2 17 Jun 2018 +------------------------------------------------------------------------ -BACKGROUND: Since 1 October 2007, overlay characters (36 per symbol) -are allowed on all symbols. Since the master symbol document, -http://aprs.org/symbols/symbolsX.txt page only has one line per symbol -character this overlay list gives us thousands of new symbol codes. +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). +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 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. @@ -54,8 +58,8 @@ of unique overlay definitions: \u - Overlay Trucks. "Tu" is a tanker. "Gu" is a gas truck, etc \< - Advisories may now have overlays \8 - Nodes with overlays. "G8" would be 802.11G -\[ - \[ is wall cloud, but overlays are humans. S[ is a skier. -\h - Buildings. \h is a Ham store, "Hh" is Home Depot, etc. +\[ - \[ was wall cloud, but overlays are humans. S[ is a skier. +\h - Was Ham store, Now Overlays are buildings "Hh" Home Depot, etc. 4 Oct 2007. ORIGINAL EXPANSION to OVERLAYS ON ALL SYMBOLS @@ -94,8 +98,9 @@ small subset of alternate symbols. Those original overlayable alternate symbols were labeled with a "#" and called "numbered" symbols. (UIview requires "No." in the symbols.ini file) -STATUS OF OVERLAYS 1 OCTOBER 2007: the APRS symbol set only had a -few remaining unused symbol codes that had not yet been defined: +STATUS OF OVERLAYS 1 OCTOBER 2007: the APRS symbol set was limited +to about 94 symbols and only had a few remaining unused symbol +codes that had not yet been defined: OF THE 94 Primary Symbols. The following were available: 10 symbols (/0 - /9) that mostly look like billiard balls now @@ -107,7 +112,7 @@ OF THE 94 Alternate Symbols. The following were available: 8 series \1 through \8 that can support 36 overlays each 3 reserved series. -ADDITIONAL OVERLAY PROPOSAL: But any of the other 79 alternate +BASIS FOR OVERLAY EXPANSION: But any of the other 79 alternate symbols could all have multiple (36) overlays if they can make sense with the existing underlying basic symbol that we have been using for that basic alternate symbol. That is, any new definition of a @@ -128,7 +133,7 @@ letting that define a new graphic just for that combination. The following tables will attempt to keep track of these and any other useful generic applications of overlay characters. -AMPLIFIED some existing ALTERNATE SYMBOL Overlays: (new Aug 2014) +AMPLIFIED some existing ALTERNATE SYMBOL Overlays: (Aug 2014) change Flooding #W to include Avalanche, Mudslide/Landslide Update #' name to crash & incident sites Update \D (was available) to DEPOT family @@ -178,6 +183,7 @@ MO = Manned Balloon (2015) TO = Teathered (2015) CO = Constant Pressure - Long duration (2015) RO = Rocket bearing Balloon (Rockoon) (2015) +WO = World-round balloon (2018) BOX SYMBOL: #A (and other system inputted symbols) /A = Aid station @@ -197,8 +203,9 @@ BUILDINGS: #h /h = Hospital \h = Ham Store ** <= now used for HAMFESTS Ch = Club (ham radio) +Eh = Electronics Store Fh = HamFest (new Aug 2014) -Hh = Home Depot etc.. +Hh = Hardware Store etc.. CARS: #> (Vehicles) /> = normal car (side view) @@ -345,6 +352,26 @@ S% = Solar T% = Turbine W% = Wind +RAIL Symbols: #= +/= = generic train (use steam engine shape for quick recognition) +\= = tbd (use same symbol for now) +B= = Bus-rail/trolley/streetcar/guiderail +C= = Commuter +D= = Diesel +E= = Electric +F= = Freight +G= = Gondola +H= = High Speed Rail (& Hyperloop?) +I= = Inclined Rail +L= = eLevated +M= = Monorail +P= = Passenger +S= = Steam +T= = Terminal (station) +U= = sUbway (& Hyperloop?) +X= = eXcursion + + RESTAURANTS: #R \R = Restaurant (generic) 7R = 7/11 diff --git a/symbolsX.txt b/data/symbolsX.txt similarity index 100% rename from symbolsX.txt rename to data/symbolsX.txt diff --git a/tocalls.txt b/data/tocalls.txt similarity index 69% rename from tocalls.txt rename to data/tocalls.txt index 64768c8..47ba2e3 100644 --- a/tocalls.txt +++ b/data/tocalls.txt @@ -1,34 +1,48 @@ -APRS TO-CALL VERSION NUMBERS 12 Dec 2017 + +APRS TO-CALL VERSION NUMBERS 13 Oct 2020 ------------------------------------------------------------------- WB4APR -12 Dec 17 Added APHWxx for use in "HamWAN -11 Dec 17 Added APDVxx for OE6PLD's SSTV with APRS status exchange -20 Nov 17 added APPICO DB1NTO' PicoAPRS -18 Oct 17 Added APBMxx BrandMeister DMR Server for R3ABM -25 Sep 17 added APP6xx for APRSlib -05 Sep 17 Chged APTAxx to APTBxx for TinyAPRS by BG5HHP -17 Aug 17 Added APOCSG for POCSAG - N0AGI's APRS to Pagers -21 Jun 17 Added APCSMS for Cosmos (used for sending commands @USNA -08 Jun 17 Added APPMxx for DL1MX's RTL-SDR pytohon Igate -01 Jun 17 added APOFF digi off on PSAT,PSAT2 - and APDTMF digi off mode on QIKCOM2 and DTMF ON - and APRSON digi ON for PSAT - and APDIGI digi ON for PSAT2 and QIKCOM-2 - and APSAT digi ON for QIKCOM-1 -20 Mar 17 added APTBxx for TinyAPRS by BG5HHP -06 Feb 17 added APIExx for W7KMV's PiAPRS system -25 Jan 17 added APSFxx F5OPV embedded devices - was APZ40 -16 Dec 16 added APYSxx for W2GMD's Python APRS -14 Nov 16 Added APINxx for PinPoint by AB0WV -09 Nov 16 added APNICx for SQ5EKU http://sq5eku.blogspot.com/ -24 Oct 16 added APTKPT TrackPoint N0LP, removed APZTKP -24 Aug 16 added APK004 for Kenwood THD-74 -29 Apr 16 added APFPRS for FreeDV by Jeroen PE1RXQ -25 Feb 16 Added APCDS0 for Leon Lessing ZS6LMG's cell tracker -21 Jan 16 added APDNOx for APRSduino by DO3SWW -In 2015 Added APSTPO,APAND1,APDRxx,APZ247,APHTxx,APMTxx,APZMAJ + + + +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 +2017 Added APHWxx,APDVxx,APPICO,APBMxx,APP6xx,APTAxx,APOCSG,APCSMS, + APPMxx,APOFF,APDTMF,APRSON,APDIGI,APSAT,APTBxx,APIExx, + APSFxx +2016 added APYSxx,APINxx,APNICx,APTKPT,APK004,APFPRS,APCDS0,APDNOx +2015 Added APSTPO,APAND1,APDRxx,APZ247,APHTxx,APMTxx,APZMAJ APB2MF,APR2MF,APAVT5 + + + In APRS, the AX.25 Destination address is not used for packet routing as is normally done in AX.25. So APRS uses it for two things. The initial APxxxx is used as a group identifier to make @@ -38,6 +52,9 @@ bytes of the field are available to indicate the software version number or application. The following applications have requested a TOCALL number series: + + + APn 3rd digit is a number AP1WWX TAPR T-238+ WX station AP1MAJ Martyn M1MAJ DeLorme inReach Tracker @@ -52,20 +69,26 @@ 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 + APAT51 for Anytone AT-D578UV APRS mobile radio APAVT5 SainSonic AP510 which is a 1watt tracker APAWxx AGWPE APB APBxxx Beacons or Rabbit TCPIP micros? APB2MF DL2MF - MF2APRS Radiosonde for balloons APBLxx BigRedBee BeeLine APBLO MOdel Rocketry K7RKT + APBKxx PY5BK Bravo Tracker in Brazil APBPQx John G8BPQ Digipeater/IGate - APBMxx BrandMeister DMR Server for R3ABM + APBMxx BrandMeister DMR Server for R3ABM + APBSDx HamBSD https://hambsd.org/ + APBT62 BTech DMR 6x2 APC APCxxx Cellular applications APCBBx VE7UDP Blackberry Applications APCDS0 Leon Lessing ZS6LMG's cell tracker APCLEY EYTraker GPRS/GSM tracker by ZS6EY - APCLWX EYWeather GPRS/GSM WX station by ZS6EY APCLEZ Telit EZ10 GSM application ZS6CEY + APCLUB Brazil APRS network + APCLWX EYWeather GPRS/GSM WX station by ZS6EY APCSMS for Cosmos (used for sending commands @USNA) APCWP8 John GM7HHB, WinphoneAPRS APCYxx Cybiko applications @@ -90,13 +113,19 @@ a TOCALL number series: APDWxx DireWolf, WB2OSZ APE APExxx Telemetry devices APECAN Pecan Pico APRS Balloon Tracker + APELKx WB8ELK balloons APERXQ Experimental tracker by PE1RXQ + APERSx Runner tracking by Jason,KG7YKZ + APESPG ESP SmartBeacon APRS-IS Client + APESPW ESP Weather Station APRS-IS Client APF APFxxx Firenet APFGxx Flood Gage (KP4DJT) APFIxx for APRS.FI OH7LZB, Hessu APFPRS for FreeDV by Jeroen PE1RXQ APG APGxxx Gates, etc APGOxx for AA3NJ PDA application + APGBLN for NW5W's GoBalloon + APGDTx for VK4FAST's Graphic Data Terminal APH APHKxx for LA1BR tracker/digipeater APHAXn SM2APRS by PY2UEP APHTxx HMTracker by IU0AAC @@ -105,7 +134,9 @@ a TOCALL number series: APICxx HA9MCQ's Pic IGate APIExx W7KMV's PiAPRS system APINxx PinPoint by AB0WV - APJ APJAxx JavAPRS + APIZCI hymTR IZCI Tracker by TA7W/OH2UDS and TA6AEU + APJ APJ8xx Jordan / KN4CRD JS8Call application + APJAxx JavAPRS APJExx JeAPRS APJIxx jAPRSIgate APJSxx javAPRSSrvr @@ -116,11 +147,15 @@ a TOCALL number series: APK1xx Kenwood D700's APK102 Kenwood D710 APKRAM KRAMstuff.com - Mark. G7LEU - APL APLQRU Charlie - QRU Server + APL APLGxx LoRa Gateway/Digipeater OE5BPA + APLIGx LightAPRS - TA2MUN and TA9OHC + APLQRU Charlie - QRU Server APLMxx WA0TQG transceiver controller + APLTxx LoRa Tracker - OE5BPA APM APMxxx MacAPRS, - APMGxx MiniGate - Alex, AB0TJ + APMGxx PiCrumbs and MiniGate - Alex, AB0TJ APMIxx SQ3PLX http://microsat.com.pl/ + APMQxx Ham Radio of Things WB2OSZ APMTxx LZ1PPL for tracker APN APNxxx Network nodes, digis, etc APN3xx Kantronics KPC-3 rom versions @@ -131,10 +166,12 @@ a TOCALL number series: APNK01 Kenwood D700 (APK101) type APNK80 KAM version 8.0 APNKMP KAM+ + APNKMX KAM-XL APNMxx MJF TNC roms APNPxx Paccom TNC roms APNTxx SV2AGW's TNT tnc as a digi APNUxx UIdigi + APNVxx SQ8L's VP digi and Nodes APNXxx TNC-X (K6DBG) APNWxx SQ3FYK.com WX/Digi and SQ3PLX http://microsat.com.pl/ APO APRSpoint @@ -142,8 +179,10 @@ a TOCALL number series: APOLUx for OSCAR satellites for AMSAT-LU by LU9DO APOAxx OpenAPRS - Greg Carter APOCSG For N0AGI's APRS to POCSAG project - APOTxx Open Track APOD1w Open Track with 1 wire WX + APOSBx openSPOT3 by HA2NON at sharkrf.com + APOSWx openSPOT2 + APOTxx Open Track APOU2k Open Track for Ultimeter APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO APP APP6xx for APRSlib @@ -151,8 +190,10 @@ a TOCALL number series: APPMxx DL1MX's RTL-SDR pytohon Igate APPTxx KetaiTracker by JF6LZE, Takeki (msg capable) APQ APQxxx Earthquake data + APQTHx W8WJB's QTH.app APR APR8xx APRSdos versions 800+ APR2MF DL2MF - MF2APRS Radiosonde WX reporting + APRARX VK5QI's radiosonde tracking APRDxx APRSdata, APRSdr APRGxx aprsg igate software, OH2GVE APRHH2 HamHud 2 @@ -180,8 +221,11 @@ a TOCALL number series: APT3xx Tiny Track III APTAxx K4ATM's tiny track APTBxx TinyAPRS by BG5HHP Was APTAxx till Sep 2017 + APTCHE PU3IKE in Brazil TcheTracker/Tcheduino + APTCMA CAPI tracker - PU1CMA Brazil APTIGR TigerTrack APTKPT TrackPoint N0LP + APTPNx TARPN Packet Node Tracker by KN4ORB http://tarpn.net/ APTTxx Tiny Track APTWxx Byons WXTrac APTVxx for ATV/APRN and SSTV applications @@ -200,11 +244,15 @@ a TOCALL number series: APWWxx APRSISCE win32 version APX APXnnn Xastir APXRnn Xrouter - APY APYxxx Yeasu + APY APYxxx Yaesu Radios APY008 Yaesu VX-8 series - APY350 Yaesu FTM-350 series - APYTxx for YagTracker - APYSxx for W2GMD's Python APRS + APY01D Yaesu FT1D series + APY02D Yaesu FT2D series + APY03D Yaesu FT3D series + APY100 Yaesu FTM-100D series + APY300 Yaesu FTM-300D series + APY350 Yaesu FTM-350 series + APY400 Yaesu FTM-400D series APZ APZxxx Experimental APZ247 for UPRS NR0Q APZ0xx Xastir (old versions. See APX) @@ -214,11 +262,15 @@ a TOCALL number series: APZTKP TrackPoint, Nick N0LP (Balloon tracking)(depricated) APZWIT MAP27 radio (Mountain Rescue) EI7IG APZWKR GM1WKR NetSked application + + 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: ------------------- @@ -244,9 +296,11 @@ The following is a list of ALTNETS that may be of interest to other users. This list is by no means complete, since ANY combination of characters other than APxxxx are considered an ALTNET. But this list can give consisntecy to ALTNETS that may be using the global APRS-IS -and need some special recognition: +and need some special recognition. Here are some ideas: + + - TEST - A generic ALTNET for use during testing - PSKAPR - PSKmail . But it is not AX.25 anyway - -de WB4APR, Bob + SATERN - Salvation Army Altnet + AFMARS - Airforce Mars + AMARS - Army Mars + \ No newline at end of file diff --git a/debian/README.Debian b/debian/README.Debian new file mode 100644 index 0000000..853f55f --- /dev/null +++ b/debian/README.Debian @@ -0,0 +1,5 @@ +In order to start direwolf as a service the configuration file +/etc/direwolf.conf needs to exist. Otherwise attempting to start the service +returns an 'Assertion failed' error. An example configuration file which may be +used as a model can be found in +/usr/share/doc/direwolf/examples/direwolf.conf.gz diff --git a/debian/changelog b/debian/changelog new file mode 120000 index 0000000..cf54708 --- /dev/null +++ b/debian/changelog @@ -0,0 +1 @@ +../CHANGES.md \ No newline at end of file diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..9a03714 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +10 \ No newline at end of file diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..106663b --- /dev/null +++ b/debian/control @@ -0,0 +1,30 @@ +Source: direwolf +Maintainer: Debian Hamradio Maintainers +Uploaders: Iain R. Learmonth +Section: hamradio +Priority: optional +Build-Depends: debhelper (>= 9), + libasound2-dev, + libgps-dev, + libhamlib-dev, + dh-systemd +Standards-Version: 4.1.0 +Vcs-Browser: https://anonscm.debian.org/cgit/pkg-hamradio/direwolf.git/ +Vcs-Git: https://anonscm.debian.org/git/pkg-hamradio/direwolf.git +Homepage: https://github.com/wb2osz/direwolf + +Package: direwolf +Architecture: alpha amd64 arm64 armel armhf i386 mipsel ppc64el sh4 x32 +Depends: ${shlibs:Depends}, + ${misc:Depends}, + adduser, + libhamlib2 +Suggests: gpsd, libhamlib-utils +Breaks: direwolf-docs (<< 1.1-1) +Replaces: direwolf-docs (<< 1.1-1) +Description: Soundcard TNC for APRS + Dire Wolf is a software "soundcard" modem/TNC and APRS encoder/decoder. It can + be used stand-alone to receive APRS messages, as a digipeater, APRStt gateway, + or Internet Gateway (IGate). It can also be used as a virtual TNC for other + applications such as APRSIS32, UI-View32, Xastir, APRS-TW, YAAC, UISS, Linux + AX25, SARTrack, and many others. \ No newline at end of file diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..b546bf7 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,176 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: direwolf +Files-Excluded: doc/*.pdf +Source: https://github.com/wb2osz/direwolf +Comment: + The files in misc/ are copied directly from the Cygwin source code. These are + listed here as dual licensed as they are both part of the Cygwin distribution + and originally part of BSD. See misc/README-dire-wolf.txt for more information. + . + Please see ftp-master's comments on this here: + https://lists.debian.org/debian-hams/2014/09/msg00063.html + https://lists.debian.org/debian-hams/2014/10/msg00003.html + +Files: * +Copyright: (C) 2011-2014 John Langner WB2OSZ +License: GPL-2+ + +Files: geotranz/* +Copyright: National Geospatial-Intelligence Agency +License: Permissive-NGA + +Files: regex/* +Copyright: (C) 2002, 2003, 2005 Free Software Foundation, Inc. +License: LGPL-2.1+ + +Files: misc/strcasestr.c +Copyright: + (C) 1990, 1993 The Regents of the University of California + (C) RedHat +License: BSD-4-clause or GPL-2+ + +Files: misc/strtok_r.c misc/strsep.c +Copyright: + (C) 1988 Regents of the University of California + (C) RedHat +License: BSD-3-clause or GPL-2+ + +Files: debian/* +Copyright: (C) 2014 Iain R. Learmonth +License: GPL-2+ + +License: BSD-3-clause + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + . + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + . + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + . + 3. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + . + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + +License: BSD-4-clause + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + . + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + . + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + . + 3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + This product includes software developed by the University of + California, Berkeley and its contributors. + . + 4. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + . + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + +License: GPL-2+ + 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 . + . + On Debian systems, a copy of the full license text is available in + /usr/share/common-licenses/GPL-2. + +License: LGPL-2.1+ + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + . + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + . + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + . + On Debian systems, a copy of the full license text is available in + /usr/share/common-licenses/LGPL-2.1. + +License: Permissive-NGA + 1. The GEOTRANS source code ("the software") is provided free of charge by the + National Geospatial-Intelligence Agency (NGA) of the United States Department + of Defense. Although NGA makes no copyright claim under Title 17 U.S.C., NGA + claims copyrights in the source code under other legal regimes. NGA hereby + grants to each user of the software a license to use and distribute the + software, and develop derivative works. + . + 2. NGA requests that products developed using the software credit the source of + the software with the following statement, "The product was developed using + GEOTRANS, a product of the National Geospatial-Intelligence Agency (NGA) and + U.S. Army Engineering Research and Development Center." Do not use the name + GEOTRANS for any derived work. + . + 3. Warranty Disclaimer: The software was developed to meet only the internal + requirements of the National Geospatial-Intelligence Agency (NGA). The software + is provided "as is," and no warranty, express or implied, including but not + limited to the implied warranties of merchantability and fitness for particular + purpose or arising by statute or otherwise in law or from a course of dealing + or usage in trade, is made by NGA as to the accuracy and functioning of the + software. + . + 4. NGA and its personnel are not required to provide technical support or + general assistance with respect to public use of the software. Government + customers may contact NGA. + . + 5. Neither NGA nor its personnel will be liable for any claims, losses, or + damages arising from or connected with the use of the software. The user agrees + to hold harmless the United States National Geospatial-Intelligence Agency + (NGA). The user's sole and exclusive remedy is to stop using the software. + . + 6. Please be advised that pursuant to the United States Code, 10 U.S.C. 425, + the name of the National Geospatial-Intelligence Agency, the initials "NGA", + the seal of the National Geospatial-Intelligence Agency, or any colorable + imitation thereof shall not be used to imply approval, endorsement, or + authorization of a product without prior written permission from United States + Secretary of Defense. Do not create the impression that NGA, the Secretary of + Defense or the Director of National Intelligence has endorsed any product + derived from GEOTRANS. \ No newline at end of file diff --git a/debian/direwolf.postinst b/debian/direwolf.postinst new file mode 100644 index 0000000..e42b9f8 --- /dev/null +++ b/debian/direwolf.postinst @@ -0,0 +1,33 @@ +#!/bin/sh + +set -e + +. /usr/share/debconf/confmodule + +add_group_if_missing() { + if ! getent group direwolf >/dev/null; then + addgroup --system --force-badname direwolf || true + fi +} + +add_user_if_missing() { + if ! id -u direwolf > /dev/null 2>&1; then + mkdir -m 02750 -p /var/lib/direwolf + adduser --system --home /var/lib/direwolf \ + --disabled-password \ + --force-badname direwolf \ + --ingroup direwolf + adduser direwolf dialout + chown direwolf:direwolf /var/lib/direwolf + fi +} + +add_group_if_missing +add_user_if_missing + +db_stop + +#DEBHELPER# + +exit 0 + diff --git a/debian/direwolf.postrm b/debian/direwolf.postrm new file mode 100644 index 0000000..886af3d --- /dev/null +++ b/debian/direwolf.postrm @@ -0,0 +1,19 @@ +#!/bin/sh + +set -e + +case "$1" in + purge) + rm -rf /var/lib/direwolf/ + ;; + remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + ;; + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 +esac + +#DEBHELPER# + +exit 0 + diff --git a/debian/rules b/debian/rules new file mode 100644 index 0000000..b8c2222 --- /dev/null +++ b/debian/rules @@ -0,0 +1,7 @@ +#!/usr/bin/make -f + +%: + dh $@ --parallel + +override_dh_auto_configure: + dh_auto_configure -- -DFORCE_SSE=1 diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..46ebe02 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) \ No newline at end of file diff --git a/demod_psk.h b/demod_psk.h deleted file mode 100644 index 0f5830d..0000000 --- a/demod_psk.h +++ /dev/null @@ -1,7 +0,0 @@ - -/* demod_psk.h */ - - -void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char profile, struct demodulator_state_s *D); - -void demod_psk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D); diff --git a/direwolf.spec b/direwolf.spec deleted file mode 100644 index 4ac7d46..0000000 --- a/direwolf.spec +++ /dev/null @@ -1,103 +0,0 @@ -#%global git_commit b2548ec58f44f4b651626757a166b9f4f18d8000 -%global git_commit 37179479caf0bf36adf8c9bc0fde641884edaeac -%global git_date 20171216 - -%global git_short_commit %(echo %{git_commit} | cut -c -8) -%global git_suffix %{git_date}git%{git_short_commit} - -Name: direwolf -Version: 1.5Beta -Release: 1.%{git_suffix}%{?dist} -Summary: Soundcard based AX.25 TNC - -Group: Applications/Communications -License: GPLv2 -URL: https://github.com/wb2osz/direwolf -#Source0: https://github.com/wb2osz/direwolf/archive/%{name}-%{version}.tar.gz -Source: %{name}-%{version}-%{git_suffix}.tgz -Packager: David Ranch (KI6ZHD) -Distribution: RedHat Linux - -Patch0: direwolf-1.5-makefile.patch - -BuildRequires: automake -BuildRequires: alsa-lib-devel - -#If the gpsd and gpsd-devel packages are installed, Direwolf will add gps support - - - -%description -Dire Wolf is a software "soundcard" modem/TNC and APRS encoder/decoder. It can -be used stand-alone to receive APRS messages, as a digipeater, APRStt gateway, -or Internet Gateway (IGate). It can also be used as a virtual TNC for other -applications such as APRSIS32, UI-View32, Xastir, APRS-TW, YAAC, UISS, -Linux AX25, SARTrack, RMS Express, and many others. - -%prep - -%setup -q -n %{name}-%{version} -%patch0 -p0 - -%build - -make -f Makefile.linux -#make -f Makefile.linux tocalls-symbols -make %{?_smp_mflags} - - -%install -make install INSTALLDIR=$RPM_BUILD_ROOT/usr -make install-conf INSTALLDIR=$RPM_BUILD_ROOT/usr - -# Install icon -mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/pixmaps/direwolf/ -cp dw-icon.png ${RPM_BUILD_ROOT}%{_datadir}/pixmaps/direwolf/ -mv symbols-new.txt ${RPM_BUILD_ROOT}%{_docdir}/%{name}/ -mv symbolsX.txt ${RPM_BUILD_ROOT}%{_docdir}/%{name}/ -mv tocalls.txt ${RPM_BUILD_ROOT}%{_docdir}/%{name}/ -desktop-file-install \ - --dir=${RPM_BUILD_ROOT}%{_datadir}/applications direwolf.desktop -#temp bug -#non echo "fixing $RPM_BUILD_ROOT/%{_bindir}/bin" -#non rm -f $RPM_BUILD_ROOT/usr/bin - - -%files -%{_sysconfdir}/ax25/direwolf.conf -%{_sysconfdir}/ax25/sdr.conf -%{_sysconfdir}/ax25/telemetry-toolkit/telem-balloon.conf -%{_sysconfdir}/ax25/telemetry-toolkit/telem-m0xer-3.txt -%{_sysconfdir}/ax25/telemetry-toolkit/telem-volts.conf -%{_sysconfdir}/udev/rules.d/99-direwolf-cmedia.rules -%{_bindir}/* -%{_datadir}/pixmaps/direwolf/dw-icon.png -%{_datadir}/applications/%{name}.desktop -%{_datadir}/direwolf/* -%{_docdir}/* -%{_mandir}/man1/* - - - -%changelog -* Sat Dec 16 2017 David Ranch - 1.5-1 -- New 1.5-Beta version from Git -* Sun Apr 2 2017 David Ranch - 1.4-1 -- New 1.4-Beta1 version from Git -* Sun Mar 5 2017 David Ranch - 1.4-1 -- New 1.4-H Alpha version from Git version -* Fri Aug 26 2016 David Ranch - 1.4-1 -- New version -* Fri May 06 2016 David Ranch - 1.3-1 -- New version -* Sat Sep 12 2015 David Ranch - 1.3F-1 -- New version with new features -* Sun May 10 2015 David Ranch - 1.2E-1 -- New version that supports a PASSALL function -- Updated the Makefile.linux patch -* Sat Mar 21 2015 David Ranch - 1.2C-1 -- changed to support different make installation variable -* Sat Feb 14 2015 David Ranch - 1.2b-1 -- new spec file -* Sat Dec 20 2014 David Ranch - 1.1b1-1 -- new spec file diff --git a/direwolf.txt b/direwolf.txt deleted file mode 100644 index a7cfadf..0000000 --- a/direwolf.txt +++ /dev/null @@ -1,536 +0,0 @@ -C############################################################# -C# # -C# Configuration file for Dire Wolf # -C# # -L# Linux version # -W# Windows version # -C# # -C############################################################# -R -R -R The sample config file was getting pretty messy -R with the Windows and Linux differences. -R It would be a maintenance burden to keep most of -R two different versions in sync. -R This common source is now used to generate the -R two different variations while having only a single -R copy of the common parts. -R -R The first column contains one of the following: -R -R R remark which is discarded. -R C common to both versions. -R W Windows version only. -R L Linux version only. -R -C# -C# Consult the User Guide for more details on configuration options. -C# -C# -C# These are the most likely settings you might change: -C# -C# (1) MYCALL - call sign and SSID for your station. -C# -C# Look for lines starting with MYCALL and -C# change NOCALL to your own. -C# -C# (2) PBEACON - enable position beaconing. -C# -C# Look for lines starting with PBEACON and -C# modify for your call, location, etc. -C# -C# (3) DIGIPEATER - configure digipeating rules. -C# -C# Look for lines starting with DIGIPEATER. -C# Most people will probably use the given example. -C# Just remove the "#" from the start of the line -C# to enable it. -C# -C# (4) IGSERVER, IGLOGIN - IGate server and login -C# -C# Configure an IGate client to relay messages between -C# radio and internet servers. -C# -C# -C# The default location is "direwolf.conf" in the current working directory. -L# On Linux, the user's home directory will also be searched. -C# An alternate configuration file location can be specified with the "-c" command line option. -C# -C# As you probably guessed by now, # indicates a comment line. -C# -C# Remove the # at the beginning of a line if you want to use a sample -C# configuration that is currently commented out. -C# -C# Commands are a keyword followed by parameters. -C# -C# Command key words are case insensitive. i.e. upper and lower case are equivalent. -C# -C# Command parameters are generally case sensitive. i.e. upper and lower case are different. -C# -C -C -C############################################################# -C# # -C# FIRST AUDIO DEVICE PROPERTIES # -C# (Channel 0 + 1 if in stereo) # -C# # -C############################################################# -C -C# -C# Many people will simply use the default sound device. -C# Some might want to use an alternative device by chosing it here. -C# -W# When the Windows version starts up, it displays something like -W# this with the available sound devices and capabilities: -W# -W# Available audio input devices for receive (*=selected): -W# * 0: Microphone (C-Media USB Headpho (channel 2) -W# 1: Microphone (Bluetooth SCO Audio -W# 2: Microphone (Bluetooth AV Audio) -W# * 3: Microphone (Realtek High Defini (channels 0 & 1) -W# Available audio output devices for transmit (*=selected): -W# * 0: Speakers (C-Media USB Headphone (channel 2) -W# 1: Speakers (Bluetooth SCO Audio) -W# 2: Realtek Digital Output(Optical) -W# 3: Speakers (Bluetooth AV Audio) -W# * 4: Speakers (Realtek High Definiti (channels 0 & 1) -W# 5: Realtek Digital Output (Realtek -W# -W# Example: To use the microphone and speaker connections on the -W# system board, either of these forms can be used: -W -W#ADEVICE High -W#ADEVICE 3 4 -W -W -W# Example: To use the USB Audio, use a command like this with -W# the input and output device numbers. (Remove the # comment character.) -W#ADEVICE USB -W -W# The position in the list can change when devices (e.g. USB) are added and removed. -W# You can also specify devices by using part of the name. -W# Here is an example of specifying the USB Audio device. -W# This is case-sensitive. Upper and lower case are not treated the same. -W -W#ADEVICE USB -W -W -L# Linux ALSA is complicated. See User Guide for discussion. -L# To use something other than the default, generally use plughw -L# and a card number reported by "arecord -l" command. Example: -L -L# ADEVICE plughw:1,0 -L -L# Starting with version 1.0, you can also use "-" or "stdin" to -L# pipe stdout from some other application such as a software defined -L# radio. You can also specify "UDP:" and an optional port for input. -L# Something different must be specified for output. -L -W# ADEVICE - 0 -W# ADEVICE UDP:7355 0 -L# ADEVICE - plughw:1,0 -L# ADEVICE UDP:7355 default -L -L -C -C# -C# Number of audio channels for this souncard: 1 or 2. -C# -C -CACHANNELS 1 -C#ACHANNELS 2 -C -C -C############################################################# -C# # -C# SECOND AUDIO DEVICE PROPERTIES # -C# (Channel 2 + 3 if in stereo) # -C# # -C############################################################# -C -C#ADEVICE1 ... -C -C -C############################################################# -C# # -C# THIRD AUDIO DEVICE PROPERTIES # -C# (Channel 4 + 5 if in stereo) # -C# # -C############################################################# -C -C#ADEVICE2 ... -C -C -C############################################################# -C# # -C# CHANNEL 0 PROPERTIES # -C# # -C############################################################# -C -CCHANNEL 0 -C -C# -C# The following MYCALL, MODEM, PTT, etc. configuration items -C# apply to the most recent CHANNEL. -C# -C -C# -C# Station identifier for this channel. -C# Multiple channels can have the same or different names. -C# -C# It can be up to 6 letters and digits with an optional ssid. -C# The APRS specification requires that it be upper case. -C# -C# Example (don't use this unless you are me): MYCALL WB2OSZ-5 -C# -C -CMYCALL N0CALL -C -C# -C# Pick a suitable modem speed based on your situation. -C# 1200 Most common for VHF/UHF. Default if not specified. -C# 300 Low speed for HF SSB. -C# 9600 High speed - Can't use Microphone and Speaker connections. -C# -C# In the simplest form, just specify the speed. -C# -C -CMODEM 1200 -C#MODEM 300 -C#MODEM 9600 -C -C# -C# These are the defaults should be fine for most cases. In special situations, -C# you might want to specify different AFSK tones or the baseband mode which does -C# not use AFSK. -C# -C#MODEM 1200 1200:2200 -C#MODEM 300 1600:1800 -C#MODEM 9600 0:0 -C# -C# -C# On HF SSB, you might want to use multiple demodulators on slightly different -C# frequencies to compensate for stations off frequency. Here we have 7 different -C# demodulators at 30 Hz intervals. This takes a lot of CPU power so you will -C# probably need to reduce the audio sampling rate with the /n option. -C -C#MODEM 300 1600:1800 7@30 /4 -C -C -C# -C# Uncomment line below to enable the DTMF decoder for this channel. -C# -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! -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. -C# Both can be used for interfaces that want them driven with opposite polarity. -C# -L# COM1 can be used instead of /dev/ttyS0, COM2 for /dev/ttyS1, and so on. -L# -C -C#PTT COM1 RTS -C#PTT COM1 RTS -DTR -L#PTT /dev/ttyUSB0 RTS -C -L# -L# On Linux, you can also use general purpose I/O pins if -L# your system is configured for user access to them. -L# This would apply mostly to microprocessor boards, not a regular PC. -L# See separate Raspberry Pi document for more details. -L# The number may be preceded by "-" to invert the signal. -L# -L -L#PTT GPIO 25 -L -C# The Data Carrier Detect (DCD) signal can be sent to the same places -C# as the PTT signal. This could be used to light up an LED like a normal TNC. -C -C#DCD COM1 -DTR -L#DCD GPIO 24 -C -C -C############################################################# -C# # -C# CHANNEL 1 PROPERTIES # -C# # -C############################################################# -C -C#CHANNEL 1 -C -C# -C# Specify MYCALL, MODEM, PTT, etc. configuration items for -C# CHANNEL 1. Repeat for any other channels. -C -C -C############################################################# -C# # -C# TEXT TO SPEECH COMMAND FILE # -C# # -C############################################################# -C -W#SPEECH dwespeak.bat -L#SPEECH dwespeak.sh -C -C -C############################################################# -C# # -C# VIRTUAL TNC SERVER PROPERTIES # -C# # -C############################################################# -C -C# -C# Dire Wolf acts as a virtual TNC and can communicate with -C# client applications by different protocols: -C# -C# - the "AGW TCPIP Socket Interface" - default port 8000 -C# - KISS protocol over TCP socket - default port 8001 -W# - KISS TNC via serial port -L# - KISS TNC via pseudo terminal (-p command line option) -C# -C -CAGWPORT 8000 -CKISSPORT 8001 -C -W# -W# Some applications are designed to operate with only a physical -W# TNC attached to a serial port. For these, we provide a virtual serial -W# port that appears to be connected to a TNC. -W# -W# Take a look at the User Guide for instructions to set up -W# two virtual serial ports named COM3 and COM4 connected by -W# a null modem. -W# -W# Using the configuration described, Dire Wolf will connect to -W# COM3 and the client application will use COM4. -W# -W# Uncomment following line to use this feature. -W -W#SERIALKISS COM3 -W -W -C# -C# It is sometimes possible to recover frames with a bad FCS. -C# This applies to all channels. -C# -C# 0 [NONE] - Don't try to repair. -C# 1 [SINGLE] - Attempt to fix single bit error. (default) -C# 2 [DOUBLE] - Also attempt to fix two adjacent bits. -C# ... see User Guide for more values and in-depth discussion. -C# -C -C#FIX_BITS 0 -C -C# -C############################################################# -C# # -C# BEACONING PROPERTIES # -C# # -C############################################################# -C -C -C# -C# Beaconing is configured with these two commands: -C# -C# PBEACON - for a position report (usually yourself) -C# OBEACON - for an object report (usually some other entity) -C# -C# Each has a series of keywords and values for options. -C# See User Guide for details. -C# -C# Example: -C# -C# This results in a broadcast once every 10 minutes. -C# Every half hour, it can travel via two digipeater hops. -C# The others are kept local. -C# -C -C#PBEACON delay=1 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" via=WIDE1-1,WIDE2-1 -C#PBEACON delay=11 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" -C#PBEACON delay=21 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" -C -C -C# With UTM coordinates instead of latitude and longitude. -C -C#PBEACON delay=1 every=10 overlay=S symbol="digi" zone=19T easting=307477 northing=4720178 -C -C -C# -C# When the destination field is set to "SPEECH" the information part is -C# converted to speech rather than transmitted as a data frame. -C# -C -C#CBEACON dest="SPEECH" info="Club meeting tonight at 7 pm." -C -C -C# -C# Modify for your particular situation before removing -C# the # comment character from the beginning of appropriate lines above. -C# -C -C -C############################################################# -C# # -C# DIGIPEATER PROPERTIES # -C# # -C############################################################# -C -C# -C# For most common situations, use something like this by removing -C# the "#" from the beginning of the line below. -C# -C -C#DIGIPEAT 0 0 ^WIDE[3-7]-[1-7]$|^TEST$ ^WIDE[12]-[12]$ TRACE -C -C# See User Guide for more explanation of what this means and how -C# it can be customized for your particular needs. -C -C# Filtering can be used to limit was is digipeated. -C# For example, only weather weather reports, received on channel 0, -C# will be retransmitted on channel 1. -C# -C -C#FILTER 0 1 t/wn -C -C -C############################################################# -C# # -C# INTERNET GATEWAY # -C# # -C############################################################# -C -C# First you need to specify the name of a Tier 2 server. -C# The current preferred way is to use one of these regional rotate addresses: -C -C# noam.aprs2.net - for North America -C# soam.aprs2.net - for South America -C# euro.aprs2.net - for Europe and Africa -C# asia.aprs2.net - for Asia -C# aunz.aprs2.net - for Oceania -C -C#IGSERVER noam.aprs2.net -C -C# You also need to specify your login name and passcode. -C# Contact the author if you can't figure out how to generate the passcode. -C -C#IGLOGIN WB2OSZ-5 123456 -C -C# That's all you need for a receive only IGate which relays -C# messages from the local radio channel to the global servers. -C -C# Some might want to send an IGate client position directly to a server -C# without sending it over the air and relying on someone else to -C# forward it to an IGate server. This is done by using sendto=IG rather -C# than a radio channel number. Overlay R for receive only, T for two way. -C -C#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=R lat=42^37.14N long=071^20.83W -C#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=T lat=42^37.14N long=071^20.83W -C -C -C# To relay messages from the Internet to radio, you need to add -C# one more options with the transmit channel number and a VIA path. -C -C#IGTXVIA 0 WIDE1-1 -C -C# The APRS Internet Server (APRS-IS) has its own idea about what you -C# should be transmitting. This includes "messages" addressed to stations -C# recently heard in your area. For special situations, you can subscribe -C# to -C# decrease what you are already subscribed to. This is known as a server -C# side filter. Read here: http://www.aprs-is.net/javaprsfilter.aspx -C# Example, positions and objects within 50 km of my location: -C -C#IGFILTER m/50 -C -C# Sometimes the server will send you more than you want. You can also apply -C# local filtering to limit what will be transmitted on the RF side. -C# For example, transmit only "messages" (which is the default) on channel 0 -C# and weather reports on channel 1. -C -C#FILTER IG 0 i/30 -C#FILTER IG 1 t/wn -C -C# Finally, we don't want to flood the radio channel. -C# The IGate function will limit the number of packets transmitted -C# during 1 minute and 5 minute intervals. If a limit would -C# be exceeded, the packet is dropped and message is displayed in red. -C -CIGTXLIMIT 6 10 -C -C -C############################################################# -C# # -C# APRStt GATEWAY # -C# # -C############################################################# -C -C# -C# Dire Wolf can receive DTMF (commonly known as Touch Tone) -C# messages and convert them to packet objects. -C# -C# See separate "APRStt-Implementation-Notes" document for details. -C# -C -C# -C# Sample gateway configuration based on: -C# -C# http://www.aprs.org/aprstt/aprstt-coding24.txt -C# http://www.aprs.org/aprs-jamboree-2013.html -C# -C -C# Define specific points. -C -CTTPOINT B01 37^55.37N 81^7.86W -CTTPOINT B7495088 42.605237 -71.34456 -CTTPOINT B934 42.605237 -71.34456 -C -CTTPOINT B901 42.661279 -71.364452 -CTTPOINT B902 42.660411 -71.364419 -CTTPOINT B903 42.659046 -71.364452 -CTTPOINT B904 42.657578 -71.364602 -C -C -C# For location at given bearing and distance from starting point. -C -CTTVECTOR B5bbbddd 37^55.37N 81^7.86W 0.01 mi -C -C# For location specified by x, y coordinates. -C -CTTGRID Byyyxxx 37^50.00N 81^00.00W 37^59.99N 81^09.99W -C -C# UTM location for Lowell-Dracut-Tyngsborough State Forest. -C -CTTUTM B6xxxyyy 19T 10 300000 4720000 -C -C -C -C# Location for the corral. -C -CTTCORRAL 37^55.50N 81^7.00W 0^0.02N -C -C# Compact messages - Fixed locations xx and object yyy where -C# Object numbers 100 - 199 = bicycle -C# Object numbers 200 - 299 = fire truck -C# Others = dog -C -CTTMACRO xx1yy B9xx*AB166*AA2B4C5B3B0A1yy -CTTMACRO xx2yy B9xx*AB170*AA3C4C7C3B0A2yy -CTTMACRO xxyyy B9xx*AB180*AA3A6C4A0Ayyy -C -CTTMACRO z Cz -C -C# Receive on channel 0, Transmit object reports on channel 1 with optional via path. -C -C#TTOBJ 0 1 WIDE1-1 -C -C# Advertise gateway position with beacon. -C -C# OBEACON DELAY=0:15 EVERY=10:00 VIA=WIDE1-1 OBJNAME=WB2OSZ-tt SYMBOL=APRStt LAT=42^37.14N LONG=71^20.83W COMMENT="APRStt Gateway" -C -C diff --git a/doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf b/doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf index 077c5ff..4efd364 100644 Binary files a/doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf and b/doc/2400-4800-PSK-for-APRS-Packet-Radio.pdf differ diff --git a/doc/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf b/doc/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf index 5fecd4c..85fafb3 100644 Binary files a/doc/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf and b/doc/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf differ diff --git a/doc/AIS-Reception.pdf b/doc/AIS-Reception.pdf new file mode 100644 index 0000000..c868d7c Binary files /dev/null and b/doc/AIS-Reception.pdf differ diff --git a/doc/APRStt-Implementation-Notes.pdf b/doc/APRStt-Implementation-Notes.pdf index c1dd566..3e6b8fb 100644 Binary files a/doc/APRStt-Implementation-Notes.pdf and b/doc/APRStt-Implementation-Notes.pdf differ diff --git a/doc/AX25_plus_FEC_equals_FX25.pdf b/doc/AX25_plus_FEC_equals_FX25.pdf new file mode 100644 index 0000000..3113a1b Binary files /dev/null and b/doc/AX25_plus_FEC_equals_FX25.pdf differ diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt new file mode 100644 index 0000000..d8b6343 --- /dev/null +++ b/doc/CMakeLists.txt @@ -0,0 +1,21 @@ + +install(FILES "${CUSTOM_DOC_DIR}/README.md" DESTINATION ${INSTALL_DOC_DIR}) +install(FILES "${CUSTOM_DOC_DIR}/2400-4800-PSK-for-APRS-Packet-Radio.pdf" DESTINATION ${INSTALL_DOC_DIR}) +install(FILES "${CUSTOM_DOC_DIR}/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf" DESTINATION ${INSTALL_DOC_DIR}) +install(FILES "${CUSTOM_DOC_DIR}/A-Better-APRS-Packet-Demodulator-Part-2-9600-baud.pdf" DESTINATION ${INSTALL_DOC_DIR}) +install(FILES "${CUSTOM_DOC_DIR}/A-Closer-Look-at-the-WA8LMF-TNC-Test-CD.pdf" DESTINATION ${INSTALL_DOC_DIR}) +install(FILES "${CUSTOM_DOC_DIR}/AIS-Reception.pdf" DESTINATION ${INSTALL_DOC_DIR}) +install(FILES "${CUSTOM_DOC_DIR}/APRS-Telemetry-Toolkit.pdf" DESTINATION ${INSTALL_DOC_DIR}) +install(FILES "${CUSTOM_DOC_DIR}/APRStt-Implementation-Notes.pdf" DESTINATION ${INSTALL_DOC_DIR}) +install(FILES "${CUSTOM_DOC_DIR}/APRStt-interface-for-SARTrack.pdf" DESTINATION ${INSTALL_DOC_DIR}) +install(FILES "${CUSTOM_DOC_DIR}/APRStt-Listening-Example.pdf" DESTINATION ${INSTALL_DOC_DIR}) +install(FILES "${CUSTOM_DOC_DIR}/AX25_plus_FEC_equals_FX25.pdf" DESTINATION ${INSTALL_DOC_DIR}) +install(FILES "${CUSTOM_DOC_DIR}/Bluetooth-KISS-TNC.pdf" DESTINATION ${INSTALL_DOC_DIR}) +install(FILES "${CUSTOM_DOC_DIR}/Going-beyond-9600-baud.pdf" DESTINATION ${INSTALL_DOC_DIR}) +install(FILES "${CUSTOM_DOC_DIR}/Raspberry-Pi-APRS.pdf" DESTINATION ${INSTALL_DOC_DIR}) +install(FILES "${CUSTOM_DOC_DIR}/Raspberry-Pi-APRS-Tracker.pdf" DESTINATION ${INSTALL_DOC_DIR}) +install(FILES "${CUSTOM_DOC_DIR}/Raspberry-Pi-SDR-IGate.pdf" DESTINATION ${INSTALL_DOC_DIR}) +install(FILES "${CUSTOM_DOC_DIR}/Successful-APRS-IGate-Operation.pdf" DESTINATION ${INSTALL_DOC_DIR}) +install(FILES "${CUSTOM_DOC_DIR}/User-Guide.pdf" DESTINATION ${INSTALL_DOC_DIR}) +install(FILES "${CUSTOM_DOC_DIR}/WA8LMF-TNC-Test-CD-Results.pdf" DESTINATION ${INSTALL_DOC_DIR}) +install(FILES "${CUSTOM_DOC_DIR}/Why-is-9600-only-twice-as-fast-as-1200.pdf" DESTINATION ${INSTALL_DOC_DIR}) diff --git a/doc/README.md b/doc/README.md index 9ea05cd..40aa77d 100644 --- a/doc/README.md +++ b/doc/README.md @@ -26,7 +26,26 @@ Brief summary of packet radio / APRS history and the capbilities of Dire Wolf. These dive into more detail for specialized topics or typical usage scenarios. -- [**Successful APRS IGate Operation**](Successful-APRS-IGate-Operation.pdf) [ [*download*](../../../raw/master/doc/Successful-APRS-IGate-Operation.pdf) ] + + +- [**AX.25 + FEC = FX.25**](AX25_plus_FEC_equals_FX25.pdf) [ [*download*](../../../raw/dev/doc/AX25_plus_FEC_equals_FX25.pdf) ] + + What can you do if your radio signal isn’t quite strong enough to get through reliably? Move to higher ground? Get a better antenna? More power? Use very narrow bandwidth and very slow data? + + Sometimes those are not options. Another way to improve communication reliability is to add redundant information so the message will still get through even if small parts are missing. FX.25 adds forward error correction (FEC) which maintaining complete compatibility with older equipment. + + +- [**AX.25 Throughput: Why is 9600 bps Packet Radio only twice as fast as 1200?**](Why-is-9600-only-twice-as-fast-as-1200.pdf) [ [*download*](../../../raw/dev/doc/Why-is-9600-only-twice-as-fast-as-1200.pdf) ] + + Simply switching to a higher data rate will probably result in great disappointment. You might expect it to be 8 times faster but it can turn out to be only twice as fast. + + In this document, we look at why a large increase in data bit rate can produce a much smaller increase in throughput. We will explore techniques that can be used to make large improvements and drastically speed up large data transfer. + + + + +- [**Successful APRS IGate Operation**](Successful-APRS-IGate-Operation.pdf) [ [*download*](../../../raw/dev/doc/Successful-APRS-IGate-Operation.pdf) ] + Dire Wolf can serve as a gateway between the APRS radio network and APRS-IS servers on the Internet. @@ -79,9 +98,29 @@ These dive into more detail for specialized topics or typical usage scenarios. Why stop at 9600 baud? Go faster if your soundcard and radio can handle it. +- [**AIS Reception**](AIS-Reception.pdf) [ [*download*](../../../raw/dev/doc/AIS-Reception.pdf) ] + + + AIS is an international tracking system for ships. Messages can contain position, speed, course, name, destination, status, vessel dimensions, and many other types of information. Learn how to receive these signals with an ordindary ham transceiver and display the ship locations with APRS applications or [OpenCPN](https://opencpn.org). + +- **[EAS to APRS message converter](https://github.com/wb2osz/eas2aprs)** + + + The [U.S. National Weather Service](https://www.weather.gov/nwr/) (NWS) operates more than 1,000 VHF FM radio stations that continuously transmit weather information. These stations also transmit special warnings about severe weather, disasters (natural & manmade), and public safety. + + Alerts are sent in a digital form known as Emergency Alert System (EAS) Specific Area Message Encoding (SAME). [You can hear a sample here](https://en.wikipedia.org/wiki/Specific_Area_Message_Encoding). + + It is possible to buy radios that decode these messages but what fun is that? We are ham radio operators so we want to build our own from stuff that we already have sitting around. + ## Miscellaneous ## +- **[Ham Radio of Things (HRoT)](https://github.com/wb2osz/hrot)** + + + Now that billions of computers and mobile phones (which are handheld computers) are all connected by the Internet, the large growth is expected from the “Internet of Things.” What is a “thing?” It could be a temperature sensor, garage door opener, motion detector, flood water level, smoke alarm, antenna rotator, coffee maker, lights, home thermostat, …, just about anything you might want to monitor or control. + + There have been other occasional mentions of merging Ham Radio with the Internet of Things but only ad hoc incompatible narrowly focused applications. Here is a proposal for a standardized more flexible method so different systems can communicate with each other. - [**A Better APRS Packet Demodulator, part 1, 1200 baud**](A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf) [ [*download*](../../../raw/master/doc/A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf) ] @@ -119,11 +158,11 @@ and a couple things that can be done about it. Here are some good places to ask questions and share your experiences: -- [Dire Wolf packet TNC](https://groups.yahoo.com/neo/groups/direwolf_packet/info) +- [Dire Wolf Software TNC](https://groups.io/g/direwolf) -- [Raspberry Pi 4 Ham Radio](https://groups.yahoo.com/neo/groups/Raspberry_Pi_4-Ham_RADIO/info) +- [Raspberry Pi 4 Ham Radio](https://groups.io/g/RaspberryPi-4-HamRadio) -- [linuxham](https://groups.yahoo.com/neo/groups/linuxham/info) +- [linuxham](https://groups.io/g/linuxham) - [TAPR aprssig](http://www.tapr.org/pipermail/aprssig/) diff --git a/doc/Raspberry-Pi-APRS-Tracker.pdf b/doc/Raspberry-Pi-APRS-Tracker.pdf index 20d86b2..c0c8c0b 100644 Binary files a/doc/Raspberry-Pi-APRS-Tracker.pdf and b/doc/Raspberry-Pi-APRS-Tracker.pdf differ diff --git a/doc/Raspberry-Pi-APRS.pdf b/doc/Raspberry-Pi-APRS.pdf index a8c98bf..7bbff45 100644 Binary files a/doc/Raspberry-Pi-APRS.pdf and b/doc/Raspberry-Pi-APRS.pdf differ diff --git a/doc/User-Guide.pdf b/doc/User-Guide.pdf index f05dbbc..d7010a1 100644 Binary files a/doc/User-Guide.pdf and b/doc/User-Guide.pdf differ diff --git a/doc/WA8LMF-TNC-Test-CD-Results.pdf b/doc/WA8LMF-TNC-Test-CD-Results.pdf index 623e265..d9af1a3 100644 Binary files a/doc/WA8LMF-TNC-Test-CD-Results.pdf and b/doc/WA8LMF-TNC-Test-CD-Results.pdf differ diff --git a/doc/Why-is-9600-only-twice-as-fast-as-1200.pdf b/doc/Why-is-9600-only-twice-as-fast-as-1200.pdf new file mode 100644 index 0000000..829aa64 Binary files /dev/null and b/doc/Why-is-9600-only-twice-as-fast-as-1200.pdf differ diff --git a/dsp.h b/dsp.h deleted file mode 100644 index 1f5aaa5..0000000 --- a/dsp.h +++ /dev/null @@ -1,10 +0,0 @@ - -/* dsp.h */ - -// TODO: put prefixes on these names. - -float window (bp_window_t type, int size, int j); - -void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype); - -void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_window_t wtype); \ No newline at end of file diff --git a/dw-icon.rc b/dw-icon.rc deleted file mode 100644 index ce34b40..0000000 --- a/dw-icon.rc +++ /dev/null @@ -1 +0,0 @@ -MAINICON ICON "dw-icon.ico" \ No newline at end of file diff --git a/LICENSE-other.txt b/external/LICENSE similarity index 100% rename from LICENSE-other.txt rename to external/LICENSE diff --git a/external/geotranz/CMakeLists.txt b/external/geotranz/CMakeLists.txt new file mode 100644 index 0000000..576d8b8 --- /dev/null +++ b/external/geotranz/CMakeLists.txt @@ -0,0 +1,17 @@ +# UTM, USNG, MGRS conversions + +set(GEOTRANZ_LIBRARIES geotranz CACHE INTERNAL "geotranz") + +list(APPEND geotranz_SOURCES + error_string.c + mgrs.c + polarst.c + tranmerc.c + ups.c + usng.c + utm.c + ) + +add_library(geotranz STATIC + ${geotranz_SOURCES} + ) diff --git a/geotranz/README-FIRST.txt b/external/geotranz/README-FIRST.txt similarity index 100% rename from geotranz/README-FIRST.txt rename to external/geotranz/README-FIRST.txt diff --git a/geotranz/error_string.c b/external/geotranz/error_string.c similarity index 100% rename from geotranz/error_string.c rename to external/geotranz/error_string.c diff --git a/geotranz/error_string.h b/external/geotranz/error_string.h similarity index 100% rename from geotranz/error_string.h rename to external/geotranz/error_string.h diff --git a/geotranz/mgrs.c b/external/geotranz/mgrs.c similarity index 99% rename from geotranz/mgrs.c rename to external/geotranz/mgrs.c index 66128c6..84454ab 100644 --- a/geotranz/mgrs.c +++ b/external/geotranz/mgrs.c @@ -400,7 +400,7 @@ long Make_MGRS_String (char* MGRS, if (Zone) i = sprintf (MGRS+i,"%2.2ld",Zone); else - strncpy(MGRS, " ", 2); // 2 spaces + strcpy(MGRS, " "); // 2 spaces - Should i be set to 2? for (j=0;j<3;j++) MGRS[i++] = alphabet[Letters[j]]; diff --git a/geotranz/mgrs.h b/external/geotranz/mgrs.h similarity index 100% rename from geotranz/mgrs.h rename to external/geotranz/mgrs.h diff --git a/geotranz/polarst.c b/external/geotranz/polarst.c similarity index 100% rename from geotranz/polarst.c rename to external/geotranz/polarst.c diff --git a/geotranz/polarst.h b/external/geotranz/polarst.h similarity index 100% rename from geotranz/polarst.h rename to external/geotranz/polarst.h diff --git a/geotranz/readme.txt b/external/geotranz/readme.txt similarity index 100% rename from geotranz/readme.txt rename to external/geotranz/readme.txt diff --git a/geotranz/releasenotes.txt b/external/geotranz/releasenotes.txt similarity index 100% rename from geotranz/releasenotes.txt rename to external/geotranz/releasenotes.txt diff --git a/geotranz/tranmerc.c b/external/geotranz/tranmerc.c similarity index 100% rename from geotranz/tranmerc.c rename to external/geotranz/tranmerc.c diff --git a/geotranz/tranmerc.h b/external/geotranz/tranmerc.h similarity index 100% rename from geotranz/tranmerc.h rename to external/geotranz/tranmerc.h diff --git a/geotranz/ups.c b/external/geotranz/ups.c similarity index 100% rename from geotranz/ups.c rename to external/geotranz/ups.c diff --git a/geotranz/ups.h b/external/geotranz/ups.h similarity index 100% rename from geotranz/ups.h rename to external/geotranz/ups.h diff --git a/geotranz/usng.c b/external/geotranz/usng.c similarity index 99% rename from geotranz/usng.c rename to external/geotranz/usng.c index e083311..fdd2fba 100644 --- a/geotranz/usng.c +++ b/external/geotranz/usng.c @@ -367,7 +367,7 @@ long Make_USNG_String (char* USNG, if (Zone) i = sprintf (USNG+i,"%2.2ld",Zone); else - strncpy(USNG, " ", 2); // 2 spaces + strcpy(USNG, " "); // 2 spaces - Should i be set to 2? for (j=0;j<3;j++) USNG[i++] = alphabet[Letters[j]]; diff --git a/geotranz/usng.h b/external/geotranz/usng.h similarity index 100% rename from geotranz/usng.h rename to external/geotranz/usng.h diff --git a/geotranz/utm.c b/external/geotranz/utm.c similarity index 100% rename from geotranz/utm.c rename to external/geotranz/utm.c diff --git a/geotranz/utm.h b/external/geotranz/utm.h similarity index 100% rename from geotranz/utm.h rename to external/geotranz/utm.h diff --git a/external/misc/CMakeLists.txt b/external/misc/CMakeLists.txt new file mode 100644 index 0000000..07b10b2 --- /dev/null +++ b/external/misc/CMakeLists.txt @@ -0,0 +1,41 @@ + +set(MISC_LIBRARIES misc CACHE INTERNAL "misc") + +include_directories( + ${CMAKE_SOURCE_DIR}/src + ) + +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 + ) + + add_library(misc STATIC + ${misc_SOURCES} + ) + +elseif(WIN32 OR CYGWIN) # windows + + list(APPEND misc_SOURCES + # There are several string functions found in Linux + # but not on Windows. Need to provide our own copy. + ${CUSTOM_MISC_DIR}/strsep.c + ${CUSTOM_MISC_DIR}/strtok_r.c + ${CUSTOM_MISC_DIR}/strcasestr.c + ${CUSTOM_MISC_DIR}/strlcpy.c + ${CUSTOM_MISC_DIR}/strlcat.c + ) + + add_library(misc STATIC + ${misc_SOURCES} + ) + +else() + + # on macOS, OpenBSD and FreeBSD not misc is necessary + set(MISC_LIBRARIES "" CACHE INTERNAL "") + +endif() diff --git a/misc/README-dire-wolf.txt b/external/misc/README similarity index 52% rename from misc/README-dire-wolf.txt rename to external/misc/README index 140f960..f69c05a 100644 --- a/misc/README-dire-wolf.txt +++ b/external/misc/README @@ -4,31 +4,24 @@ Files in this directory fill in the gaps missing for some operating systems. -------------------------------------- -These are part of the standard C library for Linux and similar operating systems. -For the Windows version we need to include our own copy. +These are part of the standard C library for Linux, BSD Unix, and similar operating systems. +They are not present for MS Windows so we need to supply our own copy. -They were copied from Cygwin source. -/usr/src/cygwin-1.7.10-1/newlib/libc/string/... +From http://ftp.netbsd.org/pub/pkgsrc/current/pkgsrc/net/tnftp/files/libnetbsd/strsep.c +and other BSD locations. strsep.c + strcasestr.c strtok_r.c -------------------------------------- -This was also missing on Windows but available everywhere else. - strcasestr.c - --------------------------------------- - - -The are used for the Linux and Windows versions. +These are needed for the Linux and Windows versions. They should be part of the standard C library for OpenBSD, FreeBSD, Mac OS X. -These are from OpenBSD. + http://ftp.netbsd.org/pub/pkgsrc/current/pkgsrc/net/tnftp/files/libnetbsd/strlcpy.c http://ftp.netbsd.org/pub/pkgsrc/current/pkgsrc/net/tnftp/files/libnetbsd/strlcat.c - strlcpy.c - strlcat.c - \ No newline at end of file + strlcat.c \ No newline at end of file diff --git a/misc/strcasestr.c b/external/misc/strcasestr.c similarity index 100% rename from misc/strcasestr.c rename to external/misc/strcasestr.c diff --git a/misc/strlcat.c b/external/misc/strlcat.c similarity index 100% rename from misc/strlcat.c rename to external/misc/strlcat.c diff --git a/misc/strlcpy.c b/external/misc/strlcpy.c similarity index 100% rename from misc/strlcpy.c rename to external/misc/strlcpy.c diff --git a/external/misc/strsep.c b/external/misc/strsep.c new file mode 100644 index 0000000..a333815 --- /dev/null +++ b/external/misc/strsep.c @@ -0,0 +1,72 @@ +/* $NetBSD: strsep.c,v 1.5 2014/10/31 18:59:32 spz Exp $ */ +/* from NetBSD: strsep.c,v 1.14 2003/08/07 16:43:52 agc Exp */ + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +//#include "tnftp.h" +#include + +/* + * Get next token from string *stringp, where tokens are possibly-empty + * strings separated by characters from delim. + * + * Writes NULs into the string at *stringp to end tokens. + * delim need not remain constant from call to call. + * On return, *stringp points past the last NUL written (if there might + * be further tokens), or is NULL (if there are definitely no more tokens). + * + * If *stringp is NULL, strsep returns NULL. + */ +char * +strsep(char **stringp, const char *delim) +{ + char *s; + const char *spanp; + int c, sc; + char *tok; + + if ((s = *stringp) == NULL) + return (NULL); + for (tok = s;;) { + c = *s++; + spanp = delim; + do { + if ((sc = *spanp++) == c) { + if (c == 0) + s = NULL; + else + s[-1] = 0; + *stringp = s; + return (tok); + } + } while (sc != 0); + } + /* NOTREACHED */ +} diff --git a/misc/strtok_r.c b/external/misc/strtok_r.c similarity index 100% rename from misc/strtok_r.c rename to external/misc/strtok_r.c diff --git a/external/regex/CMakeLists.txt b/external/regex/CMakeLists.txt new file mode 100644 index 0000000..6720763 --- /dev/null +++ b/external/regex/CMakeLists.txt @@ -0,0 +1,24 @@ +set(REGEX_LIBRARIES "" CACHE INTERNAL "") + +if(WIN32 OR CYGWIN) # windows + + set(REGEX_LIBRARIES regex CACHE INTERNAL "regex") + + list(APPEND regex_SOURCES + # When building for Linux, we use regular expression + # functions supplied by the gnu C library. + # For the native WIN32 version, we need to use our own copy. + # These were copied from http://gnuwin32.sourceforge.net/packages/regex.htm + # Consider upgrading from https://www.gnu.org/software/libc/sources.html + ${CUSTOM_REGEX_DIR}/regex.c + ) + + add_library(regex STATIC + ${regex_SOURCES} + ) + + set_target_properties(regex + PROPERTIES COMPILE_FLAGS "-Dbool=int -Dtrue=1 -Dfalse=0 -DUSE_REGEX_STATIC" + ) + +endif() diff --git a/regex/COPYING b/external/regex/COPYING similarity index 100% rename from regex/COPYING rename to external/regex/COPYING diff --git a/regex/INSTALL b/external/regex/INSTALL similarity index 100% rename from regex/INSTALL rename to external/regex/INSTALL diff --git a/regex/LICENSES b/external/regex/LICENSES similarity index 100% rename from regex/LICENSES rename to external/regex/LICENSES diff --git a/regex/NEWS b/external/regex/NEWS similarity index 100% rename from regex/NEWS rename to external/regex/NEWS diff --git a/regex/README b/external/regex/README similarity index 100% rename from regex/README rename to external/regex/README diff --git a/regex/README-dire-wolf.txt b/external/regex/README-dire-wolf.txt similarity index 100% rename from regex/README-dire-wolf.txt rename to external/regex/README-dire-wolf.txt diff --git a/regex/re_comp.h b/external/regex/re_comp.h similarity index 100% rename from regex/re_comp.h rename to external/regex/re_comp.h diff --git a/regex/regcomp.c b/external/regex/regcomp.c similarity index 99% rename from regex/regcomp.c rename to external/regex/regcomp.c index 006fe5c..eb6d7a4 100644 --- a/regex/regcomp.c +++ b/external/regex/regcomp.c @@ -2510,7 +2510,7 @@ parse_dup_op (bin_tree_t *elem, re_string_t *regexp, re_dfa_t *dfa, old_tree = NULL; if (elem->token.type == SUBEXP) - postorder (elem, mark_opt_subexp, (void *) (long) elem->token.opr.idx); + postorder (elem, mark_opt_subexp, (void *) (ptrdiff_t) elem->token.opr.idx); tree = create_tree (dfa, elem, NULL, (end == -1 ? OP_DUP_ASTERISK : OP_ALT)); if (BE (tree == NULL, 0)) @@ -3725,7 +3725,7 @@ create_token_tree (re_dfa_t *dfa, bin_tree_t *left, bin_tree_t *right, static reg_errcode_t mark_opt_subexp (void *extra, bin_tree_t *node) { - int idx = (int) (long) extra; + int idx = (int) (ptrdiff_t) extra; if (node->token.type == SUBEXP && node->token.opr.idx == idx) node->token.opt_subexp = 1; diff --git a/regex/regex.c b/external/regex/regex.c similarity index 100% rename from regex/regex.c rename to external/regex/regex.c diff --git a/regex/regex.h b/external/regex/regex.h similarity index 100% rename from regex/regex.h rename to external/regex/regex.h diff --git a/regex/regex_internal.c b/external/regex/regex_internal.c similarity index 100% rename from regex/regex_internal.c rename to external/regex/regex_internal.c diff --git a/regex/regex_internal.h b/external/regex/regex_internal.h similarity index 100% rename from regex/regex_internal.h rename to external/regex/regex_internal.h diff --git a/regex/regexec.c b/external/regex/regexec.c similarity index 100% rename from regex/regexec.c rename to external/regex/regexec.c diff --git a/fsk_demod_state.h b/fsk_demod_state.h deleted file mode 100644 index a37b24d..0000000 --- a/fsk_demod_state.h +++ /dev/null @@ -1,291 +0,0 @@ -/* fsk_demod_state.h */ - -#ifndef FSK_DEMOD_STATE_H - -#include "rpack.h" - -#include "audio.h" // for enum modem_t - -/* - * Demodulator state. - * The name of the file is from we only had FSK. Now we have other techniques. - * Different copy is required for each channel & subchannel being processed concurrently. - */ - -// TODO1.2: change prefix from BP_ to DSP_ - -typedef enum bp_window_e { BP_WINDOW_TRUNCATED, - BP_WINDOW_COSINE, - BP_WINDOW_HAMMING, - BP_WINDOW_BLACKMAN, - BP_WINDOW_FLATTOP } bp_window_t; - - -struct demodulator_state_s -{ -/* - * These are set once during initialization. - */ - enum modem_t modem_type; // MODEM_AFSK, MODEM_8PSK, etc. - - char profile; // 'A', 'B', etc. Upper case. - // Only needed to see if we are using 'F' to take fast path. - -#define TICKS_PER_PLL_CYCLE ( 256.0 * 256.0 * 256.0 * 256.0 ) - - int pll_step_per_sample; // PLL is advanced by this much each audio sample. - // Data is sampled when it overflows. - - - int ms_filter_size; /* Size of mark & space filters, in audio samples. */ - /* Started off as a guess of one bit length */ - /* but somewhat longer turned out to be better. */ - /* Currently using same size for any prefilter. */ - -#define MAX_FILTER_SIZE 320 /* 304 is needed for profile C, 300 baud & 44100. */ - -/* - * Filter length for Mark & Space in bit times. - * e.g. 1 means 1/1200 second for 1200 baud. - */ - float ms_filter_len_bits; - -/* - * 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. - * Second is frequency as ratio to baud rate for FIR. - */ - int lpf_use_fir; /* 0 for IIR, 1 for FIR. */ - - float lpf_iir; /* Only if using IIR. */ - - float lpf_baud; /* Cutoff frequency as fraction of baud. */ - /* Intuitively we'd expect this to be somewhere */ - /* in the range of 0.5 to 1. */ - /* In practice, it turned out a little larger */ - /* for profiles B, C, D. */ - - float lp_filter_len_bits; /* Length in number of bit 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. */ - -/* - * Automatic gain control. Fast attack and slow decay factors. - */ - float agc_fast_attack; - float agc_slow_decay; - -/* - * Use a longer term view for reporting signal levels. - */ - float quick_attack; - float sluggish_decay; - -/* - * Hysteresis before final demodulator 0 / 1 decision. - */ - float hysteresis; - int num_slicers; /* >1 for multiple slicers. */ - -/* - * Phase Locked Loop (PLL) inertia. - * Larger number means less influence by signal transitions. - */ - float pll_locked_inertia; - float pll_searching_inertia; - - -/* - * Optional band pass pre-filter before mark/space detector. - */ - int use_prefilter; /* True to enable it. */ - - float prefilter_baud; /* Cutoff frequencies, as fraction of */ - /* baud rate, beyond tones used. */ - /* Example, if we used 1600/1800 tones at */ - /* 300 baud, and this was 0.5, the cutoff */ - /* frequencies would be: */ - /* lower = min(1600,1800) - 0.5 * 300 = 1450 */ - /* upper = max(1600,1800) + 0.5 * 300 = 1950 */ - - float pre_filter_len_bits; /* Length in number of bit times. */ - - int pre_filter_size; /* Size of pre filter, in audio samples. */ - - float pre_filter[MAX_FILTER_SIZE] __attribute__((aligned(16))); - -/* - * Kernel for the mark and space detection filters. - */ - - float m_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); - float m_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); - - float s_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); - float s_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); - -/* - * These are for PSK only. - * They are number of delay line taps into previous symbol. - * They are one symbol period and + or - 45 degrees of the carrier frequency. - */ - int boffs; /* symbol length based on sample rate and baud. */ - int coffs; /* to get cos component of previous symbol. */ - int soffs; /* to get sin component of previous symbol. */ - - unsigned int lo_step; /* How much to advance the local oscillator */ - /* phase for each audio sample. */ - - int psk_use_lo; /* Use local oscillator rather than self correlation. */ - - -/* - * The rest are continuously updated. - */ - - 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 - * AGC uses "fast" attack and "slow" decay. - */ - - float alevel_rec_peak; - float alevel_rec_valley; - 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; - -/* - * For the PLL and data bit timing. - * starting in version 1.2 we can have multiple slicers for one demodulator. - * Each slicer has its own PLL and HDLC decoder. - */ - -/* - * Version 1.3: Clean up subchan vs. slicer. - * - * Originally some number of CHANNELS (originally 2, later 6) - * which can have multiple parallel demodulators called SUB-CHANNELS. - * This was originally for staggered frequencies for HF SSB. - * It can also be used for multiple demodulators with the same - * frequency but other differing parameters. - * Each subchannel has its own demodulator and HDLC decoder. - * - * In version 1.2 we added multiple SLICERS. - * The data structure, here, has multiple slicers per - * demodulator (subchannel). Due to fuzzy thinking or - * expediency, the multiple slicers got mapped into subchannels. - * This means we can't use both multiple decoders and - * multiple slicers at the same time. - * - * Clean this up in 1.3 and keep the concepts separate. - * This means adding a third variable many places - * we are passing around the origin. - * - */ - struct { - - signed int data_clock_pll; // PLL for data clock recovery. - // It is incremented by pll_step_per_sample - // for each audio sample. - - signed int prev_d_c_pll; // Previous value of above, before - // incrementing, to detect overflows. - - int prev_demod_data; // Previous data bit detected. - // Used to look for transitions. - float prev_demod_out_f; - - /* This is used only for "9600" baud data. */ - - int lfsr; // Descrambler shift register. - - } slicer [MAX_SLICERS]; // Actual number in use is num_slicers. - // Should be in range 1 .. MAX_SLICERS, - -/* - * Special for Rino decoder only. - * One for each possible signal polarity. - * The project showed promise but fell by the wayside. - */ - -#if 0 - - struct gr_state_s { - - signed int data_clock_pll; // PLL for data clock recovery. - // It is incremented by pll_step_per_sample - // for each audio sample. - - signed int prev_d_c_pll; // Previous value of above, before - // incrementing, to detect overflows. - - float gr_minus_peak; // For automatic gain control. - float gr_plus_peak; - - int gr_sync; // Is sync pulse present? - int gr_prev_sync; // Previous state to detect leading edge. - - int gr_first_sample; // Index of starting sample index for debugging. - - int gr_dcd; // Data carrier detect. i.e. are we - // currently decoding a message. - - float gr_early_sum; // For averaging bit values in two regions. - int gr_early_count; - float gr_late_sum; - int gr_late_count; - float gr_sync_sum; - int gr_sync_count; - - int gr_bit_count; // Bit index into message. - - struct rpack_s rpack; // Collection of bits. - - } gr_state[2]; -#endif - -}; - -#define FSK_DEMOD_STATE_H 1 -#endif \ No newline at end of file diff --git a/fx25.png b/fx25.png new file mode 100644 index 0000000..33d74a9 Binary files /dev/null and b/fx25.png differ diff --git a/generic.conf b/generic.conf deleted file mode 100644 index 223007f..0000000 --- a/generic.conf +++ /dev/null @@ -1,573 +0,0 @@ -C############################################################# -C# # -C# Configuration file for Dire Wolf # -C# # -L# Linux version # -W# Windows version # -M# Macintosh version # -C# # -C############################################################# -R -R -R The sample config file was getting pretty messy -R with the Windows and Linux differences. -R It would be a maintenance burden to keep most of -R two different versions in sync. -R This common source is now used to generate the -R two different variations while having only a single -R copy of the common parts. -R -R The first column contains one of the following: -R -R R remark which is discarded. -R C common to both versions. -R W Windows version only. -R L Linux version only. -R M Macintosh version and possibly others (portaudio used). -R -C# -C# Consult the User Guide for more details on configuration options. -C# -C# -C# These are the most likely settings you might change: -C# -C# (1) MYCALL - call sign and SSID for your station. -C# -C# Look for lines starting with MYCALL and -C# change NOCALL to your own. -C# -C# (2) PBEACON - enable position beaconing. -C# -C# Look for lines starting with PBEACON and -C# modify for your call, location, etc. -C# -C# (3) DIGIPEATER - configure digipeating rules. -C# -C# Look for lines starting with DIGIPEATER. -C# Most people will probably use the given example. -C# Just remove the "#" from the start of the line -C# to enable it. -C# -C# (4) IGSERVER, IGLOGIN - IGate server and login -C# -C# Configure an IGate client to relay messages between -C# radio and internet servers. -C# -C# -C# The default location is "direwolf.conf" in the current working directory. -L# On Linux, the user's home directory will also be searched. -C# An alternate configuration file location can be specified with the "-c" command line option. -C# -C# As you probably guessed by now, # indicates a comment line. -C# -C# Remove the # at the beginning of a line if you want to use a sample -C# configuration that is currently commented out. -C# -C# Commands are a keyword followed by parameters. -C# -C# Command key words are case insensitive. i.e. upper and lower case are equivalent. -C# -C# Command parameters are generally case sensitive. i.e. upper and lower case are different. -C# -C -C -C############################################################# -C# # -C# FIRST AUDIO DEVICE PROPERTIES # -C# (Channel 0 + 1 if in stereo) # -C# # -C############################################################# -C -C# -C# Many people will simply use the default sound device. -C# Some might want to use an alternative device by chosing it here. -C# -W# When the Windows version starts up, it displays something like -W# this with the available sound devices and capabilities: -W# -W# Available audio input devices for receive (*=selected): -W# * 0: Microphone (C-Media USB Headpho (channel 2) -W# 1: Microphone (Bluetooth SCO Audio -W# 2: Microphone (Bluetooth AV Audio) -W# * 3: Microphone (Realtek High Defini (channels 0 & 1) -W# Available audio output devices for transmit (*=selected): -W# * 0: Speakers (C-Media USB Headphone (channel 2) -W# 1: Speakers (Bluetooth SCO Audio) -W# 2: Realtek Digital Output(Optical) -W# 3: Speakers (Bluetooth AV Audio) -W# * 4: Speakers (Realtek High Definiti (channels 0 & 1) -W# 5: Realtek Digital Output (Realtek -W# -W# Example: To use the microphone and speaker connections on the -W# system board, either of these forms can be used: -W -W#ADEVICE High -W#ADEVICE 3 4 -W -W -W# Example: To use the USB Audio, use a command like this with -W# the input and output device numbers. (Remove the # comment character.) -W#ADEVICE USB -W -W# The position in the list can change when devices (e.g. USB) are added and removed. -W# You can also specify devices by using part of the name. -W# Here is an example of specifying the USB Audio device. -W# This is case-sensitive. Upper and lower case are not treated the same. -W -W#ADEVICE USB -W -W -L# Linux ALSA is complicated. See User Guide for discussion. -L# To use something other than the default, generally use plughw -L# and a card number reported by "arecord -l" command. Example: -L -L# ADEVICE plughw:1,0 -L -L# Starting with version 1.0, you can also use "-" or "stdin" to -L# pipe stdout from some other application such as a software defined -L# radio. You can also specify "UDP:" and an optional port for input. -L# Something different must be specified for output. -L -M# Macintosh Operating System uses portaudio driver for audio -M# input/output. Default device selection not available. User/OP -M# must configure the sound input/output option. Note that -M# the device names can contain spaces. In this case, the names -M# must be enclosed by quotes. -M# -M# Examples: -M# -M# ADEVICE "USB Audio Codec:6" "USB Audio Codec:5" -M# -M# -W# ADEVICE - 0 -W# ADEVICE UDP:7355 0 -L# ADEVICE - plughw:1,0 -L# ADEVICE UDP:7355 default -M# ADEVICE UDP:7355 default -M# -L -L -C -C# -C# Number of audio channels for this souncard: 1 or 2. -C# -C -CACHANNELS 1 -C#ACHANNELS 2 -C -C -C############################################################# -C# # -C# SECOND AUDIO DEVICE PROPERTIES # -C# (Channel 2 + 3 if in stereo) # -C# # -C############################################################# -C -C#ADEVICE1 ... -C -C -C############################################################# -C# # -C# THIRD AUDIO DEVICE PROPERTIES # -C# (Channel 4 + 5 if in stereo) # -C# # -C############################################################# -C -C#ADEVICE2 ... -C -C -C############################################################# -C# # -C# CHANNEL 0 PROPERTIES # -C# # -C############################################################# -C -CCHANNEL 0 -C -C# -C# The following MYCALL, MODEM, PTT, etc. configuration items -C# apply to the most recent CHANNEL. -C# -C -C# -C# Station identifier for this channel. -C# Multiple channels can have the same or different names. -C# -C# It can be up to 6 letters and digits with an optional ssid. -C# The APRS specification requires that it be upper case. -C# -C# Example (don't use this unless you are me): MYCALL WB2OSZ-5 -C# -C -CMYCALL N0CALL -C -C# -C# Pick a suitable modem speed based on your situation. -C# 1200 Most common for VHF/UHF. Default if not specified. -C# 300 Low speed for HF SSB. -C# 9600 High speed - Can't use Microphone and Speaker connections. -C# -C# In the simplest form, just specify the speed. -C# -C -CMODEM 1200 -C#MODEM 300 -C#MODEM 9600 -C -C# -C# These are the defaults should be fine for most cases. In special situations, -C# you might want to specify different AFSK tones or the baseband mode which does -C# not use AFSK. -C# -C#MODEM 1200 1200:2200 -C#MODEM 300 1600:1800 -C#MODEM 9600 0:0 -C# -C# -C# On HF SSB, you might want to use multiple demodulators on slightly different -C# frequencies to compensate for stations off frequency. Here we have 7 different -C# demodulators at 30 Hz intervals. This takes a lot of CPU power so you will -C# probably need to reduce the audio sampling rate with the /n option. -C -C#MODEM 300 1600:1800 7@30 /4 -C -C -C# -C# Uncomment line below to enable the DTMF decoder for this channel. -C# -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! -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. -C# Both can be used for interfaces that want them driven with opposite polarity. -C# -L# COM1 can be used instead of /dev/ttyS0, COM2 for /dev/ttyS1, and so on. -L# -C -C#PTT COM1 RTS -C#PTT COM1 RTS -DTR -L#PTT /dev/ttyUSB0 RTS -C -L# -L# On Linux, you can also use general purpose I/O pins if -L# your system is configured for user access to them. -L# This would apply mostly to microprocessor boards, not a regular PC. -L# See separate Raspberry Pi document for more details. -L# The number may be preceded by "-" to invert the signal. -L# -L -L#PTT GPIO 25 -L -C# The Data Carrier Detect (DCD) signal can be sent to the same places -C# as the PTT signal. This could be used to light up an LED like a normal TNC. -C -C#DCD COM1 -DTR -L#DCD GPIO 24 -C -C -C############################################################# -C# # -C# CHANNEL 1 PROPERTIES # -C# # -C############################################################# -C -C#CHANNEL 1 -C -C# -C# Specify MYCALL, MODEM, PTT, etc. configuration items for -C# CHANNEL 1. Repeat for any other channels. -C -C -C############################################################# -C# # -C# TEXT TO SPEECH COMMAND FILE # -C# # -C############################################################# -C -W#SPEECH dwespeak.bat -L#SPEECH dwespeak.sh -C -C -C############################################################# -C# # -C# VIRTUAL TNC SERVER PROPERTIES # -C# # -C############################################################# -C -C# -C# Dire Wolf acts as a virtual TNC and can communicate with -C# client applications by different protocols: -C# -C# - the "AGW TCPIP Socket Interface" - default port 8000 -C# - KISS protocol over TCP socket - default port 8001 -W# - KISS TNC via serial port -L# - KISS TNC via pseudo terminal (-p command line option) -C# -C -CAGWPORT 8000 -CKISSPORT 8001 -C -W# -W# Some applications are designed to operate with only a physical -W# TNC attached to a serial port. For these, we provide a virtual serial -W# port that appears to be connected to a TNC. -W# -W# Take a look at the User Guide for instructions to set up -W# two virtual serial ports named COM3 and COM4 connected by -W# a null modem. -W# -W# Using the configuration described, Dire Wolf will connect to -W# COM3 and the client application will use COM4. -W# -W# Uncomment following line to use this feature. -W -W#NULLMODEM COM3 -W -W -C# -C# It is sometimes possible to recover frames with a bad FCS. -C# This applies to all channels. -C# -C# 0 [NONE] - Don't try to repair. -C# 1 [SINGLE] - Attempt to fix single bit error. (default) -C# 2 [DOUBLE] - Also attempt to fix two adjacent bits. -C# ... see User Guide for more values and in-depth discussion. -C# -C -C#FIX_BITS 0 -C -C# -C############################################################# -C# # -C# BEACONING PROPERTIES # -C# # -C############################################################# -C -C -C# -C# Beaconing is configured with these two commands: -C# -C# PBEACON - for a position report (usually yourself) -C# OBEACON - for an object report (usually some other entity) -C# -C# Each has a series of keywords and values for options. -C# See User Guide for details. -C# -C# Example: -C# -C# This results in a broadcast once every 10 minutes. -C# Every half hour, it can travel via two digipeater hops. -C# The others are kept local. -C# -C -C#PBEACON delay=1 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" via=WIDE1-1,WIDE2-1 -C#PBEACON delay=11 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" -C#PBEACON delay=21 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" -C -C -C# With UTM coordinates instead of latitude and longitude. -C -C#PBEACON delay=1 every=10 overlay=S symbol="digi" zone=19T easting=307477 northing=4720178 -C -C -C# -C# When the destination field is set to "SPEECH" the information part is -C# converted to speech rather than transmitted as a data frame. -C# -C -C#CBEACON dest="SPEECH" info="Club meeting tonight at 7 pm." -C -C# Similar for Morse code. If SSID is specified, it is multiplied -C# by 2 to get speed in words per minute (WPM). -C -C#CBEACON dest="MORSE-6" info="de MYCALL" -C -C -C# -C# Modify for your particular situation before removing -C# the # comment character from the beginning of appropriate lines above. -C# -C -C -C############################################################# -C# # -C# DIGIPEATER PROPERTIES # -C# # -C############################################################# -C -C# -C# For most common situations, use something like this by removing -C# the "#" from the beginning of the line below. -C# -C -C#DIGIPEAT 0 0 ^WIDE[3-7]-[1-7]$|^TEST$ ^WIDE[12]-[12]$ TRACE -C -C# See User Guide for more explanation of what this means and how -C# it can be customized for your particular needs. -C -C# Filtering can be used to limit was is digipeated. -C# For example, only weather weather reports, received on channel 0, -C# will be retransmitted on channel 1. -C# -C -C#FILTER 0 1 t/wn -C -C -C############################################################# -C# # -C# INTERNET GATEWAY # -C# # -C############################################################# -C -C# First you need to specify the name of a Tier 2 server. -C# The current preferred way is to use one of these regional rotate addresses: -C -C# noam.aprs2.net - for North America -C# soam.aprs2.net - for South America -C# euro.aprs2.net - for Europe and Africa -C# asia.aprs2.net - for Asia -C# aunz.aprs2.net - for Oceania -C -C#IGSERVER noam.aprs2.net -C -C# You also need to specify your login name and passcode. -C# Contact the author if you can't figure out how to generate the passcode. -C -C#IGLOGIN WB2OSZ-5 123456 -C -C# That's all you need for a receive only IGate which relays -C# messages from the local radio channel to the global servers. -C -C# Some might want to send an IGate client position directly to a server -C# without sending it over the air and relying on someone else to -C# forward it to an IGate server. This is done by using sendto=IG rather -C# than a radio channel number. Overlay R for receive only, T for two way. -C -C#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=R lat=42^37.14N long=071^20.83W -C#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=T lat=42^37.14N long=071^20.83W -C -C -C# To relay messages from the Internet to radio, you need to add -C# one more option with the transmit channel number and a VIA path. -C -C#IGTXVIA 0 WIDE1-1 -C -C# You might want to apply a filter for what packets will be obtained from the server. -C# Read about filters here: http://www.aprs-is.net/javaprsfilter.aspx -C# Example, positions and objects within 50 km of my location: -C -C#IGFILTER m/50 -C -C# That is known as a server-side filter. It is processed by the IGate server. -C# You can also apply local filtering to limit what will be transmitted on the -C# RF side. For example, transmit only "messages" on channel 0 and weather -C# reports on channel 1. -C -C#FILTER IG 0 t/m -C#FILTER IG 1 t/wn -C -C# Finally, we don't want to flood the radio channel. -C# The IGate function will limit the number of packets transmitted -C# during 1 minute and 5 minute intervals. If a limit would -C# be exceeded, the packet is dropped and message is displayed in red. -C -CIGTXLIMIT 6 10 -C -C -C############################################################# -C# # -C# APRStt GATEWAY # -C# # -C############################################################# -C -C# -C# Dire Wolf can receive DTMF (commonly known as Touch Tone) -C# messages and convert them to packet objects. -C# -C# See separate "APRStt-Implementation-Notes" document for details. -C# -C -C# -C# Sample gateway configuration based on: -C# -C# http://www.aprs.org/aprstt/aprstt-coding24.txt -C# http://www.aprs.org/aprs-jamboree-2013.html -C# -C -C# Define specific points. -C -CTTPOINT B01 37^55.37N 81^7.86W -CTTPOINT B7495088 42.605237 -71.34456 -CTTPOINT B934 42.605237 -71.34456 -C -CTTPOINT B901 42.661279 -71.364452 -CTTPOINT B902 42.660411 -71.364419 -CTTPOINT B903 42.659046 -71.364452 -CTTPOINT B904 42.657578 -71.364602 -C -C -C# For location at given bearing and distance from starting point. -C -CTTVECTOR B5bbbddd 37^55.37N 81^7.86W 0.01 mi -C -C# For location specified by x, y coordinates. -C -CTTGRID Byyyxxx 37^50.00N 81^00.00W 37^59.99N 81^09.99W -C -C# UTM location for Lowell-Dracut-Tyngsborough State Forest. -C -CTTUTM B6xxxyyy 19T 10 300000 4720000 -C -C -C -C# Location for the corral. -C -CTTCORRAL 37^55.50N 81^7.00W 0^0.02N -C -C# Compact messages - Fixed locations xx and object yyy where -C# Object numbers 100 - 199 = bicycle -C# Object numbers 200 - 299 = fire truck -C# Others = dog -C -CTTMACRO xx1yy B9xx*AB166*AA2B4C5B3B0A1yy -CTTMACRO xx2yy B9xx*AB170*AA3C4C7C3B0A2yy -CTTMACRO xxyyy B9xx*AB180*AA3A6C4A0Ayyy -C -CTTMACRO z Cz -C -C# Receive on channel 0, Transmit object reports on channel 1 with optional via path. -C# You probably want to put in a transmit delay on the APRStt channel so it -C# it doesn't start sending a response before the user releases PTT. -C# This is in 10 ms units so 100 means 1000 ms = 1 second. -C -C#TTOBJ 0 1 WIDE1-1 -C#CHANNEL 0 -C#DWAIT 100 -C -C# Advertise gateway position with beacon. -C -C# OBEACON DELAY=0:15 EVERY=10:00 VIA=WIDE1-1 OBJNAME=WB2OSZ-tt SYMBOL=APRStt LAT=42^37.14N LONG=71^20.83W COMMENT="APRStt Gateway" -C -C -C# Sample speech responses. -C# Default is Morse code "R" for received OK and "?" for all errors. -C -C#TTERR OK SPEECH Message Received. -C#TTERR D_MSG SPEECH D not implemented. -C#TTERR INTERNAL SPEECH Internal error. -C#TTERR MACRO_NOMATCH SPEECH No definition for digit sequence. -C#TTERR BAD_CHECKSUM SPEECH Bad checksum on call. -C#TTERR INVALID_CALL SPEECH Invalid callsign. -C#TTERR INVALID_OBJNAME SPEECH Invalid object name. -C#TTERR INVALID_SYMBOL SPEECH Invalid symbol. -C#TTERR INVALID_LOC SPEECH Invalid location. -C#TTERR NO_CALL SPEECH No call or object name. -C#TTERR SATSQ SPEECH Satellite square must be 4 digits. -C#TTERR SUFFIX_NO_CALL SPEECH Send full call before using suffix. -C \ No newline at end of file diff --git a/man/CMakeLists.txt b/man/CMakeLists.txt new file mode 100644 index 0000000..071db62 --- /dev/null +++ b/man/CMakeLists.txt @@ -0,0 +1,13 @@ +if(NOT (WIN32 OR CYGWIN)) + install(FILES "${CUSTOM_MAN_DIR}/aclients.1" DESTINATION ${INSTALL_MAN_DIR}) + install(FILES "${CUSTOM_MAN_DIR}/atest.1" DESTINATION ${INSTALL_MAN_DIR}) + install(FILES "${CUSTOM_MAN_DIR}/decode_aprs.1" DESTINATION ${INSTALL_MAN_DIR}) + install(FILES "${CUSTOM_MAN_DIR}/direwolf.1" DESTINATION ${INSTALL_MAN_DIR}) + install(FILES "${CUSTOM_MAN_DIR}/gen_packets.1" DESTINATION ${INSTALL_MAN_DIR}) + install(FILES "${CUSTOM_MAN_DIR}/kissutil.1" DESTINATION ${INSTALL_MAN_DIR}) + install(FILES "${CUSTOM_MAN_DIR}/ll2utm.1" DESTINATION ${INSTALL_MAN_DIR}) + install(FILES "${CUSTOM_MAN_DIR}/log2gpx.1" DESTINATION ${INSTALL_MAN_DIR}) + install(FILES "${CUSTOM_MAN_DIR}/text2tt.1" DESTINATION ${INSTALL_MAN_DIR}) + install(FILES "${CUSTOM_MAN_DIR}/tt2text.1" DESTINATION ${INSTALL_MAN_DIR}) + install(FILES "${CUSTOM_MAN_DIR}/utm2ll.1" DESTINATION ${INSTALL_MAN_DIR}) +endif(NOT (WIN32 OR CYGWIN)) diff --git a/man1/aclients.1 b/man/aclients.1 similarity index 100% rename from man1/aclients.1 rename to man/aclients.1 diff --git a/man1/atest.1 b/man/atest.1 similarity index 54% rename from man1/atest.1 rename to man/atest.1 index 9470496..a1b554c 100644 --- a/man1/atest.1 +++ b/man/atest.1 @@ -21,17 +21,46 @@ atest \- Decode AX.25 frames from an audio file. .SH OPTIONS + .TP -.BI "-B " "n" -Bits / second for data. Proper modem selected for 300, 1200, 9600. -300 baud uses 1600/1800 Hz AFSK. -1200 (default) baud uses 1200/2200 Hz AFSK. -9600 baud uses K9NG/G2RUH standard. +.BI "-B " "n" +Data rate in bits/sec. Standard values are 300, 1200, 2400, 4800, 9600. +.PD 0 +.RS +.RS +300 bps defaults to AFSK tones of 1600 & 1800. +.P +1200 bps uses AFSK tones of 1200 & 2200. +.P +2400 bps uses QPSK based on V.26 standard. +.P +4800 bps uses 8PSK based on V.27 standard. +.P +9600 bps and up uses K9NG/G3RUH standard. +.RE +.RE +.PD + +.TP +.BI "-g " +Force G3RUH modem regardless of data rate. + +.TP +.BI "-j " +2400 bps QPSK compatible with Dire Wolf <= 1.5. + +.TP +.BI "-J " +2400 bps QPSK compatible with MFJ-2400. .TP .BI "-D " "n" Divide audio sample rate by n. +.TP +.BI "-h " +Print frame contents as hexadecimal bytes. + .TP .BI "-F " "n" Amount of effort to try fixing frames with an invalid CRC. @@ -39,9 +68,17 @@ Amount of effort to try fixing frames with an invalid CRC. 1 = Try to fix only a single bit. more = Try modifying more bits to get a good CRC. +.TP +.BI "-L " +Error if Less than this number decoded. + +.TP +.BI "-G " +Error if Greater than this number decoded. + .TP .BI "-P " "m" -Select the demodulator type such as A, B, C, D (default for 300 baud), E (default for 1200 baud), F, A+, B+, C+, D+, E+, F+. +Select the demodulator type such as D (default for 300 bps), E+ (default for 1200 bps), PQRS for 2400 bps, etc. @@ -72,20 +109,20 @@ This generates and decodes 3 test files with 1200, 300, and 9600 bits per second .PD 0 .B atest 02_Track_2.wav .P -.B atest -P C+ 02_Track_2.wav +.B atest -P E- 02_Track_2.wav .P .B atest -F 1 02_Track_2.wav .P -.B atest -P C+ -F 1 02_Track_2.wav +.B atest -P E- -F 1 02_Track_2.wav .PD .P .RS -Try different combinations of options to find the best decoding performance. +Try different combinations of options to compare decoding performance. .RE .P .SH SEE ALSO More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. -Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll +Applications in this package: aclients, atest, cm108, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll diff --git a/man1/decode_aprs.1 b/man/decode_aprs.1 similarity index 100% rename from man1/decode_aprs.1 rename to man/decode_aprs.1 diff --git a/man1/direwolf.1 b/man/direwolf.1 similarity index 73% rename from man1/direwolf.1 rename to man/direwolf.1 index 700790d..76bc195 100644 --- a/man1/direwolf.1 +++ b/man/direwolf.1 @@ -48,23 +48,47 @@ Audio sample size for first channel. 8 or 16. Default 16. .TP .BI "-B " "n" -Data rate in bits/sec for first channel. Standard values are 300, 1200, 9600. +Data rate in bits/sec for first channel. Standard values are 300, 1200, 2400, 4800, 9600. .PD 0 -.RS .RS -If < 600, tones are set to 1600 & 1800. +.RS +300 bps defaults to AFSK tones of 1600 & 1800. .P -If > 2400, K9NG/G3RUH scrambling is used. +1200 bps uses AFSK tones of 1200 & 2200. .P -Otherwise, AFSK tones are set to 1200 & 2200. +2400 bps uses QPSK based on V.26 standard. +.P +4800 bps uses 8PSK based on V.27 standard. +.P +9600 bps and up uses K9NG/G3RUH standard. +.P +AIS for ship Automatic Identification System. +.P +EAS for Emergency Alert System (EAS) Specific Area Message Encoding (SAME). .RE .RE .PD +.TP +.BI "-g " +Force G3RUH modem regardless of data rate. + +.TP +.BI "-j " +2400 bps QPSK compatible with Dire Wolf <= 1.5. + +.TP +.BI "-J " +2400 bps QPSK compatible with MFJ-2400. + .TP .BI "-D " "n" Divide audio sample by n for first channel. +.TP +.BI "-X " "n" +1 to enable FX.25 transmit. + .TP .BI "-d " "x" Debug options. Specify one or more of the following in place of x. @@ -92,6 +116,12 @@ o = Output controls such as PTT and DCD. i = IGate .P h = Hamlib verbose level. Repeat for more. +.P +m = Monitor heard station list. +.P +f = Packet filtering. +.P +x = FX.25 increase verbose level. .RE .RE .PD @@ -111,7 +141,8 @@ d = Decoding of APRS packets. .TP .BI "-t " "n" -Text colors. 1=normal, 0=disabled. +Text colors. 0=disabled. 1=default. 2,3,4,... alternatives. Use 9 to test compatibility with your terminal. + .TP .B "-p " @@ -133,6 +164,13 @@ Print Symbol tables and exit. .BI "-a " "n" Report audio device statistics each n seconds. +.TP +.BI "-T " "fmt" +Time stamp format for sent and received frames. + +.TP +.BI "-e " "ber" +Receive Bit Error Rate (BER), e.g. 1e-5 .SH EXAMPLES gqrx (2.3 and later) has the ability to send streaming audio through a UDP socket to another application for further processing. @@ -152,5 +190,5 @@ rtl_fm \-f 144.39M \-o 4 \- | direwolf \-n 1 \-r 24000 \-b 16 \- .SH SEE ALSO More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. -Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll +Applications in this package: aclients, atest, cm108, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll diff --git a/man1/gen_packets.1 b/man/gen_packets.1 similarity index 77% rename from man1/gen_packets.1 rename to man/gen_packets.1 index 39f8925..773a88e 100644 --- a/man1/gen_packets.1 +++ b/man/gen_packets.1 @@ -32,12 +32,36 @@ Signal amplitude in range of 0-200%. Default 50. Note that 100% is corresponds Bits / second for data. Default is 1200. .TP -.BI "-B " "n" -Bits / second for data. Proper modem selected for 300, 1200, 9600. +.BI "-B " "n" +Data rate in bits/sec for first channel. Standard values are 300, 1200, 2400, 4800, 9600. +.PD 0 +.RS +.RS +300 bps defaults to AFSK tones of 1600 & 1800. +.P +1200 bps uses AFSK tones of 1200 & 2200. +.P +2400 bps uses QPSK based on V.26 standard. +.P +4800 bps uses 8PSK based on V.27 standard. +.P +9600 bps and up uses K9NG/G3RUH standard. +.RE +.RE +.PD .TP -.BI "-g" -Scrambled baseband rather than AFSK. +.BI "-g " +Force G3RUH modem regardless of data rate. + +.TP +.BI "-j " +2400 bps QPSK compatible with Dire Wolf <= 1.5. + +.TP +.BI "-J " +2400 bps QPSK compatible with MFJ-2400. + .TP .BI "-m " "n" @@ -112,5 +136,5 @@ Read message from stdin and put quarter volume sound into the file x.wav. Decod .SH SEE ALSO More detailed information is in the pdf files in /usr/local/share/doc/direwolf, or possibly /usr/share/doc/direwolf, depending on installation location. -Applications in this package: aclients, atest, decode_aprs, direwolf, gen_packets, ll2utm, log2gpx, text2tt, tt2text, utm2ll +Applications in this package: aclients, atest, cm108, decode_aprs, direwolf, gen_packets, kissutil, ll2utm, log2gpx, text2tt, tt2text, utm2ll diff --git a/man1/kissutil.1 b/man/kissutil.1 similarity index 100% rename from man1/kissutil.1 rename to man/kissutil.1 diff --git a/man1/ll2utm.1 b/man/ll2utm.1 similarity index 100% rename from man1/ll2utm.1 rename to man/ll2utm.1 diff --git a/man1/log2gpx.1 b/man/log2gpx.1 similarity index 100% rename from man1/log2gpx.1 rename to man/log2gpx.1 diff --git a/man1/text2tt.1 b/man/text2tt.1 similarity index 100% rename from man1/text2tt.1 rename to man/text2tt.1 diff --git a/man1/tt2text.1 b/man/tt2text.1 similarity index 100% rename from man1/tt2text.1 rename to man/tt2text.1 diff --git a/man1/utm2ll.1 b/man/utm2ll.1 similarity index 100% rename from man1/utm2ll.1 rename to man/utm2ll.1 diff --git a/misc/strsep.c b/misc/strsep.c deleted file mode 100644 index 7d764d0..0000000 --- a/misc/strsep.c +++ /dev/null @@ -1,22 +0,0 @@ -/* BSD strsep function */ - -/* Copyright 2002, Red Hat Inc. */ - -/* undef STRICT_ANSI so that strsep prototype will be defined */ -#undef __STRICT_ANSI__ -#include -//#include <_ansi.h> -//#include - -#define _DEFUN(name,arglist,args) name(args) -#define _AND , - -extern char *__strtok_r (char *, const char *, char **, int); - -char * -_DEFUN (strsep, (source_ptr, delim), - register char **source_ptr _AND - register const char *delim) -{ - return __strtok_r (*source_ptr, delim, source_ptr, 0); -} diff --git a/rdq.c b/rdq.c deleted file mode 100644 index 6a47cb8..0000000 --- a/rdq.c +++ /dev/null @@ -1,372 +0,0 @@ -// -// This file is part of Dire Wolf, an amateur radio packet TNC. -// -// Copyright (C) 2011, 2012, 2015 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: rdq.c - * - * Purpose: Retry later decode queue for frames with bad FCS. - * - * Description: - * - *---------------------------------------------------------------*/ - -#include "direwolf.h" - -#include -#include -#include -#include -#include - -#include "ax25_pad.h" -#include "textcolor.h" -#include "audio.h" -#include "rdq.h" -#include "dedupe.h" - - - -static rrbb_t queue_head = NULL; /* Head of linked list for queue. */ -static int rdq_len = 0; -#define RDQ_UNDERRUN_THRESHOLD 30 /* A warning will be emitted if there are still this number of packets to decode in the queue and we try to add another one */ - - - -static dw_mutex_t rdq_mutex; /* Critical section for updating queues. */ - - -#if __WIN32__ - -static HANDLE wake_up_event; /* Notify try decode again thread when queue not empty. */ - -#else - -static pthread_cond_t wake_up_cond; /* Notify try decode again thread when queue not empty. */ - -static dw_mutex_t wake_up_mutex; /* Required by cond_wait. */ - -#endif - - -/*------------------------------------------------------------------- - * - * Name: rdq_init - * - * Purpose: Initialize the receive decode again queue. - * - * Inputs: None. Only single queue for all channels. - * - * Outputs: - * - * Description: Initialize the queue to be empty and set up other - * mechanisms for sharing it between different threads. - * - *--------------------------------------------------------------------*/ - - -void rdq_init (void) -{ - //int c, p; -#if __WIN32__ -#else - int err; -#endif - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("rdq_init ( )\n"); - dw_printf ("rdq_init: pthread_mutex_init...\n"); -#endif - - dw_mutex_init (&rdq_mutex); - -#if __WIN32__ -#else - dw_mutex_init (&wake_up_mutex); -#endif - - - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("rdq_init: pthread_cond_init...\n"); -#endif - -#if __WIN32__ - - wake_up_event = CreateEvent (NULL, 0, 0, NULL); - - if (wake_up_event == NULL) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("rdq_init: pthread_cond_init: can't create decode wake up event"); - exit (1); - } - -#else - err = pthread_cond_init (&wake_up_cond, NULL); - - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("rdq_init: pthread_cond_init returns %d\n", err); -#endif - - - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("rdq_init: pthread_cond_init err=%d", err); - perror (""); - exit (1); - } -#endif - - -} /* end rdq_init */ - - -/*------------------------------------------------------------------- - * - * Name: rdq_append - * - * Purpose: Add a packet to the end of the queue. - * - * Inputs: pp - Address of raw received bit buffer. - * Caller should NOT make any references to - * it after this point because it could - * be deleted at any time. - * - * Outputs: - * - * Description: Add buffer to end of linked list. - * Signal the decode thread if the queue was formerly empty. - * - *--------------------------------------------------------------------*/ - -void rdq_append (rrbb_t rrbb) -{ - //int was_empty; - rrbb_t plast; - rrbb_t pnext; -#ifndef __WIN32__ - int err; -#endif - - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("rdq_append (rrbb=%p)\n", rrbb); - dw_printf ("rdq_append: enter critical section\n"); -#endif - - - dw_mutex_lock (&rdq_mutex); - - //was_empty = 1; - //if (queue_head != NULL) { - //was_empty = 0; - //} - if (queue_head == NULL) { - queue_head = rrbb; - } - else { - plast = queue_head; - while ((pnext = rrbb_get_nextp(plast)) != NULL) { - plast = pnext; - } - rrbb_set_nextp (plast, rrbb); - } - rdq_len++; - if (rdq_len > RDQ_UNDERRUN_THRESHOLD) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Too many packets to decode (%d) in the queue, decrease the FIX_BITS value\n", rdq_len); - } - - dw_mutex_unlock (&rdq_mutex); - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("rdq_append: left critical section\n"); - dw_printf ("rdq_append (): about to wake up retry decode thread.\n"); -#endif - -#if __WIN32__ - SetEvent (wake_up_event); -#else - dw_mutex_lock (&wake_up_mutex); - - err = pthread_cond_signal (&wake_up_cond); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("rdq_append: pthread_cond_signal err=%d", err); - perror (""); - exit (1); - } - - dw_mutex_unlock (&wake_up_mutex); -#endif - -} - - -/*------------------------------------------------------------------- - * - * Name: rdq_wait_while_empty - * - * Purpose: Sleep while the queue is empty rather than - * polling periodically. - * - * Inputs: None. - * - *--------------------------------------------------------------------*/ - - -void rdq_wait_while_empty (void) -{ - int is_empty; -#ifndef __WIN32__ - int err; -#endif - - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("rdq_wait_while_empty () : enter critical section\n"); -#endif - - dw_mutex_lock (&rdq_mutex); - -#if DEBUG - //text_color_set(DW_COLOR_DEBUG); - //dw_printf ("rdq_wait_while_empty (): after pthread_mutex_lock\n"); -#endif - is_empty = 1; - if (queue_head != NULL) - is_empty = 0; - - dw_mutex_unlock (&rdq_mutex); - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("rdq_wait_while_empty () : left critical section\n"); -#endif - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("rdq_wait_while_empty (): is_empty = %d\n", is_empty); -#endif - - if (is_empty) { -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("rdq_wait_while_empty (): SLEEP - about to call cond wait\n"); -#endif - - -#if __WIN32__ - WaitForSingleObject (wake_up_event, INFINITE); - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("rdq_wait_while_empty (): returned from wait\n"); -#endif - -#else - dw_mutex_lock (&wake_up_mutex); - - err = pthread_cond_wait (&wake_up_cond, &wake_up_mutex); - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("rdq_wait_while_empty (): WOKE UP - returned from cond wait, err = %d\n", err); -#endif - - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("rdq_wait_while_empty: pthread_cond_wait err=%d", err); - perror (""); - exit (1); - } - - dw_mutex_unlock (&wake_up_mutex); - -#endif - } - - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("rdq_wait_while_empty () returns (%d buffers remaining)\n", rdq_len); -#endif - -} - - -/*------------------------------------------------------------------- - * - * Name: rdq_remove - * - * Purpose: Remove raw bit buffer from the head of the queue. - * - * Inputs: none - * - * Returns: Pointer to rrbb object. - * Caller should destroy it with rrbb_delete when finished with it. - * - *--------------------------------------------------------------------*/ - -rrbb_t rdq_remove (void) -{ - - rrbb_t result_p; - - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("rdq_remove() enter critical section\n"); -#endif - - dw_mutex_lock (&rdq_mutex); - - rdq_len--; -#if DEBUG - dw_printf ("-rdq_len: %d\n", rdq_len); -#endif - if (queue_head == NULL) { - result_p = NULL; - } - else { - - result_p = queue_head; - queue_head = rrbb_get_nextp(result_p); - rrbb_set_nextp (result_p, NULL); - } - - dw_mutex_unlock (&rdq_mutex); - -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf ("rdq_remove() leave critical section, returns %p\n", result_p); -#endif - return (result_p); -} - -/* end rdq.c */ diff --git a/rdq.h b/rdq.h deleted file mode 100644 index b0e430c..0000000 --- a/rdq.h +++ /dev/null @@ -1,28 +0,0 @@ - -/*------------------------------------------------------------------ - * - * Module: rdq.h - * - * Purpose: Retry decode queue - Hold raw received frames with errors - * for retrying the decoding later. - * - *---------------------------------------------------------------*/ - -#ifndef RDQ_H -#define RDQ_H 1 - -#include "rrbb.h" -//#include "audio.h" - -void rdq_init (void); - -void rdq_append (rrbb_t rrbb); - -void rdq_wait_while_empty (void); - -rrbb_t rdq_remove (void); - - -#endif - -/* end rdq.h */ diff --git a/rpm/direwolf.spec b/rpm/direwolf.spec new file mode 100644 index 0000000..d37bf5c --- /dev/null +++ b/rpm/direwolf.spec @@ -0,0 +1,175 @@ +%global shorttag 0d2c175c +Name: direwolf +Version: 1.6 +Release: 0.4.20200419git%{shorttag}%{?dist} +Summary: Sound Card-based AX.25 TNC + +License: GPLv2+ +URL: https://github.com/wb2osz/direwolf/ +Source0: https://github.com/wb2osz/direwolf/archive/%{version}/%{name}-%{version}.tar.gz +#Source0: https://github.com/wb2osz/direwolf/archive/%{version}/%{name}-%{shorttag}.tar.gz + +BuildRequires: gcc gcc-c++ +BuildRequires: cmake +BuildRequires: glibc-devel +BuildRequires: alsa-lib-devel +BuildRequires: gpsd-devel +BuildRequires: hamlib-devel +BuildRequires: systemd systemd-devel +Requires: ax25-tools ax25-apps +Requires(pre): shadow-utils + + +%description +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 an APRS GPS Tracker, Digipeater, Internet Gateway +(IGate), APRStt gateway. It can also be used as a virtual TNC for +other applications such as APRSIS32, UI-View32, Xastir, APRS-TW, YAAC, +UISS, Linux AX25, SARTrack, Winlink Express, BPQ32, Outpost PM, and many +others. + + +%prep +%autosetup -n %{name}-%{version} + + +%build +%cmake -DUNITTEST=1 -DENABLE_GENERIC=1 . + + +%check +ctest -V %{?_smp_mflags} + + +%install +%make_install + +# Install service file +mkdir -p ${RPM_BUILD_ROOT}%{_unitdir} +cp %{_builddir}/%{buildsubdir}/systemd/%{name}.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}.service + +# Install service config file +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/sysconfig +cp %{_builddir}/%{buildsubdir}/systemd/%{name}.sysconfig ${RPM_BUILD_ROOT}%{_sysconfdir}/sysconfig/%{name} + +# Install logrotate config file +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d +cp %{_builddir}/%{buildsubdir}/systemd/%{name}.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name} + +# copy config file +cp ${RPM_BUILD_ROOT}%{_pkgdocdir}/conf/%{name}.conf ${RPM_BUILD_ROOT}/%{_sysconfdir}/%{name}.conf + +# Make log directory +mkdir -m 0755 -p ${RPM_BUILD_ROOT}/var/log/%{name} + +# Move udev rules to system dir +mkdir -p ${RPM_BUILD_ROOT}%{_udevrulesdir} +mv ${RPM_BUILD_ROOT}%{_sysconfdir}/udev/rules.d/99-direwolf-cmedia.rules ${RPM_BUILD_ROOT}%{_udevrulesdir}/99-direwolf-cmedia.rules + +# Copy doc pngs +cp direwolf-block-diagram.png ${RPM_BUILD_ROOT}%{_pkgdocdir}/direwolf-block-diagram.png +cp tnc-test-cd-results.png ${RPM_BUILD_ROOT}%{_pkgdocdir}/tnc-test-cd-results.png + +# remove extraneous files +# This is not a desktop application, per the guidelines. Running it in a terminal +# does not make it a desktop application. +rm ${RPM_BUILD_ROOT}/usr/share/applications/direwolf.desktop +rm ${RPM_BUILD_ROOT}%{_datadir}/pixmaps/direwolf_icon.png +rm ${RPM_BUILD_ROOT}%{_pkgdocdir}/CHANGES.md +rm ${RPM_BUILD_ROOT}%{_pkgdocdir}/LICENSE +rm ${RPM_BUILD_ROOT}%{_pkgdocdir}/README.md + +# remove Windows external library directories +rm -r ${RPM_BUILD_ROOT}%{_pkgdocdir}/external + +# Move Telemetry Toolkit sample scripts into docs +mkdir -p ${RPM_BUILD_ROOT}%{_pkgdocdir}/telem/ +mv ${RPM_BUILD_ROOT}%{_bindir}/telem* ${RPM_BUILD_ROOT}%{_pkgdocdir}/telem/ +chmod 0644 ${RPM_BUILD_ROOT}%{_pkgdocdir}/telem/* + + +%package -n %{name}-doc +Summary: Documentation for Dire Wolf +BuildArch: noarch +Requires: %{name} = %{version}-%{release} + +%description -n %{name}-doc +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 an APRS GPS Tracker, Digipeater, Internet Gateway +(IGate), APRStt gateway. It can also be used as a virtual TNC for +other applications such as APRSIS32, UI-View32, Xastir, APRS-TW, YAAC, +UISS, Linux AX25, SARTrack, RMS Express, BPQ32, Outpost PM, and many +others. + + +%files +%license LICENSE +%{_udevrulesdir}/99-direwolf-cmedia.rules +%{_bindir}/* +%{_mandir}/man1/* +%{_datadir}/%{name}/* +%dir %{_pkgdocdir} +%{_pkgdocdir}/conf/* +%{_pkgdocdir}/scripts/* +%{_pkgdocdir}/telem/* +%{_unitdir}/%{name}.service +%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/sysconfig/%{name} +%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/%{name}.conf +%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/logrotate.d/%{name} +%dir %attr(0755, %{name}, %{name}) /var/log/%{name} + +%files -n %{name}-doc +%{_pkgdocdir}/*.pdf +%{_pkgdocdir}/*.png + +# At install, create a user in group audio (so can open sound card device files) +# and in group dialout (so can open serial device files) +%pre +getent group direwolf >/dev/null || groupadd -r direwolf +getent passwd direwolf >/dev/null || \ + useradd -r -g audio -G audio,dialout -d %{_datadir}/%{name} -s /sbin/nologin \ + -c "Direwolf Sound Card-based AX.25 TNC" direwolf +exit 0 + + +%changelog +* Mon Apr 20 2020 Matt Domsch - 1.6-0.3 +- drop unneeded BR libax25-devel + +* Mon Apr 20 2020 Matt Domsch - 1.6-0.2 +- write stdout/err to /var/log/direwolf, logrotate 30 days. +- run ctest +- remove CPU instruction tests, leave architecture choice up to the distro + +* Sun Apr 19 2020 Matt Domsch - 1.6-0.1 +- upstream 1.6 prerelease +- drop obsolete patches, use cmake +- add systemd startup, direwolf user + +* Tue Mar 31 2020 Richard Shaw - 1.5-6 +- Rebuild for hamlib 4. + +* Thu Feb 20 2020 Matt Domsch - 1.5-5 +- Remove unneeded dependency on python2-devel (#1805225) + +* Tue Jan 28 2020 Fedora Release Engineering - 1.5-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild + +* Wed Jul 24 2019 Fedora Release Engineering - 1.5-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild + +* Wed Jul 03 2019 Björn Esser - 1.5-2 +- Rebuild (gpsd) + +* Sun Feb 17 2019 Matt Domsch - 1.5-1 +- Upgrade to released version 1.5 +- Apply upstream patch for newer gpsd API + +* Thu Jan 31 2019 Fedora Release Engineering - 1.5-0.2.beta4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild + +* Mon Aug 27 2018 Matt Domsch - 1.5-0.1.beta4 +- Fedora Packaging Guidelines, based on spec by David Ranch + Moved Telemetry Toolkit examples into examples/ docs. diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt new file mode 100644 index 0000000..886e5b1 --- /dev/null +++ b/scripts/CMakeLists.txt @@ -0,0 +1,6 @@ + +if(NOT (WIN32 OR CYGWIN)) + install(PROGRAMS "${CUSTOM_SCRIPTS_DIR}/dwespeak.sh" DESTINATION ${INSTALL_BIN_DIR}) + install(PROGRAMS "${CUSTOM_SCRIPTS_DIR}/dw-start.sh" DESTINATION ${INSTALL_SCRIPTS_DIR}) + add_subdirectory(telemetry-toolkit) +endif() diff --git a/dw-start.sh b/scripts/dw-start.sh similarity index 91% rename from dw-start.sh rename to scripts/dw-start.sh index 360b9c7..e0b06cd 100755 --- a/dw-start.sh +++ b/scripts/dw-start.sh @@ -1,4 +1,11 @@ -#!/bin/bash +#!/usr/bin/env bash + +# Why not simply "#!/bin/bash" ? + +# For OpenBSD, the bash location is /usr/local/bin/bash. +# By using env here, bash is found based on the user's $PATH. +# I hope this does not break some other operating system. + # Run this from crontab periodically to start up # Dire Wolf automatically. @@ -37,7 +44,8 @@ RUNMODE=AUTO DIREWOLF="direwolf" -#Direwolf start up command :: two examples where example one is enabled + +#Direwolf start up command :: Uncomment only one of the examples. # # 1. For normal operation as TNC, digipeater, IGate, etc. # Print audio statistics each 100 seconds for troubleshooting. @@ -45,9 +53,14 @@ DIREWOLF="direwolf" DWCMD="$DIREWOLF -a 100" +# 2. FX.25 Forward Error Correction (FEC) will allow your signal to +# go farther under poor radio conditions. Add "-X 1" to the command line. + +#DWCMD="$DIREWOLF -a 100 -X 1" + #--------------------------------------------------------------- # -# 2. Alternative for running with SDR receiver. +# 3. Alternative for running with SDR receiver. # Piping one application into another makes it a little more complicated. # We need to use bash for the | to be recognized. diff --git a/dwespeak.bat b/scripts/dwespeak.bat similarity index 100% rename from dwespeak.bat rename to scripts/dwespeak.bat diff --git a/dwespeak.sh b/scripts/dwespeak.sh old mode 100644 new mode 100755 similarity index 100% rename from dwespeak.sh rename to scripts/dwespeak.sh diff --git a/scripts/telemetry-toolkit/CMakeLists.txt b/scripts/telemetry-toolkit/CMakeLists.txt new file mode 100644 index 0000000..46f8e61 --- /dev/null +++ b/scripts/telemetry-toolkit/CMakeLists.txt @@ -0,0 +1,13 @@ +install(PROGRAMS "${CUSTOM_TELEMETRY_DIR}/telem-balloon.pl" DESTINATION ${INSTALL_BIN_DIR}) +install(PROGRAMS "${CUSTOM_TELEMETRY_DIR}/telem-bits.pl" DESTINATION ${INSTALL_BIN_DIR}) +install(PROGRAMS "${CUSTOM_TELEMETRY_DIR}/telem-data.pl" DESTINATION ${INSTALL_BIN_DIR}) +install(PROGRAMS "${CUSTOM_TELEMETRY_DIR}/telem-data91.pl" DESTINATION ${INSTALL_BIN_DIR}) +install(PROGRAMS "${CUSTOM_TELEMETRY_DIR}/telem-eqns.pl" DESTINATION ${INSTALL_BIN_DIR}) +install(PROGRAMS "${CUSTOM_TELEMETRY_DIR}/telem-parm.pl" DESTINATION ${INSTALL_BIN_DIR}) +install(PROGRAMS "${CUSTOM_TELEMETRY_DIR}/telem-seq.sh" DESTINATION ${INSTALL_BIN_DIR}) +install(PROGRAMS "${CUSTOM_TELEMETRY_DIR}/telem-unit.pl" DESTINATION ${INSTALL_BIN_DIR}) +install(PROGRAMS "${CUSTOM_TELEMETRY_DIR}/telem-volts.py" DESTINATION ${INSTALL_BIN_DIR}) + +install(FILES "${CUSTOM_TELEMETRY_DIR}/telem-m0xer-3.txt" DESTINATION ${INSTALL_CONF_DIR}) +install(FILES "${CUSTOM_TELEMETRY_DIR}/telem-balloon.conf" DESTINATION ${INSTALL_CONF_DIR}) +install(FILES "${CUSTOM_TELEMETRY_DIR}/telem-volts.conf" DESTINATION ${INSTALL_CONF_DIR}) diff --git a/telemetry-toolkit/telem-balloon.conf b/scripts/telemetry-toolkit/telem-balloon.conf similarity index 100% rename from telemetry-toolkit/telem-balloon.conf rename to scripts/telemetry-toolkit/telem-balloon.conf diff --git a/telemetry-toolkit/telem-balloon.pl b/scripts/telemetry-toolkit/telem-balloon.pl similarity index 100% rename from telemetry-toolkit/telem-balloon.pl rename to scripts/telemetry-toolkit/telem-balloon.pl diff --git a/telemetry-toolkit/telem-bits.pl b/scripts/telemetry-toolkit/telem-bits.pl similarity index 100% rename from telemetry-toolkit/telem-bits.pl rename to scripts/telemetry-toolkit/telem-bits.pl diff --git a/telemetry-toolkit/telem-data.pl b/scripts/telemetry-toolkit/telem-data.pl similarity index 100% rename from telemetry-toolkit/telem-data.pl rename to scripts/telemetry-toolkit/telem-data.pl diff --git a/telemetry-toolkit/telem-data91.pl b/scripts/telemetry-toolkit/telem-data91.pl similarity index 100% rename from telemetry-toolkit/telem-data91.pl rename to scripts/telemetry-toolkit/telem-data91.pl diff --git a/telemetry-toolkit/telem-eqns.pl b/scripts/telemetry-toolkit/telem-eqns.pl similarity index 100% rename from telemetry-toolkit/telem-eqns.pl rename to scripts/telemetry-toolkit/telem-eqns.pl diff --git a/telemetry-toolkit/telem-m0xer-3.txt b/scripts/telemetry-toolkit/telem-m0xer-3.txt similarity index 100% rename from telemetry-toolkit/telem-m0xer-3.txt rename to scripts/telemetry-toolkit/telem-m0xer-3.txt diff --git a/telemetry-toolkit/telem-parm.pl b/scripts/telemetry-toolkit/telem-parm.pl similarity index 100% rename from telemetry-toolkit/telem-parm.pl rename to scripts/telemetry-toolkit/telem-parm.pl diff --git a/telemetry-toolkit/telem-seq.sh b/scripts/telemetry-toolkit/telem-seq.sh similarity index 100% rename from telemetry-toolkit/telem-seq.sh rename to scripts/telemetry-toolkit/telem-seq.sh diff --git a/telemetry-toolkit/telem-unit.pl b/scripts/telemetry-toolkit/telem-unit.pl similarity index 100% rename from telemetry-toolkit/telem-unit.pl rename to scripts/telemetry-toolkit/telem-unit.pl diff --git a/telemetry-toolkit/telem-volts.conf b/scripts/telemetry-toolkit/telem-volts.conf similarity index 100% rename from telemetry-toolkit/telem-volts.conf rename to scripts/telemetry-toolkit/telem-volts.conf diff --git a/telemetry-toolkit/telem-volts.py b/scripts/telemetry-toolkit/telem-volts.py similarity index 100% rename from telemetry-toolkit/telem-volts.py rename to scripts/telemetry-toolkit/telem-volts.py diff --git a/search_sdks.sh b/search_sdks.sh deleted file mode 100755 index 5d9bc32..0000000 --- a/search_sdks.sh +++ /dev/null @@ -1,109 +0,0 @@ -#!/bin/bash -# -# This file is part of Dire Wolf, an amateur radio packet TNC. -# -# Bash script to search for SDKs on various MacOSX versions. -# -# Copyright (C) 2015 Robert Stiles -# -# 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 . -# - -FILENAME="./use_this_sdk" -selected_sdk="" -valid_flag=0 -system_sdk="" - -if [ -f $FILENAME ]; then - selected_sdk=`cat $FILENAME` - if [ -d $selected_sdk ]; then - valid_flag=1 - fi -fi - -if [ $valid_flag -eq "0" ]; then - echo " " >&2 - echo " " >&2 - echo "Searching for SDKs.... (Wait for results)" >&2 - echo " " >&2 - echo "Enter the number and press Enter/Return Key" >&2 - echo " " >&2 - echo " " >&2 - - prompt="Select SDK to use:" - - loc1=( $(find /Applications/Xcode.app -type l -name "MacOSX10.*.sdk") ) - loc2=( $(find /Developer/SDKs -maxdepth 1 -type d -name "MacOSX10.*.sdk") ) - - options=("${loc1[@]}" "${loc2[@]}") - - if [ "${#options[@]}" -lt "2" ]; then - echo "$options" - fi - - PS3="$prompt " - select opt in "${options[@]}" "Do not use any SDK" ; do - if (( REPLY == 1 + ${#options[@]} )) ; then - echo " " - break - elif (( REPLY > 0 && REPLY <= ${#options[@]} )) ; then - selected_sdk="$opt" - break - fi - done - - if [ ! -z "$selected_sdk" ]; then - echo "$selected_sdk" > $FILENAME - else - echo " " > $FILENAME - fi -fi - -if [ ! -z "$selected_sdk" ]; then - temp_str="$selected_sdk" - min_str="" - flag=true - - # Search for the last MacOSX in the string. - while [ "${#temp_str}" -gt 4 ]; do - temp_str="${temp_str#*MacOSX}" - temp_str="${temp_str%%.sdk}" - min_str="$temp_str" - temp_str="${temp_str:1}" - done - - # Remove the "u" if 10.4u Universal SDK is used. - min_str="${min_str%%u}" - - system_sdk="-isystem ${selected_sdk} -mmacosx-version-min=${min_str}" -else - system_sdk=" " -fi - -echo " " >&2 -echo "*******************************************************************" >&2 - -if [ -z "${system_sdk}" ]; then - echo "SDK Selected: None" >&2 -else - echo "SDK Selected: ${system_sdk}" >&2 -fi - -echo "To change SDK version execute 'make clean' followed by 'make'." >&2 -echo "*******************************************************************" >&2 -echo " " >&2 - -echo ${system_sdk} - - diff --git a/sock.h b/sock.h deleted file mode 100644 index 400ea8a..0000000 --- a/sock.h +++ /dev/null @@ -1,19 +0,0 @@ - -/* sock.h - Socket helper functions. */ - -#ifndef SOCK_H -#define SOCK_H 1 - -#define SOCK_IPADDR_LEN 48 // Size of string to hold IPv4 or IPv6 address. - // I think 40 would be adequate but we'll make - // it a little larger just to be safe. - // Use INET6_ADDRSTRLEN (from netinet/in.h) instead? - -int sock_init (void); - -int sock_connect (char *hostname, char *port, char *description, int allow_ipv6, int debug, char *ipaddr_str); - /* ipaddr_str needs to be at least SOCK_IPADDR_LEN bytes */ - -char *sock_ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t StringBufSize); - -#endif \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..46d3ac7 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,487 @@ +# global includes +# not ideal but not so slow +# otherwise use target_include_directories +include_directories( + ${GPSD_INCLUDE_DIRS} + ${HAMLIB_INCLUDE_DIRS} + ${ALSA_INCLUDE_DIRS} + ${UDEV_INCLUDE_DIRS} + ${PORTAUDIO_INCLUDE_DIRS} + ${CUSTOM_GEOTRANZ_DIR} + ) + +if(WIN32 OR CYGWIN) + include_directories( + ${CUSTOM_REGEX_DIR} + ) +endif() + + +# direwolf +list(APPEND direwolf_SOURCES + direwolf.c + ais.c + aprs_tt.c + audio_stats.c + ax25_link.c + ax25_pad.c + ax25_pad2.c + beacon.c + config.c + decode_aprs.c + dedupe.c + demod_9600.c + demod_afsk.c + demod_psk.c + demod.c + digipeater.c + cdigipeater.c + dlq.c + dsp.c + dtime_now.c + dtmf.c + dwgps.c + dwsock.c + encode_aprs.c + encode_aprs.c + fcs_calc.c + fcs_calc.c + fx25_encode.c + fx25_extract.c + fx25_init.c + fx25_rec.c + fx25_send.c + fx25_auto.c + gen_tone.c + hdlc_rec.c + hdlc_rec2.c + hdlc_send.c + igate.c + kiss_frame.c + kiss.c + kissserial.c + kissnet.c + latlong.c + latlong.c + log.c + morse.c + multi_modem.c + waypoint.c + serial_port.c + pfilter.c + ptt.c + recv.c + rrbb.c + server.c + symbols.c + telemetry.c + textcolor.c + tq.c + tt_text.c + tt_user.c + xid.c + xmit.c + dwgps.c + dwgpsnmea.c + dwgpsd.c + mheard.c + ) + +if(LINUX) + list(APPEND direwolf_SOURCES + audio.c + ) + if(UDEV_FOUND) + list(APPEND direwolf_SOURCES + cm108.c + ) + endif() + elseif(WIN32 OR CYGWIN) # windows + list(APPEND direwolf_SOURCES + audio_win.c + + # icon + # require plain gcc binary or link + #${CMAKE_SOURCE_DIR}/cmake/cpack/direwolf.rc + ) + list(REMOVE_ITEM direwolf_SOURCES + dwgpsd.c + ) + else() # macOS freebsd openbsd + list(APPEND direwolf_SOURCES + audio_portaudio.c + ) +endif() + +add_executable(direwolf + ${direwolf_SOURCES} + ) + +target_link_libraries(direwolf + ${GEOTRANZ_LIBRARIES} + ${MISC_LIBRARIES} + ${REGEX_LIBRARIES} + Threads::Threads + ${GPSD_LIBRARIES} + ${HAMLIB_LIBRARIES} + ${ALSA_LIBRARIES} + ${UDEV_LIBRARIES} + ${PORTAUDIO_LIBRARIES} + ) + +if(WIN32 OR CYGWIN) + set_target_properties(direwolf + PROPERTIES COMPILE_FLAGS "-DUSE_REGEX_STATIC" + ) + target_link_libraries(direwolf winmm ws2_32) +endif() + +# decode_aprs +list(APPEND decode_aprs_SOURCES + decode_aprs.c + ais.c + kiss_frame.c + ax25_pad.c + dwgpsnmea.c + dwgps.c + dwgpsd.c + serial_port.c + symbols.c + textcolor.c + fcs_calc.c + latlong.c + log.c + telemetry.c + tt_text.c + ) + +if(WIN32 OR CYGWIN) + list(REMOVE_ITEM decode_aprs_SOURCES + dwgpsd.c + ) +endif() + +add_executable(decode_aprs + ${decode_aprs_SOURCES} + ) + +set_target_properties(decode_aprs + PROPERTIES COMPILE_FLAGS "-DDECAMAIN -DUSE_REGEX_STATIC" + ) + +target_link_libraries(decode_aprs + ${MISC_LIBRARIES} + ${REGEX_LIBRARIES} + Threads::Threads + ${GPSD_LIBRARIES} + ) + + +# Convert between text and touch tone representation. +# text2tt +list(APPEND text2tt_SOURCES + tt_text.c + ) + +add_executable(text2tt + ${text2tt_SOURCES} + ) + +set_target_properties(text2tt + PROPERTIES COMPILE_FLAGS "-DENC_MAIN" + ) + +target_link_libraries(text2tt + ${MISC_LIBRARIES} + ) + +# tt2text +list(APPEND tt2text_SOURCES + tt_text.c + ) + +add_executable(tt2text + ${tt2text_SOURCES} + ) + +set_target_properties(tt2text + PROPERTIES COMPILE_FLAGS "-DDEC_MAIN" + ) + +target_link_libraries(tt2text + ${MISC_LIBRARIES} + ) + + +# Convert between Latitude/Longitude and UTM coordinates. +# ll2utm +list(APPEND ll2utm_SOURCES + ll2utm.c + textcolor.c + ) + +add_executable(ll2utm + ${ll2utm_SOURCES} + ) + +target_link_libraries(ll2utm + ${GEOTRANZ_LIBRARIES} + ${MISC_LIBRARIES} + ) + +# utm2ll +list(APPEND utm2ll_SOURCES + utm2ll.c + textcolor.c + ) + +add_executable(utm2ll + ${utm2ll_SOURCES} + ) + +target_link_libraries(utm2ll + ${GEOTRANZ_LIBRARIES} + ${MISC_LIBRARIES} + ) + + +# Convert from log file to GPX. +# log2gpx +list(APPEND log2gpx_SOURCES + log2gpx.c + textcolor.c + ) + +add_executable(log2gpx + ${log2gpx_SOURCES} + ) + +target_link_libraries(log2gpx + ${MISC_LIBRARIES} + ) + + +# Test application to generate sound. +# gen_packets +list(APPEND gen_packets_SOURCES + gen_packets.c + ax25_pad.c + fx25_encode.c + fx25_init.c + fx25_send.c + hdlc_send.c + fcs_calc.c + gen_tone.c + morse.c + dtmf.c + textcolor.c + dsp.c + ) + +add_executable(gen_packets + ${gen_packets_SOURCES} + ) + +target_link_libraries(gen_packets + ${MISC_LIBRARIES} + ) + + +# Unit test for AFSK demodulator +# atest +list(APPEND atest_SOURCES + atest.c + ais.c + demod.c + demod_afsk.c + demod_psk.c + demod_9600.c + dsp.c + fx25_extract.c + fx25_init.c + fx25_rec.c + hdlc_rec.c + hdlc_rec2.c + multi_modem.c + rrbb.c + fcs_calc.c + ax25_pad.c + decode_aprs.c + dwgpsnmea.c + dwgps.c + dwgpsd.c + serial_port.c + telemetry.c + dtime_now.c + latlong.c + symbols.c + tt_text.c + textcolor.c + ) + +if(WIN32 OR CYGWIN) + list(REMOVE_ITEM atest_SOURCES + dwgpsd.c + ) +endif() + +add_executable(atest + ${atest_SOURCES} + ) + +target_link_libraries(atest + ${MISC_LIBRARIES} + ${GPSD_LIBRARIES} + ${REGEX_LIBRARIES} + Threads::Threads + ) + +if(WIN32 OR CYGWIN) + set_target_properties(atest + PROPERTIES COMPILE_FLAGS "-DUSE_REGEX_STATIC" + ) +endif() + + +# Multiple AGWPE network or serial port clients to test TNCs side by side. +# aclients +list(APPEND aclients_SOURCES + aclients.c + ax25_pad.c + fcs_calc.c + textcolor.c + ) + +add_executable(aclients + ${aclients_SOURCES} + ) + +target_link_libraries(aclients + ${MISC_LIBRARIES} + Threads::Threads + ) + +if(WIN32 OR CYGWIN) + target_link_libraries(aclients ws2_32) +endif() + + +# Talk to a KISS TNC. +# Note: kiss_frame.c has conditional compilation on KISSUTIL. +# kissutil +list(APPEND kissutil_SOURCES + kissutil.c + kiss_frame.c + ax25_pad.c + fcs_calc.c + textcolor.c + serial_port.c + dtime_now.c + dwsock.c + ) + +add_executable(kissutil + ${kissutil_SOURCES} + ) + +set_target_properties(kissutil + PROPERTIES COMPILE_FLAGS "-DKISSUTIL" + ) + +target_link_libraries(kissutil + ${MISC_LIBRARIES} + Threads::Threads + ) + +if(WIN32 OR CYGWIN) + target_link_libraries(kissutil ws2_32) +endif() + + +# List USB audio adapters than can use GPIO for PTT. +# cm108 +if(UDEV_FOUND) + list(APPEND cm108_SOURCES + cm108.c + textcolor.c + ) + + add_executable(cm108 + ${cm108_SOURCES} + ) + + set_target_properties(cm108 + PROPERTIES COMPILE_FLAGS "-DCM108_MAIN" + ) + + target_link_libraries(cm108 + ${MISC_LIBRARIES} + ${UDEV_LIBRARIES} + ) +endif() + + +# Touch Tone to Speech sample application. +# ttcalc +list(APPEND ttcalc_SOURCES + ttcalc.c + ax25_pad.c + fcs_calc.c + textcolor.c + ) + +add_executable(ttcalc + ${ttcalc_SOURCES} + ) + +target_link_libraries(ttcalc + ${MISC_LIBRARIES} + ) + +if(WIN32 OR CYGWIN) + target_link_libraries(ttcalc ws2_32) +endif() + + +# Sample for packet radio server application. +# appserver +list(APPEND appserver_SOURCES + appserver.c + agwlib.c + dwsock.c + dtime_now.c + ax25_pad.c + fcs_calc.c + textcolor.c + ) + +add_executable(appserver + ${appserver_SOURCES} + ) + +target_link_libraries(appserver + ${MISC_LIBRARIES} + Threads::Threads + ) + +if(WIN32 OR CYGWIN) + target_link_libraries(appserver ws2_32) +endif() + + +install(TARGETS direwolf DESTINATION ${INSTALL_BIN_DIR}) +install(TARGETS decode_aprs DESTINATION ${INSTALL_BIN_DIR}) +install(TARGETS text2tt DESTINATION ${INSTALL_BIN_DIR}) +install(TARGETS tt2text DESTINATION ${INSTALL_BIN_DIR}) +install(TARGETS ll2utm DESTINATION ${INSTALL_BIN_DIR}) +install(TARGETS utm2ll DESTINATION ${INSTALL_BIN_DIR}) +install(TARGETS aclients DESTINATION ${INSTALL_BIN_DIR}) +install(TARGETS log2gpx DESTINATION ${INSTALL_BIN_DIR}) +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 appserver DESTINATION ${INSTALL_BIN_DIR}) +if(UDEV_FOUND) + install(TARGETS cm108 DESTINATION ${INSTALL_BIN_DIR}) +endif() diff --git a/aclients.c b/src/aclients.c similarity index 98% rename from aclients.c rename to src/aclients.c index 6675b09..0d0c3e7 100644 --- a/aclients.c +++ b/src/aclients.c @@ -69,13 +69,14 @@ #include #include #include -#include +#include #endif #include #include #include #include +#include #include #include @@ -283,10 +284,10 @@ int main (int argc, char *argv[]) #if __WIN32__ if (isdigit(port[j][0])) { - client_th[j] = (HANDLE)_beginthreadex (NULL, 0, client_thread_net, (void *)j, 0, NULL); + client_th[j] = (HANDLE)_beginthreadex (NULL, 0, client_thread_net, (void *)(ptrdiff_t)j, 0, NULL); } else { - client_th[j] = (HANDLE)_beginthreadex (NULL, 0, client_thread_serial, (void *)j, 0, NULL); + client_th[j] = (HANDLE)_beginthreadex (NULL, 0, client_thread_serial, (void *)(ptrdiff_t)j, 0, NULL); } if (client_th[j] == NULL) { printf ("Internal error: Could not create client thread %d.\n", j); @@ -294,10 +295,10 @@ int main (int argc, char *argv[]) } #else if (isdigit(port[j][0])) { - e = pthread_create (&client_tid[j], NULL, client_thread_net, (void *)(long)j); + e = pthread_create (&client_tid[j], NULL, client_thread_net, (void *)(ptrdiff_t)j); } else { - e = pthread_create (&client_tid[j], NULL, client_thread_serial, (void *)(long)j); + e = pthread_create (&client_tid[j], NULL, client_thread_serial, (void *)(ptrdiff_t)j); } if (e != 0) { perror("Internal error: Could not create client thread."); @@ -394,7 +395,7 @@ static void * client_thread_net (void *arg) int use_chan = -1; - my_index = (int)(long)arg; + my_index = (int)(ptrdiff_t)arg; #if DEBUGx printf ("DEBUG: client_thread_net %d start, port = '%s'\n", my_index, port[my_index]); @@ -664,7 +665,7 @@ static unsigned __stdcall client_thread_serial (void *arg) static void * client_thread_serial (void *arg) #endif { - int my_index = (int)(long)arg; + int my_index = (int)(ptrdiff_t)arg; #if __WIN32__ diff --git a/src/agwlib.c b/src/agwlib.c new file mode 100644 index 0000000..0988109 --- /dev/null +++ b/src/agwlib.c @@ -0,0 +1,749 @@ + +// ****** PRELIMINARY - needs work ****** + +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// + + +/*------------------------------------------------------------------ + * + * Module: agwlib.c + * + * Purpose: Sample application Program Interface (API) to use network TNC with AGW protocol. + * + * Input: + * + * Outputs: + * + * Description: This file contains functions to attach to a TNC over a TCP socket and send + * commands to it. The current list includes some of the following: + * + * 'C' Connect, Start an AX.25 Connection + * 'v' Connect VIA, Start an AX.25 circuit thru digipeaters + * 'c' Connection with non-standard PID + * 'D' Send Connected Data + * 'd' Disconnect, Terminate an AX.25 Connection + * 'X' Register CallSign + * 'x' Unregister CallSign + * 'R' Request for version number. + * 'G' Ask about radio ports. + * 'g' Capabilities of a port. + * 'k' Ask to start receiving RAW AX25 frames. + * 'm' Ask to start receiving Monitor AX25 frames. + * 'V' Transmit UI data frame. + * 'H' Report recently heard stations. Not implemented yet in direwolf. + * 'K' Transmit raw AX.25 frame. + * 'y' Ask Outstanding frames waiting on a Port + * 'Y' How many frames waiting for transmit for a particular station + * + * + * The user supplied application must supply functions to handle or ignore + * messages that come from the TNC. Common examples: + * + * 'C' AX.25 Connection Received + * 'D' Connected AX.25 Data + * 'd' Disconnected + * 'R' Reply to Request for version number. + * 'G' Reply to Ask about radio ports. + * 'g' Reply to capabilities of a port. + * 'K' Received AX.25 frame in raw format. (Enabled with 'k' command.) + * 'U' Received AX.25 frame in monitor format. (Enabled with 'm' command.) + * 'y' Outstanding frames waiting on a Port + * 'Y' How many frames waiting for transmit for a particular station + * 'C' AX.25 Connection Received + * 'D' Connected AX.25 Data + * 'd' Disconnected + * + * + * + * References: AGWPE TCP/IP API Tutorial + * http://uz7ho.org.ua/includes/agwpeapi.htm + * + * Usage: See appclient.c and appserver.c for examples of how to use this. + * + *---------------------------------------------------------------*/ + + +#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 +#endif + +#include +#include +#include +#include +#include +#include + +#include "textcolor.h" +#include "dwsock.h" // socket helper functions. +#include "ax25_pad.h" // forAX25_MAX_PACKET_LEN +#include "agwlib.h" + + + + +/* + * Message header for AGW protocol. + * Multibyte numeric values require rearranging for big endian cpu. + */ + +/* + * With MinGW version 4.6, obviously x86. + * or Linux gcc version 4.9, Linux ARM. + * + * $ gcc -E -dM - < /dev/null | grep END + * #define __ORDER_LITTLE_ENDIAN__ 1234 + * #define __FLOAT_WORD_ORDER__ __ORDER_LITTLE_ENDIAN__ + * #define __ORDER_PDP_ENDIAN__ 3412 + * #define __ORDER_BIG_ENDIAN__ 4321 + * #define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__ + * + * This is for standard OpenWRT on MIPS. + * + * #define __ORDER_LITTLE_ENDIAN__ 1234 + * #define __FLOAT_WORD_ORDER__ __ORDER_BIG_ENDIAN__ + * #define __ORDER_PDP_ENDIAN__ 3412 + * #define __ORDER_BIG_ENDIAN__ 4321 + * #define __BYTE_ORDER__ __ORDER_BIG_ENDIAN__ + * + * This was reported for an old Mac with PowerPC processor. + * (Newer versions have x86.) + * + * $ gcc -E -dM - < /dev/null | grep END + * #define __BIG_ENDIAN__ 1 + * #define _BIG_ENDIAN 1 + */ + + +#if defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + +// gcc >= 4.2 has __builtin_swap32() but might not be compatible with older versions or other compilers. + +#define host2netle(x) ( (((x)>>24)&0x000000ff) | (((x)>>8)&0x0000ff00) | (((x)<<8)&0x00ff0000) | (((x)<<24)&0xff000000) ) +#define netle2host(x) ( (((x)>>24)&0x000000ff) | (((x)>>8)&0x0000ff00) | (((x)<<8)&0x00ff0000) | (((x)<<24)&0xff000000) ) + +#else + +#define host2netle(x) (x) +#define netle2host(x) (x) + +#endif + + +struct agw_hdr_s { /* Command header. */ + + unsigned char portx; /* 0 for first, 1 for second, etc. */ + /* Dire Wolf uses the term "channel" to avoid confusion with TCP ports */ + /* or other places port might be used. */ + 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; + + char call_from[10]; + + char call_to[10]; + + int data_len_NETLE; /* Number of data bytes following. */ + /* _NETLE suffix is reminder to convert for network byte order. */ + + int user_reserved_NETLE; +}; + + +struct agw_cmd_s { /* Complete command with header and data. */ + + struct agw_hdr_s hdr; /* Command header. */ + char data[AX25_MAX_PACKET_LEN]; /* Possible variable length data. */ +}; + + + +/*------------------------------------------------------------------- + * + * Name: agwlib_init + * + * Purpose: Attach to TNC over TCP. + * + * Inputs: host - Host name or address. Often "localhost". + * + * port - TCP port number as text. Usually "8000". + * + * init_func - Call this function after establishing communication + * with the TNC. We put it here, so that it can be done + * again automatically if the TNC disappears and we + * reattach to it. + * It must return 0 for success. + * Can be NULL if not needed. + * + * Returns: 0 for success, -1 for failure. + * + * Description: This starts up a thread which listens to the socket and + * dispatches the messages to the corresponding callback functions. + * It will also attempt to re-establish communication with the + * TNC if it goes away. + * + *--------------------------------------------------------------------*/ + + +static char s_tnc_host[80]; +static char s_tnc_port[8]; +static int s_tnc_sock; // Socket handle or file descriptor. +static int (*s_tnc_init_func)(void); // Call after establishing socket. + + +// TODO: define macros somewhere to hide platform specifics. + +#if __WIN32__ +#define THREAD_F unsigned __stdcall +#else +#define THREAD_F void * +#endif + +#if __WIN32__ +static HANDLE tnc_listen_th; +static THREAD_F tnc_listen_thread (void *arg); +#else +static pthread_t tnc_listen_tid; +static THREAD_F tnc_listen_thread (void *arg); +#endif + + +int agwlib_init (char *host, char *port, int (*init_func)(void)) +{ + char tncaddr[DWSOCK_IPADDR_LEN]; + int e; + + strlcpy (s_tnc_host, host, sizeof(s_tnc_host)); + strlcpy (s_tnc_port, port, sizeof(s_tnc_port)); + s_tnc_sock = -1; + s_tnc_init_func = init_func; + + dwsock_init(); + + s_tnc_sock = dwsock_connect (host, port, "TNC", 0, 0, tncaddr); + + if (s_tnc_sock == -1) { + return (-1); + } + + +/* + * Incoming messages are dispatched to application-supplied callback functions. + * If the TNC disappears, try to reestablish communication. + */ + + +#if __WIN32__ + tnc_listen_th = (HANDLE)_beginthreadex (NULL, 0, tnc_listen_thread, (void *)NULL, 0, NULL); + if (tnc_listen_th == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error: Could not create TNC listening thread\n"); + return (-1); + } +#else + e = pthread_create (&tnc_listen_tid, NULL, tnc_listen_thread, (void *)NULL); + if (e != 0) { + text_color_set(DW_COLOR_ERROR); + perror("Internal error: Could not create TNC listening thread"); + return (-1); + } +#endif + +// TNC initialization if specified. + + if (s_tnc_init_func != NULL) { + e = (*s_tnc_init_func)(); + return (e); + } + + return (0); +} + + +/*------------------------------------------------------------------- + * + * Name: tnc_listen_thread + * + * Purpose: Listen for anything from TNC and process it. + * Reconnect if something goes wrong and we got disconnected. + * + * Inputs: s_tnc_host + * s_tnc_port + * + * Outputs: s_tnc_sock - File descriptor for communicating with TNC. + * Will be -1 if not connected. + * + *--------------------------------------------------------------------*/ + +static void process_from_tnc (struct agw_cmd_s *cmd); + + +#if __WIN32__ +static unsigned __stdcall tnc_listen_thread (void *arg) +#else +static void * tnc_listen_thread (void *arg) +#endif +{ + char tncaddr[DWSOCK_IPADDR_LEN]; + + struct agw_cmd_s cmd; + + while (1) { + +/* + * Connect to TNC if not currently connected. + */ + + if (s_tnc_sock == -1) { + + text_color_set(DW_COLOR_ERROR); + // I'm using the term "attach" here, in an attempt to + // avoid confusion with the AX.25 connect. + dw_printf ("Attempting to reattach to network TNC...\n"); + + 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"); + + // Might need to run TNC initialization again. + // For example, a server would register its callsigns. + + if (s_tnc_init_func != NULL) { + int e = (*s_tnc_init_func)(); + (void) e; + } + + } + SLEEP_SEC(5); + } + else { + int n = SOCK_RECV (s_tnc_sock, (char *)(&cmd.hdr), sizeof(cmd.hdr)); + + if (n == -1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Lost communication with network TNC. Will try to reattach.\n"); + dwsock_close (s_tnc_sock); + s_tnc_sock = -1; + continue; + } + else if (n != sizeof(cmd.hdr)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Error reading message header from network TNC.\n"); + dw_printf ("Tried to read %d bytes but got only %d.\n", (int)sizeof(cmd.hdr), n); + dw_printf ("Closing socket to TNC. Will try to reattach.\n"); + dwsock_close (s_tnc_sock); + s_tnc_sock = -1; + continue; + } + +/* + * Take some precautions to guard against bad data which could cause problems later. + */ + if (cmd.hdr.portx < 0 || cmd.hdr.portx >= MAX_CHANS) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Invalid channel number, %d, in command '%c', from network TNC.\n", + cmd.hdr.portx, cmd.hdr.datakind); + cmd.hdr.portx = 0; // avoid subscript out of bounds, try to keep going. + } + +/* + * Call to/from fields are 10 bytes but contents must not exceeed 9 characters. + * It's not guaranteed that unused bytes will contain 0 so we + * don't issue error message in this case. + */ + cmd.hdr.call_from[sizeof(cmd.hdr.call_from)-1] = '\0'; + cmd.hdr.call_to[sizeof(cmd.hdr.call_to)-1] = '\0'; + +/* + * Following data must fit in available buffer. + * Leave room for an extra nul byte terminator at end later. + */ + + int data_len = netle2host(cmd.hdr.data_len_NETLE); + + if (data_len < 0 || data_len > (int)(sizeof(cmd.data) - 1)) { + + text_color_set(DW_COLOR_ERROR); + dw_printf ("Invalid message from network TNC.\n"); + dw_printf ("Data Length of %d is out of range.\n", data_len); + + /* This is a bad situation. */ + /* If we tried to read again, the header probably won't be there. */ + /* No point in trying to continue reading. */ + + dw_printf ("Closing connection to TNC.\n"); + dwsock_close (s_tnc_sock); + s_tnc_sock = -1; + continue; + } + + cmd.data[0] = '\0'; + + if (data_len > 0) { + n = SOCK_RECV (s_tnc_sock, cmd.data, data_len); + if (n != data_len) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Error getting message data from network TNC.\n"); + dw_printf ("Tried to read %d bytes but got only %d.\n", data_len, n); + dw_printf ("Closing socket to network TNC.\n\n"); + dwsock_close (s_tnc_sock); + s_tnc_sock = -1; + continue; + } + if (n >= 0) { + cmd.data[n] = '\0'; // Terminate so it can be used as a C string. + } + + process_from_tnc (&cmd); + + } // additional data after command header + } // s_tnc_sock != -1 + } // while (1) + + return (0); // unreachable but shutup warning. + +} // end tnc_listen_thread + + +/* + * The user supplied application must supply functions to handle or ignore + * messages that come from the TNC. + */ + +static void process_from_tnc (struct agw_cmd_s *cmd) +{ + int data_len = netle2host(cmd->hdr.data_len_NETLE); + //int session; + + + switch (cmd->hdr.datakind) { + + case 'C': // AX.25 Connection Received + { + //agw_cb_C_connection_received (cmd->hdr.portx, cmd->hdr.call_from, cmd->hdr.call_to, data_len, cmd->data); + // TODO: compute session id + // There are two different cases to consider here. + if (strncmp(cmd->data, "*** CONNECTED To Station", 24) == 0) { + // Incoming: Other station initiated the connect request. + on_C_connection_received (cmd->hdr.portx, cmd->hdr.call_from, cmd->hdr.call_to, 1, cmd->data); + } + else if (strncmp(cmd->data, "*** CONNECTED With Station", 26) == 0) { + // Outgoing: Other station accepted my connect request. + on_C_connection_received (cmd->hdr.portx, cmd->hdr.call_from, cmd->hdr.call_to, 0, cmd->data); + } + else { +// TBD + } + } + break; + + case 'D': // Connected AX.25 Data + // FIXME: should probably add pid here. + agw_cb_D_connected_data (cmd->hdr.portx, cmd->hdr.call_from, cmd->hdr.call_to, data_len, cmd->data); + break; + + case 'd': // Disconnected + agw_cb_d_disconnected (cmd->hdr.portx, cmd->hdr.call_from, cmd->hdr.call_to, data_len, cmd->data); + break; + + case 'R': // Reply to Request for version number. + break; + + case 'G': // Port Information. + // Data part should be fields separated by semicolon. + // First field is number of ports (we call them channels). + // Other fields are of the form "Port99 comment" where first is number 1. + { + int num_chan = 1; // FIXME: FIXME: actually parse it. + char *chans[20]; + chans[0] = "Port1 blah blah"; + chans[1] = "Port2 blah blah"; + agw_cb_G_port_information (num_chan, chans); + } + break; + +// TODO: Maybe fill in more someday. + + case 'g': // Reply to capabilities of a port. + break; + case 'K': // Received AX.25 frame in raw format. (Enabled with 'k' command.) + break; + case 'U': // Received AX.25 frame in monitor format. (Enabled with 'm' command.) + break; + case 'y': // Outstanding frames waiting on a Port + break; + + case 'Y': // How many frames waiting for transmit for a particular station + { + int *p = (int*)(cmd->data); + int frame_count = netle2host(*p); + agw_cb_Y_outstanding_frames_for_station (cmd->hdr.portx, cmd->hdr.call_from, cmd->hdr.call_to, frame_count); + } + break; + + default: + break; + } + +} // end process_from_tnc + + + +/*------------------------------------------------------------------- + * + * Name: agwlib_X_register_callsign + * + * Purpose: Tell TNC to accept incoming connect requests to given callsign. + * + * Inputs: chan - Radio channel number, first is 0. + * + * call_from - My callsign or alias. + * + * Returns: Number of bytes sent for success, -1 for error. + * + *--------------------------------------------------------------------*/ + +int agwlib_X_register_callsign (int chan, char *call_from) +{ + struct agw_cmd_s cmd; + + memset (&cmd.hdr, 0, sizeof(cmd.hdr)); + cmd.hdr.portx = chan; + cmd.hdr.datakind = 'X'; + strlcpy (cmd.hdr.call_from, call_from, sizeof(cmd.hdr.call_from)); + return (SOCK_SEND(s_tnc_sock, (char*)(&cmd), sizeof(cmd.hdr) + netle2host(cmd.hdr.data_len_NETLE))); +} + + +/*------------------------------------------------------------------- + * + * Name: agwlib_x_unregister_callsign + * + * Purpose: Tell TNC to stop accepting incoming connect requests to given callsign. + * + * Inputs: chan - Radio channel number, first is 0. + * + * call_from - My callsign or alias. + * + * Returns: Number of bytes sent for success, -1 for error. + * + * FIXME: question do we need channel here? + * + *--------------------------------------------------------------------*/ + +int agwlib_x_unregister_callsign (int chan, char *call_from) +{ + struct agw_cmd_s cmd; + + memset (&cmd.hdr, 0, sizeof(cmd.hdr)); + cmd.hdr.portx = chan; + cmd.hdr.datakind = 'x'; + strlcpy (cmd.hdr.call_from, call_from, sizeof(cmd.hdr.call_from)); + return (SOCK_SEND(s_tnc_sock, (char*)(&cmd), sizeof(cmd.hdr) + netle2host(cmd.hdr.data_len_NETLE))); +} + + +/*------------------------------------------------------------------- + * + * Name: agwlib_G_ask_port_information + * + * Purpose: Tell TNC to stop accepting incoming connect requests to given callsign. + * + * Inputs: call_from - My callsign or alias. + * + * Returns: 0 for success, -1 for error. TODO: all like this. + * + *--------------------------------------------------------------------*/ + +int agwlib_G_ask_port_information (void) +{ + struct agw_cmd_s cmd; + + memset (&cmd.hdr, 0, sizeof(cmd.hdr)); + cmd.hdr.datakind = 'G'; + int n = SOCK_SEND(s_tnc_sock, (char*)(&cmd), sizeof(cmd.hdr) + netle2host(cmd.hdr.data_len_NETLE)); + return (n > 0 ? 0 : -1); +} + + + +/*------------------------------------------------------------------- + * + * Name: agwlib_C_connect + * + * Purpose: Tell TNC to start sequence for connecting to remote station. + * + * Inputs: chan - Radio channel number, first is 0. + * + * call_from - My callsign. + * + * call_to - Callsign (or alias) of remote station. + * + * 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 ? + * + *--------------------------------------------------------------------*/ + +int agwlib_C_connect (int chan, char *call_from, char *call_to) +{ + struct agw_cmd_s cmd; + + memset (&cmd.hdr, 0, sizeof(cmd.hdr)); + cmd.hdr.portx = chan; + cmd.hdr.datakind = 'C'; + cmd.hdr.pid = 0xF0; // Shouldn't matter because this appears + // only in Information frame, not connect sequence. + strlcpy (cmd.hdr.call_from, call_from, sizeof(cmd.hdr.call_from)); + strlcpy (cmd.hdr.call_to, call_to, sizeof(cmd.hdr.call_to)); + return (SOCK_SEND(s_tnc_sock, (char*)(&cmd), sizeof(cmd.hdr) + netle2host(cmd.hdr.data_len_NETLE))); +} + + + +/*------------------------------------------------------------------- + * + * Name: agwlib_d_disconnect + * + * Purpose: Tell TNC to disconnect from remote station. + * + * Inputs: chan - Radio channel number, first is 0. + * + * call_from - My callsign. + * + * call_to - Callsign (or alias) of remote station. + * + * 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 ? + * + *--------------------------------------------------------------------*/ + +int agwlib_d_disconnect (int chan, char *call_from, char *call_to) +{ + struct agw_cmd_s cmd; + + memset (&cmd.hdr, 0, sizeof(cmd.hdr)); + cmd.hdr.portx = chan; + cmd.hdr.datakind = 'd'; + strlcpy (cmd.hdr.call_from, call_from, sizeof(cmd.hdr.call_from)); + strlcpy (cmd.hdr.call_to, call_to, sizeof(cmd.hdr.call_to)); + return (SOCK_SEND(s_tnc_sock, (char*)(&cmd), sizeof(cmd.hdr) + netle2host(cmd.hdr.data_len_NETLE))); +} + + + +/*------------------------------------------------------------------- + * + * Name: agwlib_D_send_connected_data + * + * Purpose: Send connected data to remote station. + * + * Inputs: chan - Radio channel number, first is 0. + * + * pid - Protocol ID. Normally 0xFo for Ax.25. + * + * call_from - My callsign. + * + * call_to - Callsign (or alias) of remote station. + * + * data_len - Number of bytes for Information part. + * + * data - Content for Information part. + * + * Returns: Number of bytes sent for success, -1 for error. + * + * Description: This should only be done when we are known to have + * an established link to other station. + * + *--------------------------------------------------------------------*/ + +int agwlib_D_send_connected_data (int chan, int pid, char *call_from, char *call_to, int data_len, char *data) +{ + struct agw_cmd_s cmd; + + memset (&cmd.hdr, 0, sizeof(cmd.hdr)); + cmd.hdr.portx = chan; + cmd.hdr.datakind = 'D'; + cmd.hdr.pid = pid; // Normally 0xF0 but other special cases are possible. + strlcpy (cmd.hdr.call_from, call_from, sizeof(cmd.hdr.call_from)); + strlcpy (cmd.hdr.call_to, call_to, sizeof(cmd.hdr.call_to)); + cmd.hdr.data_len_NETLE = host2netle(data_len); + +// FIXME: DANGER possible buffer overflow, Need checking. + + assert (data_len <= sizeof(cmd.data)); + + memcpy (cmd.data, data, data_len); + return (SOCK_SEND(s_tnc_sock, (char*)(&cmd), sizeof(cmd.hdr) + netle2host(cmd.hdr.data_len_NETLE))); +} + + +/*------------------------------------------------------------------- + * + * Name: agwlib_Y_outstanding_frames_for_station + * + * Purpose: Ask how many frames remain to be sent to station on other end of link. + * + * Inputs: chan - Radio channel number, first is 0. + * + * call_from - My call [ or is it Station which initiated the link? (sent SABM/SABME) ] + * + * call_to - Remote station call [ or is it Station which accepted the link? ] + * + * Returns: Number of bytes sent for success, -1 for error. + * + * Description: We expect to get a 'Y' frame response shortly. + * + * This would be useful for a couple different purposes. + * + * When sending bulk data, we want to keep a fair amount queued up to take + * advantage of large window sizes (MAXFRAME, EMAXFRAME). On the other + * 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, + * I think it would be good for this to include frames that were + * transmitted but not yet acknowleged. (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. + * + *--------------------------------------------------------------------*/ + +int agwlib_Y_outstanding_frames_for_station (int chan, char *call_from, char *call_to) +{ + struct agw_cmd_s cmd; + + memset (&cmd.hdr, 0, sizeof(cmd.hdr)); + cmd.hdr.portx = chan; + cmd.hdr.datakind = 'Y'; + strlcpy (cmd.hdr.call_from, call_from, sizeof(cmd.hdr.call_from)); + strlcpy (cmd.hdr.call_to, call_to, sizeof(cmd.hdr.call_to)); + return (SOCK_SEND(s_tnc_sock, (char*)(&cmd), sizeof(cmd.hdr) + netle2host(cmd.hdr.data_len_NETLE))); +} + + + +/* end agwlib.c */ diff --git a/src/agwlib.h b/src/agwlib.h new file mode 100644 index 0000000..688f918 --- /dev/null +++ b/src/agwlib.h @@ -0,0 +1,45 @@ + +#ifndef AGWLIB_H +#define AGWLIB_H 1 + + +// Call at beginning to start it up. + +int agwlib_init (char *host, char *port, int (*init_func)(void)); + + + +// Send commands to TNC. + + +int agwlib_X_register_callsign (int chan, char *call_from); + +int agwlib_x_unregister_callsign (int chan, char *call_from); + +int agwlib_G_ask_port_information (void); + +int agwlib_C_connect (int chan, char *call_from, char *call_to); + +int agwlib_d_disconnect (int chan, char *call_from, char *call_to); + +int agwlib_D_send_connected_data (int chan, int pid, char *call_from, char *call_to, int data_len, char *data); + +int agwlib_Y_outstanding_frames_for_station (int chan, char *call_from, char *call_to); + + + +// The application must define these. + +void agw_cb_C_connection_received (int chan, char *call_from, char *call_to, int data_len, char *data); +void on_C_connection_received (int chan, char *call_from, char *call_to, int incoming, char *data); + +void agw_cb_d_disconnected (int chan, char *call_from, char *call_to, int data_len, char *data); + +void agw_cb_D_connected_data (int chan, char *call_from, char *call_to, int data_len, char *data); + +void agw_cb_G_port_information (int num_chan, char *chan_descriptions[]); + +void agw_cb_Y_outstanding_frames_for_station (int chan, char *call_from, char *call_to, int frame_count); + + +#endif \ No newline at end of file diff --git a/src/ais.c b/src/ais.c new file mode 100644 index 0000000..cadf648 --- /dev/null +++ b/src/ais.c @@ -0,0 +1,710 @@ + +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 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 +// 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: ais.c + * + * Purpose: Functions for processing received AIS transmissions and + * converting to NMEA sentence representation. + * + * References: AIVDM/AIVDO protocol decoding by Eric S. Raymond + * https://gpsd.gitlab.io/gpsd/AIVDM.html + * + * Sample recording with about 100 messages. Test with "atest -B AIS xxx.wav" + * https://github.com/freerange/ais-on-sdr/wiki/example-data/long-beach-160-messages.wav + * + * Useful on-line decoder for AIS NMEA sentences. + * https://www.aggsoft.com/ais-decoder.htm + * + * Future? Add an interface to feed AIS data into aprs.fi. + * https://aprs.fi/page/ais_feeding + * + *******************************************************************************/ + +#include "direwolf.h" + +#include +#include +#include +#include +#include + +#include "textcolor.h" +#include "ais.h" + +// Lengths, in bits, for the AIS message types. + +#define NUM_TYPES 27 +static const struct { + short min; + short max; +} valid_len[NUM_TYPES+1] = { + { -1, -1 }, // 0 not used + { 168, 168 }, // 1 + { 168, 168 }, // 2 + { 168, 168 }, // 3 + { 168, 168 }, // 4 + { 424, 424 }, // 5 + { 72, 1008 }, // 6 multipurpose + { 72, 168 }, // 7 increments of 32 bits + { 168, 1008 }, // 8 multipurpose + { 168, 168 }, // 9 + { 72, 72 }, // 10 + { 168, 168 }, // 11 + { 72, 1008 }, // 12 + { 72, 168 }, // 13 increments of 32 bits + { 40, 1008 }, // 14 + { 88, 160 }, // 15 + { 96, 114 }, // 16 96 or 114, not range + { 80, 816 }, // 17 + { 168, 168 }, // 18 + { 312, 312 }, // 19 + { 72, 160 }, // 20 + { 272, 360 }, // 21 + { 168, 168 }, // 22 + { 160, 160 }, // 23 + { 160, 168 }, // 24 + { 40, 168 }, // 25 + { 60, 1064 }, // 26 + { 96, 168 } // 27 96 or 168, not range +}; + +static void save_ship_data(char *mssi, char *shipname, char *callsign, char *destination); +static void get_ship_data(char *mssi, char *comment, int comment_size); + + +/*------------------------------------------------------------------- + * + * Functions to get and set element of a bit vector. + * + *--------------------------------------------------------------------*/ + +static const unsigned char mask[8] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; + +static inline unsigned int get_bit (unsigned char *base, unsigned int offset) +{ + return ( (base[offset >> 3] & mask[offset & 0x7]) != 0); +} + +static inline void set_bit (unsigned char *base, unsigned int offset, int val) +{ + if (val) { + base[offset >> 3] |= mask[offset & 0x7]; + } + else { + base[offset >> 3] &= ~ mask[offset & 0x7]; + } +} + + +/*------------------------------------------------------------------- + * + * Extract a variable length field from a bit vector. + * + *--------------------------------------------------------------------*/ + +static unsigned int get_field (unsigned char *base, unsigned int start, unsigned int len) +{ + unsigned int result = 0; + for (int k = 0; k < len; k++) { + result <<= 1; + result |= get_bit (base, start + k); + } + return (result); +} + +static void set_field (unsigned char *base, unsigned int start, unsigned int len, unsigned int val) +{ + for (int k = 0; k < len; k++) { + set_bit (base, start + k, (val >> (len - 1 - k) ) & 1); + } +} + + +static int get_field_signed (unsigned char *base, unsigned int start, unsigned int len) +{ + int result = (int) get_field(base, start, len); + // Sign extend. + result <<= (32 - len); + result >>= (32 - len); + return (result); +} + +static double get_field_lat (unsigned char *base, unsigned int start, unsigned int len) +{ + // Latitude of 0x3412140 (91 deg) means not available. + // Message type 27 uses lower resolution, 17 bits rather than 27. + // It encodes minutes/10 rather than normal minutes/10000. + + int n = get_field_signed(base, start, len); + if (len == 17) { + return ((n == 91*600) ? G_UNKNOWN : (double)n / 600.0); + } + else { + return ((n == 91*600000) ? G_UNKNOWN : (double)n / 600000.0); + } +} + +static double get_field_lon (unsigned char *base, unsigned int start, unsigned int len) +{ + // Longitude of 0x6791AC0 (181 deg) means not available. + // Message type 27 uses lower resolution, 18 bits rather than 28. + // It encodes minutes/10 rather than normal minutes/10000. + + int n = get_field_signed(base, start, len); + if (len == 18) { + return ((n == 181*600) ? G_UNKNOWN : (double)n / 600.0); + } + else { + return ((n == 181*600000) ? G_UNKNOWN : (double)n / 600000.0); + } +} + +static float get_field_speed (unsigned char *base, unsigned int start, unsigned int len) +{ + // Raw 1023 means not available. + // Multiply by 0.1 to get knots. + // For aircraft it is knots, not deciknots. + + // Message type 27 uses lower resolution, 6 bits rather than 10. + // It encodes minutes/10 rather than normal minutes/10000. + + int n = get_field(base, start, len); + if (len == 6) { + return ((n == 63) ? G_UNKNOWN : (float)n); + } + else { + return ((n == 1023) ? G_UNKNOWN : (float)n * 0.1); + } +} + +static float get_field_course (unsigned char *base, unsigned int start, unsigned int len) +{ + // Raw 3600 means not available. + // Multiply by 0.1 to get degrees + // Message type 27 uses lower resolution, 9 bits rather than 12. + // It encodes degrees rather than normal degrees/10. + + int n = get_field(base, start, len); + if (len == 9) { + return ((n == 360) ? G_UNKNOWN : (float)n); + } + else { + return ((n == 3600) ? G_UNKNOWN : (float)n * 0.1); + } +} + +static int get_field_ascii (unsigned char *base, unsigned int start, unsigned int len) +{ + assert (len == 6); + int ch = get_field(base, start, len); + if (ch < 32) ch += 64; + return (ch); +} + +static void get_field_string (unsigned char *base, unsigned int start, unsigned int len, char *result) +{ + assert (len % 6 == 0); + int nc = len / 6; // Number of characters. + // Caller better provide space for at least this +1. + // No bounds checking here. + for (int i = 0; i < nc; i++) { + result[i] = get_field_ascii (base, start + i * 6, 6); + } + result[nc] = '\0'; + // Officially it should be terminated/padded with @ but we also see trailing spaces. + char *p = strchr(result, '@'); + if (p != NULL) *p = '\0'; + for (int k = strlen(result) - 1; k >= 0 && result[k] == ' '; k--) { + result[k] = '\0'; + } +} + + + +/*------------------------------------------------------------------- + * + * Convert between 6 bit values and printable characters used in + * in the AIS NMEA sentences. + * + *--------------------------------------------------------------------*/ + +// Characters '0' thru 'W' become values 0 thru 39. +// Characters '`' thru 'w' become values 40 thru 63. + +static int char_to_sextet (char ch) +{ + if (ch >= '0' && ch <= 'W') { + return (ch - '0'); + } + else if (ch >= '`' && ch <= 'w') { + return (ch - '`' + 40); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Invalid character \"%c\" found in AIS NMEA sentence payload.\n", ch); + return (0); + } +} + + +// Values 0 thru 39 become characters '0' thru 'W'. +// Values 40 thru 63 become characters '`' thru 'w'. +// This is known as "Payload Armoring." + +static int sextet_to_char (int val) +{ + if (val >= 0 && val <= 39) { + return ('0' + val); + } + else if (val >= 40 && val <= 63) { + return ('`' + val - 40); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Invalid 6 bit value %d from AIS HDLC payload.\n", val); + return ('0'); + } +} + + +/*------------------------------------------------------------------- + * + * Convert AIS binary block (from HDLC frame) to NMEA sentence. + * + * In: Pointer to AIS binary block and number of bytes. + * Out: NMEA sentence. Provide size to avoid string overflow. + * + *--------------------------------------------------------------------*/ + +void ais_to_nmea (unsigned char *ais, int ais_len, char *nmea, int nmea_size) +{ + char payload[256]; + // Number of resulting characters for payload. + int ns = (ais_len * 8 + 5) / 6; + if (ns+1 > sizeof(payload)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("AIS HDLC payload of %d bytes is too large.\n", ais_len); + ns = sizeof(payload) - 1; + } + for (int k = 0; k < ns; k++) { + payload[k] = sextet_to_char(get_field(ais, k*6, 6)); + } + payload[ns] = '\0'; + + strlcpy (nmea, "!AIVDM,1,1,,A,", nmea_size); + strlcat (nmea, payload, nmea_size); + + // If the number of bytes in is not a multiple of 3, this does not + // produce a whole number of characters out. Extra padding bits were + // added to get the last character. Include this number so the + // decoding application can drop this number of bits from the end. + // At least, I think that is the way it should work. + // The examples all have 0. + char pad_bits[8]; + snprintf (pad_bits, sizeof(pad_bits), ",%d", ns * 6 - ais_len * 8); + strlcat (nmea, pad_bits, nmea_size); + + // Finally the NMEA style checksum. + int cs = 0; + for (char *p = nmea + 1; *p != '\0'; p++) { + cs ^= *p; + } + char checksum[8]; + snprintf (checksum, sizeof(checksum), "*%02X", cs & 0x7f); + strlcat (nmea, checksum, nmea_size); +} + + +/*------------------------------------------------------------------- + * + * Name: ais_parse + * + * Purpose: Parse AIS sentence and extract interesing parts. + * + * Inputs: sentence NMEA sentence. + * + * quiet Suppress printing of error messages. + * + * Outputs: descr Description of AIS message type. + * mssi 9 digit identifier. + * odlat latitude. + * odlon longitude. + * ofknots speed, knots. + * ofcourse direction of travel. + * ofalt_m altitude, meters. + * symtab APRS symbol table. + * symbol APRS symbol code. + * + * Returns: 0 for success, -1 for error. + * + *--------------------------------------------------------------------*/ + +// Maximum NMEA sentence length is 82, including CR/LF. +// Make buffer considerably larger to be safe. +#define NMEA_MAX_LEN 240 + +int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mssi, int mssi_size, double *odlat, double *odlon, + float *ofknots, float *ofcourse, float *ofalt_m, char *symtab, char *symbol, char *comment, int comment_size) +{ + char stemp[NMEA_MAX_LEN]; /* Make copy because parsing is destructive. */ + + strlcpy (mssi, "?", mssi_size); + *odlat = G_UNKNOWN; + *odlon = G_UNKNOWN; + *ofknots = G_UNKNOWN; + *ofcourse = G_UNKNOWN; + *ofalt_m = G_UNKNOWN; + + strlcpy (stemp, sentence, sizeof(stemp)); + +// Verify and remove checksum. + + unsigned char cs = 0; + char *p; + + for (p = stemp+1; *p != '*' && *p != '\0'; p++) { + cs ^= *p; + } + + p = strchr (stemp, '*'); + if (p == NULL) { + if ( ! quiet) { + text_color_set (DW_COLOR_INFO); + dw_printf("Missing AIS sentence checksum.\n"); + } + return (-1); + } + if (cs != strtoul(p+1, NULL, 16)) { + if ( ! quiet) { + text_color_set (DW_COLOR_ERROR); + dw_printf("AIS sentence checksum error. Expected %02x but found %s.\n", cs, p+1); + } + return (-1); + } + *p = '\0'; // Remove the checksum. + +// Extract the comma separated fields. + + char *next; + + char *talker; /* Expecting !AIVDM */ + char *frag_count; /* ignored */ + char *frag_num; /* ignored */ + char *msg_id; /* ignored */ + char *radio_chan; /* ignored */ + char *payload; /* Encoded as 6 bits per character. */ + char *fill_bits; /* Number of bits to discard. */ + + next = stemp; + talker = strsep(&next, ","); + frag_count = strsep(&next, ","); + frag_num = strsep(&next, ","); + msg_id = strsep(&next, ","); + radio_chan = strsep(&next, ","); + payload = strsep(&next, ","); + fill_bits = strsep(&next, ","); + + /* Suppress the 'set but not used' compiler warnings. */ + /* Alternatively, we might use __attribute__((unused)) */ + + (void)(talker); + (void)(frag_count); + (void)(frag_num); + (void)(msg_id); + (void)(radio_chan); + + if (payload == NULL || strlen(payload) == 0) { + if ( ! quiet) { + text_color_set (DW_COLOR_ERROR); + dw_printf("Payload is missing from AIS sentence.\n"); + } + return (-1); + } + +// Convert character representation to bit vector. + + unsigned char ais[256]; + memset (ais, 0, sizeof(ais)); + + int plen = strlen(payload); + + for (int k = 0; k < plen; k++) { + set_field (ais, k*6, 6, char_to_sextet(payload[k])); + } + +// Verify number of filler bits. + + int nfill = atoi(fill_bits); + int nbytes = (plen * 6) / 8; + + if (nfill != plen * 6 - nbytes * 8) { + if ( ! quiet) { + text_color_set (DW_COLOR_ERROR); + dw_printf("Number of filler bits is %d when %d is expected.\n", + nfill, plen * 6 - nbytes * 8); + } + } + + +// Extract the fields of interest from a few message types. +// Don't get too carried away. + + int type = get_field(ais, 0, 6); + + if (type >= 1 && type <= 27) { + snprintf (mssi, mssi_size, "%09d", get_field(ais, 8, 30)); + } + switch (type) { + + case 1: // Position Report Class A + case 2: + case 3: + + snprintf (descr, descr_size, "AIS %d: Position Report Class A", type); + *symtab = '/'; + *symbol = 's'; // Power boat (ship) side view + *odlon = get_field_lon(ais, 61, 28); + *odlat = get_field_lat(ais, 89, 27); + *ofknots = get_field_speed(ais, 50, 10); + *ofcourse = get_field_course(ais, 116, 12); + get_ship_data(mssi, comment, comment_size); + break; + + case 4: // Base Station Report + + snprintf (descr, descr_size, "AIS %d: Base Station Report", type); + *symtab = '\\'; + *symbol = 'L'; // Lighthouse + //year = get_field(ais, 38, 14); + //month = get_field(ais, 52, 4); + //day = get_field(ais, 56, 5); + //hour = get_field(ais, 61, 5); + //minute = get_field(ais, 66, 6); + //second = get_field(ais, 72, 6); + *odlon = get_field_lon(ais, 79, 28); + *odlat = get_field_lat(ais, 107, 27); + // Is this suitable or not? Doesn't hurt, I suppose. + get_ship_data(mssi, comment, comment_size); + break; + + case 5: // Static and Voyage Related Data + + snprintf (descr, descr_size, "AIS %d: Static and Voyage Related Data", type); + *symtab = '/'; + *symbol = 's'; // Power boat (ship) side view + { + char callsign[12]; + char shipname[24]; + char destination[24]; + get_field_string(ais, 70, 42, callsign); + get_field_string(ais, 112, 120, shipname); + get_field_string(ais, 302, 120, destination); + save_ship_data(mssi, shipname, callsign, destination); + get_ship_data(mssi, comment, comment_size); + } + break; + + + case 9: // Standard SAR Aircraft Position Report + + snprintf (descr, descr_size, "AIS %d: SAR Aircraft Position Report", type); + *symtab = '/'; + *symbol = '\''; // Small AIRCRAFT + *ofalt_m = get_field(ais, 38, 12); // meters, 4095 means not available + *odlon = get_field_lon(ais, 61, 28); + *odlat = get_field_lat(ais, 89, 27); + *ofknots = get_field_speed(ais, 50, 10); // plane is knots, not knots/10 + if (*ofknots != G_UNKNOWN) *ofknots = *ofknots * 10.0; + *ofcourse = get_field_course(ais, 116, 12); + get_ship_data(mssi, comment, comment_size); + break; + + case 18: // Standard Class B CS Position Report + // As an oversimplification, Class A is commercial, B is recreational. + + snprintf (descr, descr_size, "AIS %d: Standard Class B CS Position Report", type); + *symtab = '/'; + *symbol = 'Y'; // YACHT (sail) + *odlon = get_field_lon(ais, 57, 28); + *odlat = get_field_lat(ais, 85, 27); + get_ship_data(mssi, comment, comment_size); + break; + + case 19: // Extended Class B CS Position Report + + snprintf (descr, descr_size, "AIS %d: Extended Class B CS Position Report", type); + *symtab = '/'; + *symbol = 'Y'; // YACHT (sail) + *odlon = get_field_lon(ais, 57, 28); + *odlat = get_field_lat(ais, 85, 27); + get_ship_data(mssi, comment, comment_size); + break; + + case 27: // Long Range AIS Broadcast message + + snprintf (descr, descr_size, "AIS %d: Long Range AIS Broadcast message", type); + *symtab = '\\'; + *symbol = 's'; // OVERLAY SHIP/boat (top view) + *odlon = get_field_lon(ais, 44, 18); // Note: minutes/10 rather than usual /10000. + *odlat = get_field_lat(ais, 62, 17); + *ofknots = get_field_speed(ais, 79, 6); // Note: knots, not deciknots. + *ofcourse = get_field_course(ais, 85, 9); // Note: degrees, not decidegrees. + get_ship_data(mssi, comment, comment_size); + break; + + default: + snprintf (descr, descr_size, "AIS message type %d", type); + break; + } + + return (0); + +} /* end ais_parse */ + + + +/*------------------------------------------------------------------- + * + * Name: ais_check_length + * + * Purpose: Verify frame length against expected. + * + * Inputs: type Message type, 1 - 27. + * + * length Number of data octets in in frame. + * + * Returns: -1 Invalid message type. + * 0 Good length. + * 1 Unexpected lenth. + * + *--------------------------------------------------------------------*/ + +int ais_check_length (int type, int length) +{ + if (type >= 1 && type <= NUM_TYPES) { + int b = length * 8; + if (b >= valid_len[type].min && b <= valid_len[type].max) { + return (0); // Good. + } + else { + //text_color_set (DW_COLOR_ERROR); + //dw_printf("AIS ERROR: type %d, has %d bits when %d to %d expected.\n", + // type, b, valid_len[type].min, valid_len[type].max); + return (1); // Length out of range. + } + } + else { + //text_color_set (DW_COLOR_ERROR); + //dw_printf("AIS ERROR: message type %d is invalid.\n", type); + return (-1); // Invalid type. + } + +} // end ais_check_length + + + +/*------------------------------------------------------------------- + * + * Name: save_ship_data + * + * Purpose: Save shipname, etc., from "Static and Voyage Related Data" + * so it can be combined later with the position reports. + * + * Inputs: mssi + * shipname + * callsign + * destination + * + *--------------------------------------------------------------------*/ + +struct ship_data_s { + struct ship_data_s *pnext; + char mssi[9+1]; + char shipname[20+1]; + char callsign[7+1]; + char destination[20+1]; +}; + +// Just use a single linked list for now. +// If I get ambitious, I might use a hash table. +// I don't think we need a critical region because all channels +// should be serialized thru the receive queue. + +static struct ship_data_s *ships = NULL; + + +static void save_ship_data(char *mssi, char *shipname, char *callsign, char *destination) +{ + // Get list node, either existing or new. + struct ship_data_s *p = ships; + while (p != NULL) { + if (strcmp(mssi, p->mssi) == 0) { + break; + } + p = p->pnext; + } + if (p == NULL) { + p = calloc(sizeof(struct ship_data_s),1); + p->pnext = ships; + ships = p; + } + + strlcpy (p->mssi, mssi, sizeof(p->mssi)); + strlcpy (p->shipname, shipname, sizeof(p->shipname)); + strlcpy (p->callsign, callsign, sizeof(p->callsign)); + strlcpy (p->destination, destination, sizeof(p->destination)); +} + +/*------------------------------------------------------------------- + * + * Name: save_ship_data + * + * Purpose: Get ship data for specified mssi. + * + * Inputs: mssi + * + * Outputs: comment - If mssi is found, return in single string here, + * suitable for the comment field. + * + *--------------------------------------------------------------------*/ + +static void get_ship_data(char *mssi, char *comment, int comment_size) +{ + struct ship_data_s *p = ships; + while (p != NULL) { + if (strcmp(mssi, p->mssi) == 0) { + break; + } + p = p->pnext; + } + if (p != NULL) { + if (strlen(p->destination) > 0) { + snprintf (comment, comment_size, "%s, %s, dest. %s", p->shipname, p->callsign, p->destination); + } + else { + snprintf (comment, comment_size, "%s, %s", p->shipname, p->callsign); + } + } +} + + +// end ais.c diff --git a/src/ais.h b/src/ais.h new file mode 100644 index 0000000..6b96288 --- /dev/null +++ b/src/ais.h @@ -0,0 +1,8 @@ + + +void ais_to_nmea (unsigned char *ais, int ais_len, char *nema, int nema_size); + +int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mssi, int mssi_size, double *odlat, double *odlon, + float *ofknots, float *ofcourse, float *ofalt_m, char *symtab, char *symbol, char *comment, int comment_size); + +int ais_check_length (int type, int length); diff --git a/src/appserver.c b/src/appserver.c new file mode 100644 index 0000000..2badaec --- /dev/null +++ b/src/appserver.c @@ -0,0 +1,737 @@ +// ****** PRELIMINARY - needs work ****** + +#define DEBUG 1 + + +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// + + + +#include "direwolf.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ax25_pad.h" +#include "textcolor.h" +#include "agwlib.h" // Network TNC interface. + + +/*------------------------------------------------------------------ + * + * Module: appserver.c + * + * Purpose: Simple application server for connected mode AX.25. + * + * This demonstrates how you can write a application that will wait for + * a connection from another station and respond to commands. + * It can be used as a starting point for developing your own applications. + * + * Description: This attaches to an instance of Dire Wolf via the AGW network interface. + * It processes commands from other radio stations and responds. + * + *---------------------------------------------------------------*/ + + +static void usage() +{ + text_color_set(DW_COLOR_ERROR); + dw_printf ("Usage: \n"); + dw_printf (" \n"); + dw_printf ("appserver [ -h hostname ] [ -p port ] mycall \n"); + dw_printf (" \n"); + dw_printf (" -h hostname for TNC. Default is localhost. \n"); + dw_printf (" \n"); + dw_printf (" -p tcp port for TNC. Default is 8000. \n"); + dw_printf (" \n"); + dw_printf (" mycall is required because that is the callsign for \n"); + dw_printf (" which the TNC will accept connections. \n"); + dw_printf (" \n"); + exit (EXIT_FAILURE); +} + + + + + +static char mycall[AX25_MAX_ADDR_LEN]; /* Callsign, with SSID, for the application. */ + /* Future? Could have multiple applications, on the same */ + /* radio channel, each with its own SSID. */ + +static char tnc_hostname[80]; /* 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 tnc_port[8]; /* a TCP port number. Default 8000. */ + + + +/* + * Maintain information about connections from users which we will call "sessions." + * It should be possible to have multiple users connected at the same time. + * + * This allows a "who" command to see who is currently connected and a place to keep + * possible state information for each user. + * + * Each combination of channel & callsign is a separate session. + * The same user (callsign), on a different channel, is a different session. + */ + + +struct session_s { + + char client_addr[AX25_MAX_ADDR_LEN]; // Callsign of other station. + // Clear to mean this table entry is not in use. + + int channel; // Radio channel. + + time_t login_time; // Time when connection established. + +// For the timing test. +// Send specified number of frames, optional length. +// When finished summarize with statistics. + + time_t tt_start_time; + volatile int tt_count; // Number to send. + int tt_length; // Bytes in info part. + int tt_next; // Next sequence to send. + + volatile int tx_queue_len; // Number in transmit queue. For flow control. +}; + +#define MAX_SESSIONS 12 + +static struct session_s session[MAX_SESSIONS]; + +static int find_session (int chan, char *addr, int create); +static void poll_timing_test (void); + + + +/*------------------------------------------------------------------ + * + * Name: main + * + * Purpose: Attach to Dire Wolf TNC, wait for requests from users. + * + * Usage: Described above. + * + *---------------------------------------------------------------*/ + + +int main (int argc, char *argv[]) +{ + int c; + char *p; + +#if __WIN32__ + setvbuf(stdout, NULL, _IONBF, 0); +#else + setlinebuf (stdout); +#endif + + memset (session, 0, sizeof(session)); + + strlcpy (tnc_hostname, "localhost", sizeof(tnc_hostname)); + strlcpy (tnc_port, "8000", sizeof(tnc_port)); + +/* + * Extract command line args. + */ + + while ((c = getopt (argc, argv, "h:p:")) != -1) { + switch (c) { + + case 'h': + strlcpy (tnc_hostname, optarg, sizeof(tnc_hostname)); + break; + + case 'p': + strlcpy (tnc_port, optarg, sizeof(tnc_port)); + break; + + default: + usage (); + } + } + + if (argv[optind] == NULL) { + usage (); + } + + strlcpy (mycall, argv[optind], sizeof(mycall)); + + // Force to upper case. + for (p = mycall; *p != '\0'; p++) { + if (islower(*p)) { + *p = toupper(*p); + } + } + +/* + * Establish a TCP socket to the network TNC. + * It starts up a thread, which listens for messages from the TNC, + * and calls the corresponding agw_cb_... callback functions. + * + * After attaching to the TNC, the specified init function is called. + * We pass it to the library, rather than doing it here, so it can + * repeated automatically if the TNC goes away and comes back again. + * We need to reestablish what it knows about the application. + */ + + if (agwlib_init (tnc_hostname, tnc_port, agwlib_G_ask_port_information) != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Could not attach to network TNC %s:%s.\n", tnc_hostname, tnc_port); + exit (EXIT_FAILURE); + } + + +/* + * Send command to ask what channels are available. + * The response will be handled by agw_cb_G_port_information. + */ +// FIXME: Need to do this again if we lose TNC and reattach to it. + + /// should happen automatically now. agwlib_G_ask_port_information (); + + + while (1) { + SLEEP_SEC(1); // other places based on 1 second assumption. + poll_timing_test (); + } + + +} /* end main */ + + + +static void poll_timing_test (void) +{ + int s; + for (s = 0; s < MAX_SESSIONS; s++) { + + if (session[s].tt_count == 0) { + continue; // nothing to do + } + else if (session[s].tt_next <= session[s].tt_count) { + int rem = session[s].tt_count - session[s].tt_next + 1; // remaining to send. + agwlib_Y_outstanding_frames_for_station (session[s].channel, mycall, session[s].client_addr); + SLEEP_MS(10); + if (session[s].tx_queue_len > 128) continue; // enough queued up for now. + if (rem > 64) rem = 64; // add no more than 64 at a time. + int i; + for (i = 0; i < rem; i++) { + char c = 'a'; + char stuff[AX25_MAX_INFO_LEN+2]; + snprintf (stuff, sizeof(stuff), "%06d ", session[s].tt_next); + int k; + for (k = strlen(stuff); k < session[s].tt_length - 1; k++) { + stuff[k] = c; + c++; + if (c == 'z' + 1) c = 'A'; + if (c == 'Z' + 1) c = '0'; + if (c == '9' + 1) c = 'a'; + } + stuff[k++] = '\r'; + stuff[k++] = '\0'; + agwlib_D_send_connected_data (session[s].channel, 0xF0, mycall, session[s].client_addr, strlen(stuff), stuff); + session[s].tt_next++; + } + } + else { + // All done queuing up the packets. + // Wait until they have all been sent and ack'ed by other end. + + agwlib_Y_outstanding_frames_for_station (session[s].channel, mycall, session[s].client_addr); + SLEEP_MS(10); + + if (session[s].tx_queue_len > 0) continue; // not done yet. + + int elapsed = time(NULL) - session[s].tt_start_time; + if (elapsed <= 0) elapsed = 1; // avoid divide by 0 + + int byte_count = session[s].tt_count * session[s].tt_length; + char summary[100]; + snprintf (summary, sizeof(summary), "%d bytes in %d seconds, %d bytes/sec, efficiency %d%% at 1200, %d%% at 9600.\r", + byte_count, elapsed, byte_count/elapsed, + byte_count * 8 * 100 / elapsed / 1200, + byte_count * 8 * 100 / elapsed / 9600); + + agwlib_D_send_connected_data (session[s].channel, 0xF0, mycall, session[s].client_addr, strlen(summary), summary); + session[s].tt_count = 0; // all done. + } + } + +} // end poll_timing_test + + + +/*------------------------------------------------------------------- + * + * Name: agw_cb_C_connection_received + * + * Purpose: Callback for the "connection received" command from the TNC. + * + * Inputs: chan - Radio channel, first is 0. + * + * call_from - Address of other station. + * + * call_to - Callsign I responded to. (could be an alias.) + * + * data_len - Length of data field. + * + * data - Should look something like this for incoming: + * *** CONNECTED to Station xxx\r + * + * Description: Add to the sessions table. + * + *--------------------------------------------------------------------*/ + +/*------------------------------------------------------------------- + * + * Name: on_C_connection_received + * + * Purpose: Callback for the "connection received" command from the TNC. + * + * Inputs: chan - Radio channel, first is 0. + * + * call_from - Address of other station. + * + * call_to - My call. + * In the case of an incoming connect request (i.e. to + * a server) this is the callsign I responded to. + * It is possible to define additional aliases and respond + * to any one of them. It would be possible to have a server + * that responds to multiple names and behaves differently + * depending on the name. + * + * incoming - true(1) if other station made connect request. + * false(0) if I made request and other statio accepted. + * + * data - Should look something like this for incoming: + * *** CONNECTED to Station xxx\r + * and ths for my request being accepted: + * *** CONNECTED With Station xxx\r + * + * session_id - Session id to be used in data transfer and + * other control functions related to this connection. + * Think of it like a file handle. Once it is open + * we usually don't care about the name anymore and + * and just refer to the handle. This is used to + * keep track of multiple connections at the same + * time. e.g. a server could be handling multiple + * clients at once on the same or different channels. + * + * Description: Add to the table of clients. + * + *--------------------------------------------------------------------*/ + + +// old void agw_cb_C_connection_received (int chan, char *call_from, char *call_to, int data_len, char *data) +void on_C_connection_received (int chan, char *call_from, char *call_to, int incoming, char *data) +{ + int s; + char *p; + char greeting[256]; + + + for (p = data; *p != '\0'; p++) { + if (! isprint(*p)) *p = '\0'; // Remove any \r character at end. + } + + s = find_session (chan, call_from, 1); + + if (s >= 0) { + + text_color_set(DW_COLOR_INFO); + dw_printf ("Begin session %d: %s\n", s, data); + +// Send greeting. + + snprintf (greeting, sizeof(greeting), "Welcome! Type ? for list of commands or HELP for details.\r"); + agwlib_D_send_connected_data (chan, 0xF0, mycall, call_from, strlen(greeting), greeting); + } + else { + + text_color_set(DW_COLOR_INFO); + dw_printf ("Too many users already: %s\n", data); + +// Sorry, too many users already. + + snprintf (greeting, sizeof(greeting), "Sorry, maximum number of users has been exceeded. Try again later.\r"); + agwlib_D_send_connected_data (chan, 0xF0, mycall, call_from, strlen(greeting), greeting); + + // FIXME: Ideally we'd want to wait until nothing in the outgoing queue + // to that station so we know the rejection message was received. + SLEEP_SEC (10); + agwlib_d_disconnect (chan, mycall, call_from); + } + +} /* end agw_cb_C_connection_received */ + + + +/*------------------------------------------------------------------- + * + * Name: agw_cb_d_disconnected + * + * Purpose: Process the "disconnected" command from the TNC. + * + * Inputs: chan - Radio channel. + * + * call_from - Address of other station. + * + * call_to - Callsign I responded to. (could be aliases.) + * + * data_len - Length of data field. + * + * data - Should look something like one of these: + * *** DISCONNECTED RETRYOUT With xxx\r + * *** DISCONNECTED From Station xxx\r + * + * Description: Remove from the sessions table. + * + *--------------------------------------------------------------------*/ + + + +void agw_cb_d_disconnected (int chan, char *call_from, char *call_to, int data_len, char *data) +{ + int s; + char *p; + + s = find_session (chan, call_from, 0); + + for (p = data; *p != '\0'; p++) { + if (! isprint(*p)) *p = '\0'; // Remove any \r character at end. + } + + text_color_set(DW_COLOR_INFO); + dw_printf ("End session %d: %s\n", s, data); + +// Remove from session table. + + if (s >= 0) { + memset (&(session[s]), 0, sizeof(struct session_s)); + } + +} /* end agw_cb_d_disconnected */ + + + +/*------------------------------------------------------------------- + * + * Name: agw_cb_D_connected_data + * + * Purpose: Process "connected ax.25 data" from the TNC. + * + * Inputs: chan - Radio channel. + * + * addr - Address of other station. + * + * msg - What the user sent us. Probably a command. + * + * Global In: tnc_sock - Socket for TNC. + * + * Description: Remove from the session table. + * + *--------------------------------------------------------------------*/ + + +void agw_cb_D_connected_data (int chan, char *call_from, char *call_to, int data_len, char *data) +{ + int s; + char *p; + char logit[AX25_MAX_INFO_LEN+100]; + char *pcmd; + char *save; + + s = find_session (chan, call_from, 0); + + for (p = data; *p != '\0'; p++) { + if (! isprint(*p)) *p = '\0'; // Remove any \r character at end. + } + + // TODO: Should timestamp to all output. + + snprintf (logit, sizeof(logit), "%d,%d,%s: %s\n", s, chan, call_from, data); + text_color_set(DW_COLOR_INFO); + dw_printf ("%s", logit); + + if (s < 0) { + // Uh oh. Data from some station when not connected. + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error. Incoming data, no corresponding session.\n"); + return; + } + +// Process the command from user. + + pcmd = strtok_r (data, " ", &save); + if (pcmd == NULL || strlen(pcmd) == 0) { + + char greeting[80]; + strlcpy (greeting, "Type ? for list of commands or HELP for details.\r", sizeof(greeting)); + agwlib_D_send_connected_data (chan, 0xF0, mycall, call_from, strlen(greeting), greeting); + return; + } + + if (strcasecmp(pcmd, "who") == 0) { + +// who - list people currently logged in. + + int n; + char greeting[80]; + + 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]) { + snprintf (greeting, sizeof(greeting), " %2d %d %-9s [time later]\r", + n, session[n].channel, session[n].client_addr); + agwlib_D_send_connected_data (chan, 0xF0, mycall, call_from, strlen(greeting), greeting); + } + } + } + else if (strcasecmp(pcmd, "test") == 0) { + +// test - timing test +// Send specified number of frames with optional length. + + char *pcount = strtok_r (NULL, " ", &save); + char *plength = strtok_r (NULL, " ", &save); + + session[s].tt_start_time = time(NULL); + session[s].tt_next = 1; + session[s].tt_length = 256; + session[s].tt_count = 1; + + if (plength != NULL) { + session[s].tt_length = atoi(plength); + if (session[s].tt_length < 16) session[s].tt_length = 16; + if (session[s].tt_length > AX25_MAX_INFO_LEN) session[s].tt_length = AX25_MAX_INFO_LEN; + } + if (pcount != NULL) { + session[s].tt_count = atoi(pcount); + } + + // The background polling will take it from here. + } + else if (strcasecmp(pcmd, "bye") == 0) { + +// bye - disconnect. + + char greeting[80]; + strlcpy (greeting, "Thank you folks for kindly droppin' in. Y'all come on back now, ya hear?\r", sizeof(greeting)); + agwlib_D_send_connected_data (chan, 0xF0, mycall, call_from, strlen(greeting), greeting); + // Ideally we'd want to wait until nothing in the outgoing queue + // to that station so we know the message was received. + SLEEP_SEC (10); + agwlib_d_disconnect (chan, mycall, call_from); + } + else if (strcasecmp(pcmd, "help") == 0 || strcasecmp(pcmd, "?") == 0) { + +// help. + + char greeting[80]; + strlcpy (greeting, "Help not yet available.\r", sizeof(greeting)); + agwlib_D_send_connected_data (chan, 0xF0, mycall, call_from, strlen(greeting), greeting); + } + else { + +// command not recognized. + + char greeting[80]; + strlcpy (greeting, "Invalid command. Type ? for list of commands or HELP for details.\r", sizeof(greeting)); + agwlib_D_send_connected_data (chan, 0xF0, mycall, call_from, strlen(greeting), greeting); + } + +} /* end agw_cb_D_connected_data */ + + + + +/*------------------------------------------------------------------- + * + * Name: agw_cb_G_port_information + * + * Purpose: Process the port information "radio channels available" response from the TNC. + * + * + * Inputs: num_chan_avail - Number of radio channels available. + * + * chan_descriptions - Array of string pointers to form "Port99 description". + * Port1 is channel 0. + * + *--------------------------------------------------------------------*/ + +void agw_cb_G_port_information (int num_chan_avail, char *chan_descriptions[]) +{ + char *p; + int n; + + text_color_set(DW_COLOR_INFO); + dw_printf("TNC has %d radio channel%s available:\n", num_chan_avail, (num_chan_avail != 1) ? "s" : ""); + + for (n = 0; n < num_chan_avail; n++) { + + p = chan_descriptions[n]; + + // Expecting something like this: "Port1 first soundcard mono" + + if (strncasecmp(p, "Port", 4) == 0 && isdigit(p[4])) { + + int chan = atoi(p+4) - 1; // "Port1" is our channel 0. + if (chan >= 0 && chan < MAX_CHANS) { + + char *desc = p + 4; + while (*desc != '\0' && (*desc == ' ' || isdigit(*desc))) { + desc++; + } + + text_color_set(DW_COLOR_INFO); + dw_printf(" Channel %d: %s\n", chan, desc); + + // Later? Use 'g' to get speed and maybe other properties? + // Though I'm not sure why we would care here. + +/* + * Send command to register my callsign for incoming connect requests. + */ + + agwlib_X_register_callsign (chan, mycall); + + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf("Radio channel number is out of bounds: %s\n", p); + } + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf("Radio channel description not in expected format: %s\n", p); + } + } + +} /* end agw_cb_G_port_information */ + + +/*------------------------------------------------------------------- + * + * Name: agw_cb_Y_outstanding_frames_for_station + * + * Purpose: Process the "disconnected" command from the TNC. + * + * Inputs: chan - Radio channel. + * + * call_from - Should be my call. + * + * call_to - Callsign of other station. + * + * frame_count + * + * Description: Remove from the sessions table. + * + *--------------------------------------------------------------------*/ + + + +void agw_cb_Y_outstanding_frames_for_station (int chan, char *call_from, char *call_to, int frame_count) +{ + int s; + + s = find_session (chan, call_to, 0); + + text_color_set(DW_COLOR_DEBUG); // FIXME temporary + dw_printf ("debug ----------------------> session %d, callback Y outstanding frame_count %d\n", s, frame_count); + +// Update the transmit queue length + + if (s >= 0) { + session[s].tx_queue_len = frame_count; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Oops! Did not expect to be here.\n"); + } + +} /* end agw_cb_Y_outstanding_frames_for_station */ + + + +/*------------------------------------------------------------------- + * + * Name: find_session + * + * Purpose: Given a channel number and address (callsign), find existing + * table entry or create a new one. + * + * Inputs: chan - Radio channel number. + * + * addr - Address of station contacting us. + * + * create - If true, try create a new entry if not already there. + * + * Returns: "session id" which is an index into "session" array or -1 for failure. + * + *--------------------------------------------------------------------*/ + +static int find_session (int chan, char *addr, int create) +{ + int i; + int s = -1; + +// Is it there already? + + +//#if DEBUG +// +// text_color_set(DW_COLOR_DEBUG); +// dw_printf("find_session (%d, %s, %d)\n", chan, addr, create); +//#endif + + for (i = 0; i < MAX_SESSIONS; i++) { + if (session[i].channel == chan && strcmp(session[i].client_addr, addr) == 0) { + s = i; + break; + } + } + + if (s >= 0) return (s); + + if (! create) return (-1); + +// No, and there is a request to add a new entry. +// See if we have any available space. + + s = -1; + for (i = 0; i < MAX_SESSIONS; i++) { + if (strlen(session[i].client_addr) == 0) { + s = i; + break; + } + } + + if (s < 0) return (-1); + + strlcpy (session[s].client_addr, addr, sizeof(session[s].client_addr)); + session[s].channel = chan; + session[s].login_time = time(NULL); + + return (s); + +} /* end find_session */ + + +/* end appserver.c */ diff --git a/aprs_tt.c b/src/aprs_tt.c similarity index 99% rename from aprs_tt.c rename to src/aprs_tt.c index cf00cc8..0543f8a 100644 --- a/aprs_tt.c +++ b/src/aprs_tt.c @@ -422,7 +422,7 @@ void aprs_tt_sequence (int chan, char *msg) * Anything from script, above, will override other predefined responses. */ - char audible_response[1000]; + char audible_response[sizeof(script_response) + 16]; snprintf (audible_response, sizeof(audible_response), "APRSTT>%s:%s", @@ -1708,7 +1708,7 @@ static void raw_tt_data_to_app (int chan, char *msg) alevel.mark = -2; alevel.space = -2; - dlq_rec_frame (chan, -1, 0, pp, alevel, RETRY_NONE, "tt"); + dlq_rec_frame (chan, -1, 0, pp, alevel, 0, RETRY_NONE, "tt"); } else { text_color_set(DW_COLOR_ERROR); diff --git a/aprs_tt.h b/src/aprs_tt.h similarity index 100% rename from aprs_tt.h rename to src/aprs_tt.h diff --git a/atest.c b/src/atest.c similarity index 61% rename from atest.c rename to src/atest.c index 701b613..5c19775 100644 --- a/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 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2019 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 @@ -81,7 +81,8 @@ #include "dlq.h" #include "ptt.h" #include "dtime_now.h" - +#include "fx25.h" +#include "hdlc_rec.h" #if 0 /* Typical but not flexible enough. */ @@ -137,9 +138,13 @@ static struct { static FILE *fp; static int e_o_f; -static int packets_decoded = 0; +static int packets_decoded_one = 0; +static int packets_decoded_total = 0; static int decimate = 0; /* Reduce that sampling rate if set. */ - /* 1 = normal, 2 = half, etc. */ + /* 1 = normal, 2 = half, 3 = 1/3, etc. */ + +static int upsample = 0; /* Upsample for G3RUH decoder. */ + /* Non-zero will override the default. */ static struct audio_s my_audio_config; @@ -174,6 +179,20 @@ static int sample_number = -1; /* Sample number from the file. */ /* Use to print timestamp, relative to beginning */ /* of file, when frame was decoded. */ +// command line options. + +static int B_opt = DEFAULT_BAUD; // Bits per second. Need to change all baud references to bps. +static int g_opt = 0; // G3RUH modem regardless of speed. +static int j_opt = 0; /* 2400 bps PSK compatible with direwolf <= 1.5 */ +static int J_opt = 0; /* 2400 bps PSK compatible MFJ-2400 and maybe others. */ +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 dcd_count = 0; +static int dcd_missing_errors = 0; + + int main (int argc, char *argv[]) { @@ -182,7 +201,8 @@ int main (int argc, char *argv[]) int channel; double start_time; // Time when we started so we can measure elapsed time. - double duration; // Length of the audio file in seconds. + double one_filetime = 0; // Length of one audio file in seconds. + double total_filetime = 0; // Length of all audio files in seconds. double elapsed; // Time it took us to process it. @@ -207,49 +227,6 @@ int main (int argc, char *argv[]) my_audio_config.adev[0].samples_per_sec = DEFAULT_SAMPLES_PER_SEC; my_audio_config.adev[0].bits_per_sample = DEFAULT_BITS_PER_SAMPLE; - // Results v0.9: - // - // fix_bits = 0 971 packets, 69 sec - // fix_bits = SINGLE 990 64 - // fix_bits = DOUBLE 992 65 - // fix_bits = TRIPLE 992 67 - // fix_bits = TWO_SEP 1004 476 - - // Essentially no difference in time for those with order N time. - // Time increases greatly for the one with order N^2 time. - - - // Results: version 1.1, decoder C, my_audio_config.fix_bits = RETRY_MAX - 1 - // - // 971 NONE - // +19 SINGLE - // +2 DOUBLE - // +12 TWO_SEP - // +3 REMOVE_MANY - // ---- - // 1007 Total in 1008 sec. More than twice as long as earlier version. - - // Results: version 1.1, decoders ABC, my_audio_config.fix_bits = RETRY_MAX - 1 - // - // 976 NONE - // +21 SINGLE - // +1 DOUBLE - // +22 TWO_SEP - // +1 MANY - // +3 REMOVE_MANY - // ---- - // 1024 Total in 2042 sec. - // About 34 minutes of CPU time for a roughly 40 minute CD. - // Many computers wouldn't be able to keep up. - - // The SINGLE and TWO_SEP techniques are the most effective. - // Should we reorder the enum values so that TWO_SEP - // comes after SINGLE? That way "FIX_BITS 2" would - // use the two most productive techniques and not waste - // time on the others. People with plenty of CPU power - // to spare can still specify larger numbers for the other - // techniques with less return on investment. - for (channel=0; channel MAX_BAUD) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD); - exit (EXIT_FAILURE); - } - - /* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */ - /* that need to be kept in sync. Maybe it could be a common function someday. */ - - if (my_audio_config.achan[0].baud == 100) { - 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)); + /* Also implies modem type based on speed. */ + /* Special case "AIS" rather than number. */ + if (strcasecmp(optarg, "AIS") == 0) { + B_opt = 12345; // See special case below. } - else if (my_audio_config.achan[0].baud < 600) { - 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)); - } - else if (my_audio_config.achan[0].baud < 1800) { - my_audio_config.achan[0].modem_type = MODEM_AFSK; - my_audio_config.achan[0].mark_freq = DEFAULT_MARK_FREQ; - my_audio_config.achan[0].space_freq = DEFAULT_SPACE_FREQ; - // Should default to E+ or something similar later. - } - else if (my_audio_config.achan[0].baud < 3600) { - my_audio_config.achan[0].modem_type = MODEM_QPSK; - my_audio_config.achan[0].mark_freq = 0; - my_audio_config.achan[0].space_freq = 0; - strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles)); - dw_printf ("Using V.26 QPSK rather than AFSK.\n"); - } - else if (my_audio_config.achan[0].baud < 7200) { - my_audio_config.achan[0].modem_type = MODEM_8PSK; - my_audio_config.achan[0].mark_freq = 0; - my_audio_config.achan[0].space_freq = 0; - strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles)); - dw_printf ("Using V.27 8PSK rather than AFSK.\n"); + else if (strcasecmp(optarg, "EAS") == 0) { + B_opt = 23456; // See special case below. } else { - my_audio_config.achan[0].modem_type = MODEM_SCRAMBLE; - my_audio_config.achan[0].mark_freq = 0; - my_audio_config.achan[0].space_freq = 0; - strlcpy (my_audio_config.achan[0].profiles, " ", sizeof(my_audio_config.achan[0].profiles)); // avoid getting default later. - dw_printf ("Using scrambled baseband signal rather than AFSK.\n"); + B_opt = atoi(optarg); } break; + case 'g': /* -G Force G3RUH regardless of speed. */ + + g_opt = 1; + break; + + case 'j': /* -j V.26 compatible with earlier direwolf. */ + + j_opt = 1; + break; + + case 'J': /* -J V.26 compatible with MFJ-2400. */ + + J_opt = 1; + break; + case 'P': /* -P for modem profile. */ - dw_printf ("Demodulator profile set to \"%s\"\n", optarg); - strlcpy (my_audio_config.achan[0].profiles, optarg, sizeof(my_audio_config.achan[0].profiles)); + // Wait until after other options processed. + strlcpy (P_opt, optarg, sizeof(P_opt)); break; case 'D': /* -D reduce sampling rate for lower CPU usage. */ @@ -373,7 +320,24 @@ int main (int argc, char *argv[]) my_audio_config.achan[0].decimate = decimate; break; - case 'F': /* -D set "fix bits" level. */ + case 'U': /* -U upsample for G3RUH to improve performance */ + /* when the sample rate to baud ratio is low. */ + /* Actually it is set automatically and this will */ + /* override the normal calculation. */ + + upsample = atoi(optarg); + + dw_printf ("Multiply audio sample rate by %d\n", upsample); + if (upsample < 1 || upsample > 4) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Unreasonable value for -U.\n"); + exit (EXIT_FAILURE); + } + dw_printf ("Multiply audio sample rate by %d\n", upsample); + my_audio_config.achan[0].upsample = upsample; + break; + + case 'F': /* -F set "fix bits" level. */ my_audio_config.achan[0].fix_bits = atoi(optarg); @@ -409,6 +373,27 @@ int main (int argc, char *argv[]) decode_only = 2; break; + case 'h': /* Hexadecimal display. */ + + h_opt = 1; + break; + + case 'e': /* Receive Bit Error Rate (BER). */ + + my_audio_config.recv_ber = atof(optarg); + break; + + case 'd': /* Debug message options. */ + + for (char *p=optarg; *p!='\0'; p++) { + switch (*p) { + case 'x': d_x_opt++; break; // FX.25 + case 'o': d_o_opt++; break; // DCD output control + default: break; + } + } + break; + case '?': /* Unknown option message was already printed. */ @@ -424,6 +409,124 @@ int main (int argc, char *argv[]) } } +/* + * Set modem type based on data rate. + * (Could be overridden by -g, -j, or -J later.) + */ + /* 300 implies 1600/1800 AFSK. */ + /* 1200 implies 1200/2200 AFSK. */ + /* 2400 implies V.26 QPSK. */ + /* 4800 implies V.27 8PSK. */ + /* 9600 implies G3RUH baseband scrambled. */ + + my_audio_config.achan[0].baud = B_opt; + + if (my_audio_config.achan[0].baud < MIN_BAUD || my_audio_config.achan[0].baud > MAX_BAUD) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD); + exit (EXIT_FAILURE); + } + + /* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */ + /* that need to be kept in sync. Maybe it could be a common function someday. */ + + if (my_audio_config.achan[0].baud == 100) { + 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)); + } + else if (my_audio_config.achan[0].baud < 600) { + 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)); + } + else if (my_audio_config.achan[0].baud < 1800) { + my_audio_config.achan[0].modem_type = MODEM_AFSK; + my_audio_config.achan[0].mark_freq = DEFAULT_MARK_FREQ; + my_audio_config.achan[0].space_freq = DEFAULT_SPACE_FREQ; + // Should default to E+ or something similar later. + } + else if (my_audio_config.achan[0].baud < 3600) { + my_audio_config.achan[0].modem_type = MODEM_QPSK; + my_audio_config.achan[0].mark_freq = 0; + my_audio_config.achan[0].space_freq = 0; + strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles)); + } + else if (my_audio_config.achan[0].baud < 7200) { + my_audio_config.achan[0].modem_type = MODEM_8PSK; + my_audio_config.achan[0].mark_freq = 0; + my_audio_config.achan[0].space_freq = 0; + strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles)); + } + else if (my_audio_config.achan[0].baud == 12345) { + my_audio_config.achan[0].modem_type = MODEM_AIS; + my_audio_config.achan[0].baud = 9600; + my_audio_config.achan[0].mark_freq = 0; + my_audio_config.achan[0].space_freq = 0; + strlcpy (my_audio_config.achan[0].profiles, " ", sizeof(my_audio_config.achan[0].profiles)); // avoid getting default later. + } + else if (my_audio_config.achan[0].baud == 23456) { + my_audio_config.achan[0].modem_type = MODEM_EAS; + my_audio_config.achan[0].baud = 521; // Actually 520.83 but we have an integer field here. + // Will make more precise in afsk demod init. + 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)); + } + else { + my_audio_config.achan[0].modem_type = MODEM_SCRAMBLE; + my_audio_config.achan[0].mark_freq = 0; + my_audio_config.achan[0].space_freq = 0; + strlcpy (my_audio_config.achan[0].profiles, " ", sizeof(my_audio_config.achan[0].profiles)); // avoid getting default later. + } + +/* + * -g option means force g3RUH regardless of speed. + */ + + if (g_opt) { + my_audio_config.achan[0].modem_type = MODEM_SCRAMBLE; + my_audio_config.achan[0].mark_freq = 0; + my_audio_config.achan[0].space_freq = 0; + strlcpy (my_audio_config.achan[0].profiles, " ", sizeof(my_audio_config.achan[0].profiles)); // avoid getting default later. + } + +/* + * We have two different incompatible flavors of V.26. + */ + if (j_opt) { + + // V.26 compatible with earlier versions of direwolf. + // Example: -B 2400 -j or simply -j + + my_audio_config.achan[0].v26_alternative = V26_A; + my_audio_config.achan[0].modem_type = MODEM_QPSK; + my_audio_config.achan[0].mark_freq = 0; + my_audio_config.achan[0].space_freq = 0; + my_audio_config.achan[0].baud = 2400; + strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles)); + } + if (J_opt) { + + // V.26 compatible with MFJ and maybe others. + // Example: -B 2400 -J or simply -J + + my_audio_config.achan[0].v26_alternative = V26_B; + my_audio_config.achan[0].modem_type = MODEM_QPSK; + my_audio_config.achan[0].mark_freq = 0; + my_audio_config.achan[0].space_freq = 0; + my_audio_config.achan[0].baud = 2400; + strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles)); + } + + // Needs to be after -B, -j, -J. + if (strlen(P_opt) > 0) { + dw_printf ("Demodulator profile set to \"%s\"\n", P_opt); + strlcpy (my_audio_config.achan[0].profiles, P_opt, sizeof(my_audio_config.achan[0].profiles)); + } + memcpy (&my_audio_config.achan[1], &my_audio_config.achan[0], sizeof(my_audio_config.achan[0])); @@ -433,6 +536,12 @@ int main (int argc, char *argv[]) usage (); } + fx25_init (d_x_opt); + + start_time = dtime_now(); + + while (optind < argc) { + fp = fopen(argv[optind], "rb"); if (fp == NULL) { text_color_set(DW_COLOR_ERROR); @@ -441,8 +550,6 @@ int main (int argc, char *argv[]) exit (EXIT_FAILURE); } - start_time = dtime_now(); - /* * Read the file header. * Doesn't handle all possible cases but good enough for our purposes. @@ -507,25 +614,31 @@ 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].valid = 1; - if (format.nchannels == 2) my_audio_config.achan[1].valid = 1; + my_audio_config.achan[0].medium = MEDIUM_RADIO; + if (format.nchannels == 2) { + my_audio_config.achan[1].medium = MEDIUM_RADIO; + } text_color_set(DW_COLOR_INFO); dw_printf ("%d samples per second. %d bits per sample. %d audio channels.\n", my_audio_config.adev[0].samples_per_sec, my_audio_config.adev[0].bits_per_sample, my_audio_config.adev[0].num_channels); - duration = (double) wav_data.datasize / + one_filetime = (double) wav_data.datasize / ((my_audio_config.adev[0].bits_per_sample / 8) * my_audio_config.adev[0].num_channels * my_audio_config.adev[0].samples_per_sec); + total_filetime += one_filetime; + dw_printf ("%d audio bytes in file. Duration = %.1f seconds.\n", (int)(wav_data.datasize), - duration); + one_filetime); dw_printf ("Fix Bits level = %d\n", my_audio_config.achan[0].fix_bits); /* * Initialize the AFSK demodulator and HDLC decoder. + * Needs to be done for each file because they could have different sample rates. */ multi_modem_init (&my_audio_config); + packets_decoded_one = 0; e_o_f = 0; @@ -578,17 +691,27 @@ int main (int argc, char *argv[]) } #endif + dw_printf ("%d from %s\n", packets_decoded_one, argv[optind]); + packets_decoded_total += packets_decoded_one; + + fclose (fp); + optind++; + } elapsed = dtime_now() - start_time; - dw_printf ("%d packets decoded in %.3f seconds. %.1f x realtime\n", packets_decoded, elapsed, duration/elapsed); + 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); + } - if (error_if_less_than != -1 && packets_decoded < error_if_less_than) { + if (error_if_less_than != -1 && packets_decoded_total < error_if_less_than) { text_color_set(DW_COLOR_ERROR); dw_printf ("\n * * * TEST FAILED: number decoded is less than %d * * * \n", error_if_less_than); exit (EXIT_FAILURE); } - if (error_if_greater_than != -1 && packets_decoded > error_if_greater_than) { + if (error_if_greater_than != -1 && packets_decoded_total > error_if_greater_than) { text_color_set(DW_COLOR_ERROR); dw_printf ("\n * * * TEST FAILED: number decoded is greater than %d * * * \n", error_if_greater_than); exit (EXIT_FAILURE); @@ -625,32 +748,11 @@ int audio_get (int a) -/* - * Rather than queuing up frames with bad FCS, - * try to fix them immediately. - */ - -void rdq_append (rrbb_t rrbb) -{ - int chan, subchan, slice; - alevel_t alevel; - - - chan = rrbb_get_chan(rrbb); - subchan = rrbb_get_subchan(rrbb); - slice = rrbb_get_slice(rrbb); - alevel = rrbb_get_audio_level(rrbb); - - hdlc_rec2_try_to_fix_later (rrbb, chan, subchan, slice, alevel); - rrbb_delete (rrbb); -} - - /* * This is called when we have a good frame. */ -void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum) +void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, int is_fx25, retry_t retries, char *spectrum) { char stemp[500]; @@ -660,7 +762,8 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev char heard[AX25_MAX_ADDR_LEN]; char alevel_text[AX25_ALEVEL_TO_TEXT_SIZE]; - packets_decoded++; + packets_decoded_one++; + if ( ! hdlc_rec_data_detect_any(chan)) dcd_missing_errors++; ax25_format_addrs (pp, stemp); @@ -686,7 +789,7 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev text_color_set(DW_COLOR_DEBUG); dw_printf ("\n"); - dw_printf("DECODED[%d] ", packets_decoded ); + dw_printf("DECODED[%d] ", packets_decoded_one ); /* Insert time stamp relative to start of file. */ @@ -694,7 +797,7 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev int min = (int)(sec / 60.); sec -= min * 60; - dw_printf ("%d:%07.4f ", min, sec); + dw_printf ("%d:%06.3f ", min, sec); if (h != AX25_SOURCE) { dw_printf ("Digipeater "); @@ -704,7 +807,11 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev if (my_audio_config.achan[chan].fix_bits == RETRY_NONE && my_audio_config.achan[chan].passall == 0) { dw_printf ("%s audio level = %s %s\n", heard, alevel_text, spectrum); } + else if (is_fx25) { + dw_printf ("%s audio level = %s %s\n", heard, alevel_text, spectrum); + } else { + assert (retries >= RETRY_NONE && retries <= RETRY_MAX); dw_printf ("%s audio level = %s [%s] %s\n", heard, alevel_text, retry_text[(int)retries], spectrum); } @@ -749,6 +856,21 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev ax25_safe_print ((char *)pinfo, info_len, 0); dw_printf ("\n"); +/* + * -h option for hexadecimal display. (new in 1.6) + */ + + if (h_opt) { + + text_color_set(DW_COLOR_DEBUG); + dw_printf ("------\n"); + ax25_hex_dump (pp); + dw_printf ("------\n"); + } + + + + #if 1 // temp experiment TODO: remove this. #include "decode_aprs.h" @@ -774,6 +896,38 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev void ptt_set (int ot, int chan, int ptt_signal) { + // Should only get here for DCD output control. + static double dcd_start_time[MAX_CHANS]; + + if (d_o_opt) { + double t = (double)sample_number / my_audio_config.adev[0].samples_per_sec; + double sec1, sec2; + int min1, min2; + + text_color_set(DW_COLOR_INFO); + + if (ptt_signal) { + //sec1 = t; + //min1 = (int)(sec1 / 60.); + //sec1 -= min1 * 60; + //dw_printf ("DCD[%d] = ON %d:%06.3f\n", chan, min1, sec1); + dcd_count++; + dcd_start_time[chan] = t; + } + else { + //dw_printf ("DCD[%d] = off %d:%06.3f %3.0f\n", chan, min, sec, (t - dcd_start_time[chan]) * 1000.); + + sec1 = dcd_start_time[chan]; + min1 = (int)(sec1 / 60.); + sec1 -= min1 * 60; + + sec2 = t; + min2 = (int)(sec2 / 60.); + sec2 -= min2 * 60; + + dw_printf ("DCD[%d] %d:%06.3f - %d:%06.3f = %3.0f\n", chan, min1, sec1, min2, sec2, (t - dcd_start_time[chan]) * 1000.); + } + } return; } @@ -796,19 +950,35 @@ static void usage (void) { dw_printf (" atest [ options ] wav-file-in\n"); dw_printf ("\n"); dw_printf (" -B n Bits/second for data. Proper modem automatically selected for speed.\n"); - dw_printf (" 300 baud uses 1600/1800 Hz AFSK.\n"); - dw_printf (" 1200 (default) baud uses 1200/2200 Hz AFSK.\n"); - dw_printf (" 9600 baud uses K9NG/G2RUH standard.\n"); + dw_printf (" 300 bps defaults to AFSK tones of 1600 & 1800.\n"); + dw_printf (" 1200 bps uses AFSK tones of 1200 & 2200.\n"); + dw_printf (" 2400 bps uses QPSK based on V.26 standard.\n"); + dw_printf (" 4800 bps uses 8PSK based on V.27 standard.\n"); + dw_printf (" 9600 bps and up uses K9NG/G3RUH standard.\n"); + dw_printf (" AIS for ship Automatic Identification System.\n"); + dw_printf (" EAS for Emergency Alert System (EAS) Specific Area Message Encoding (SAME).\n"); + dw_printf ("\n"); + dw_printf (" -g Use G3RUH modem rather rather than default for data rate.\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 ("\n"); dw_printf (" -D n Divide audio sample rate by n.\n"); dw_printf ("\n"); + dw_printf (" -h Print frame contents as hexadecimal bytes.\n"); + dw_printf ("\n"); dw_printf (" -F n Amount of effort to try fixing frames with an invalid CRC. \n"); dw_printf (" 0 (default) = consider only correct frames. \n"); dw_printf (" 1 = Try to fix only a single bit. \n"); dw_printf (" more = Try modifying more bits to get a good CRC.\n"); dw_printf ("\n"); - dw_printf (" -P m Select the demodulator type such as A, B, C, D (default for 300 baud),\n"); - dw_printf (" E (default for 1200 baud), F, A+, B+, C+, D+, E+, F+.\n"); + dw_printf (" -d x Debug information for FX.25. Repeat for more detail.\n"); + dw_printf ("\n"); + dw_printf (" -L Error if less than this number decoded.\n"); + dw_printf ("\n"); + dw_printf (" -G Error if greater than this number decoded.\n"); + dw_printf ("\n"); + dw_printf (" -P m Select the demodulator type such as D (default for 300 bps),\n"); + dw_printf (" E+ (default for 1200 bps), PQRS for 2400 bps, etc.\n"); dw_printf ("\n"); dw_printf (" -0 Use channel 0 (left) of stereo audio (default).\n"); dw_printf (" -1 use channel 1 (right) of stereo audio.\n"); @@ -831,11 +1001,11 @@ static void usage (void) { dw_printf (" bits per second.\n"); dw_printf ("\n"); dw_printf (" atest 02_Track_2.wav\n"); - dw_printf (" atest -P C+ 02_Track_2.wav\n"); + dw_printf (" atest -P E- 02_Track_2.wav\n"); dw_printf (" atest -F 1 02_Track_2.wav\n"); - dw_printf (" atest -P C+ -F 1 02_Track_2.wav\n"); + dw_printf (" atest -P E- -F 1 02_Track_2.wav\n"); dw_printf ("\n"); - dw_printf (" Try different combinations of options to find the best decoding\n"); + dw_printf (" Try different combinations of options to compare decoding\n"); dw_printf (" performance.\n"); exit (1); diff --git a/audio.c b/src/audio.c similarity index 98% rename from audio.c rename to src/audio.c index f66971d..613be06 100644 --- a/audio.c +++ b/src/audio.c @@ -1204,6 +1204,17 @@ int audio_flush (int a) snd_pcm_recover (adev[a].audio_out_handle, k, 1); } + else if (k == -ESTRPIPE) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Driver suspended, recovering\n"); + snd_pcm_recover(adev[a].audio_out_handle, k, 1); + } + else if (k == -EBADFD) { + k = snd_pcm_prepare (adev[a].audio_out_handle); + if(k < 0) { + dw_printf ("Error preparing after bad state: %s\n", snd_strerror(k)); + } + } else if (k < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Audio write error: %s\n", snd_strerror(k)); @@ -1211,7 +1222,10 @@ int audio_flush (int a) /* Some other error condition. */ /* Try again. What do we have to lose? */ - snd_pcm_recover (adev[a].audio_out_handle, k, 1); + k = snd_pcm_prepare (adev[a].audio_out_handle); + if(k < 0) { + dw_printf ("Error preparing after error: %s\n", snd_strerror(k)); + } } else if (k != adev[a].outbuf_len / adev[a].bytes_per_frame) { text_color_set(DW_COLOR_ERROR); diff --git a/audio.h b/src/audio.h similarity index 81% rename from audio.h rename to src/audio.h index 9ff86c5..61dec9d 100644 --- a/audio.h +++ b/src/audio.h @@ -18,7 +18,7 @@ #include "direwolf.h" /* for MAX_CHANS used throughout the application. */ #include "ax25_pad.h" /* for AX25_MAX_ADDR_LEN */ - +#include "version.h" /* @@ -53,6 +53,13 @@ typedef enum retry_e { RETRY_INVERT_TWO_SEP=4, RETRY_MAX = 5} retry_t; +// Type of communication medium associated with the channel. + +enum medium_e { MEDIUM_NONE = 0, // Channel is not valid for use. + MEDIUM_RADIO, // Internal modem for radio. + MEDIUM_IGATE, // Access IGate as ordinary channel. + MEDIUM_NETTNC }; // Remote network TNC. (possible future) + typedef enum sanity_e { SANITY_APRS, SANITY_AX25, SANITY_NONE } sanity_t; @@ -97,24 +104,51 @@ struct audio_s { int recv_error_rate; /* Similar but the % probablity of dropping a received frame. */ + 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. */ + /* 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. */ + + int fx25_auto_enable; /* Turn on FX.25 for current connected mode session */ + /* under poor conditions. */ + /* Set to 0 to disable feature. */ + /* 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. */ + char timestamp_format[40]; /* -T option */ /* Precede received & transmitted frames with timestamp. */ /* Command line option uses "strftime" format string. */ - /* Properties for each audio channel, common to receive and transmit. */ + /* 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. */ + struct achan_param_s { - int valid; /* Is this channel valid? */ + // 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. + // MEDIUM_RADIO for internal modem. (only possibility earlier) + // MEDIUM_IGATE allows application access to IGate. + char mycall[AX25_MAX_ADDR_LEN]; /* Call associated with this radio channel. */ /* Could all be the same or different. */ - enum modem_t { MODEM_AFSK, MODEM_BASEBAND, MODEM_SCRAMBLE, MODEM_QPSK, MODEM_8PSK, MODEM_OFF } modem_type; + enum modem_t { MODEM_AFSK, MODEM_BASEBAND, MODEM_SCRAMBLE, MODEM_QPSK, MODEM_8PSK, MODEM_OFF, MODEM_16_QAM, MODEM_64_QAM, MODEM_AIS, MODEM_EAS } modem_type; /* Usual AFSK. */ /* Baseband signal. Not used yet. */ @@ -122,6 +156,15 @@ struct audio_s { /* Might try MFJ-2400 / CCITT v.26 / Bell 201 someday. */ /* No modem. Might want this for DTMF only channel. */ + enum v26_e { V26_UNSPECIFIED=0, V26_A, V26_B } v26_alternative; + + // Original implementaion 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 + // pick one explicitly. + +#define V26_DEFAULT V26_B enum dtmf_decode_t { DTMF_DECODE_OFF, DTMF_DECODE_ON } dtmf_decode; @@ -138,8 +181,7 @@ struct audio_s { int decimate; /* Reduce AFSK sample rate by this factor to */ /* decrease computational requirements. */ - int interleave; /* If > 1, interleave samples among multiple decoders. */ - /* Quick hack for experiment. */ + int upsample; /* Upsample by this factor for G3RUH. */ int mark_freq; /* Two tones for AFSK modulation, in Hz. */ int space_freq; /* Standard tones are 1200 and 2200 for 1200 baud. */ @@ -236,6 +278,8 @@ struct audio_s { #ifdef USE_HAMLIB int ptt_model; /* HAMLIB model. -1 for AUTO. 2 for rigctld. Others are radio model. */ + int ptt_rate; /* Serial port speed when using hamlib CAT control for PTT. */ + /* If zero, hamlib will come up with a default for pariticular rig. */ #endif } octrl[NUM_OCTYPES]; @@ -300,15 +344,18 @@ struct audio_s { }; -#if __WIN32__ || __APPLE__ +#if __WIN32__ #define DEFAULT_ADEVICE "" /* Windows: Empty string = default audio device. */ -#else -#if USE_ALSA +#elif __APPLE__ +#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. */ #else -#define DEFAULT_ADEVICE "/dev/dsp" /* First audio device for OSS. */ +#define DEFAULT_ADEVICE "/dev/dsp" /* First audio device for OSS. (FreeBSD) */ #endif -#endif + /* diff --git a/audio_portaudio.c b/src/audio_portaudio.c similarity index 100% rename from audio_portaudio.c rename to src/audio_portaudio.c diff --git a/audio_stats.c b/src/audio_stats.c similarity index 100% rename from audio_stats.c rename to src/audio_stats.c diff --git a/audio_stats.h b/src/audio_stats.h similarity index 100% rename from audio_stats.h rename to src/audio_stats.h diff --git a/audio_win.c b/src/audio_win.c similarity index 98% rename from audio_win.c rename to src/audio_win.c index 92094a8..2183d10 100644 --- a/audio_win.c +++ b/src/audio_win.c @@ -222,8 +222,8 @@ static struct adev_s { *----------------------------------------------------------------*/ -static void CALLBACK in_callback (HWAVEIN handle, UINT msg, DWORD instance, DWORD param1, DWORD param2); -static void CALLBACK out_callback (HWAVEOUT handle, UINT msg, DWORD instance, DWORD param1, DWORD param2); +static void CALLBACK in_callback (HWAVEIN handle, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2); +static void CALLBACK out_callback (HWAVEOUT handle, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2); int audio_open (struct audio_s *pa) { @@ -684,24 +684,25 @@ int audio_open (struct audio_s *pa) * Called when input audio block is ready. */ -static void CALLBACK in_callback (HWAVEIN handle, UINT msg, DWORD instance, DWORD param1, DWORD param2) +static void CALLBACK in_callback (HWAVEIN handle, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) { + //dw_printf ("in_callback, handle = %p, msg = %d, instance = %I64d\n", handle, msg, instance); + int a = instance; - -//dw_printf ("in_callback, handle = %d, a = %d\n", (int)handle, a); - assert (a >= 0 && a < MAX_ADEVS); struct adev_s *A = &(adev[a]); - if (msg == WIM_DATA) { WAVEHDR *p = (WAVEHDR*)param1; - p->dwUser = -1; /* needs to be unprepared. */ + p->dwUser = 0x5a5a5a5a; /* needs to be unprepared. */ + /* dwUser can be 32 or 64 bit unsigned int. */ p->lpNext = NULL; + // dw_printf ("dwBytesRecorded = %ld\n", p->dwBytesRecorded); + EnterCriticalSection (&(A->in_cs)); if (A->in_headp == NULL) { @@ -726,7 +727,7 @@ static void CALLBACK in_callback (HWAVEIN handle, UINT msg, DWORD instance, DWOR */ -static void CALLBACK out_callback (HWAVEOUT handle, UINT msg, DWORD instance, DWORD param1, DWORD param2) +static void CALLBACK out_callback (HWAVEOUT handle, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) { if (msg == WOM_DONE) { @@ -807,7 +808,7 @@ int audio_get (int a) p = (WAVEHDR*)(A->in_headp); /* no need to be volatile at this point */ - if (p->dwUser == (DWORD)(-1)) { + if (p->dwUser == 0x5a5a5a5a) { // dwUser can be 32 or bit unsigned. waveInUnprepareHeader(A->audio_in_handle, p, sizeof(WAVEHDR)); p->dwUser = 0; /* Index for next byte. */ diff --git a/ax25_link.c b/src/ax25_link.c similarity index 98% rename from ax25_link.c rename to src/ax25_link.c index 565c11e..041066e 100644 --- a/ax25_link.c +++ b/src/ax25_link.c @@ -887,6 +887,7 @@ static ax25_dlsm_t *get_link_handle (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LE // // dl_connect_request // dl_disconnect_request +// dl_outstanding_frames_request - (mine) Ask about outgoing queue for a link. // dl_data_request - send connected data // dl_unit_data_request - not implemented. APRS & KISS bypass this // dl_flow_off - not implemented. Not in AGW API. @@ -1156,6 +1157,15 @@ 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: + * "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." + * "The segmenter/reassembler procedure is only enabled if both stations on the + * link are using AX.25 version 2.2 or higher." + * + * The Segmenter Ready State SDL has no decision based on protocol version. + * *------------------------------------------------------------------------------*/ static void data_request_good_size (ax25_dlsm_t *S, cdata_t *txdata); @@ -1165,8 +1175,6 @@ void dl_data_request (dlq_item_t *E) { ax25_dlsm_t *S; int ok_to_create = 1; - int nseg_to_follow; - int orig_offset, remaining_len; S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create); @@ -1184,6 +1192,39 @@ void dl_data_request (dlq_item_t *E) return; } +#define DIVROUNDUP(a,b) (((a)+(b)-1) / (b)) + +// Erratum: Don't do V2.2 segmentation for a V2.0 link. +// In this case, we can just split it into multiple frames not exceeding the specified max size. +// Hopefully the receiving end treats it like a stream and doesn't care about length of each frame. + + if (S->modulo == 8) { + + int num_frames = 0; + int remaining_len = E->txdata->len; + int offset = 0; + + while (remaining_len > 0) { + int this_len = MIN(remaining_len, S->n1_paclen); + + cdata_t *new_txdata = cdata_new(E->txdata->pid, E->txdata->data + offset, this_len); + data_request_good_size (S, new_txdata); + + offset += this_len; + remaining_len -= this_len; + num_frames++; + } + + if (num_frames != DIVROUNDUP(E->txdata->len, S->n1_paclen) || remaining_len != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("INTERNAL ERROR, Segmentation line %d, data length = %d, N1 = %d, num frames = %d, remaining len = %d\n", + __LINE__, E->txdata->len, S->n1_paclen, num_frames, remaining_len); + } + cdata_delete (E->txdata); + E->txdata = NULL; + return; + } + // More interesting case. // It is too large to fit in one frame so we segment it. @@ -1250,7 +1291,7 @@ void dl_data_request (dlq_item_t *E) // We will decrement this before putting it in the frame so the first // will have one less than this number. - nseg_to_follow = DIVROUNDUP(E->txdata->len + 1, S->n1_paclen - 1); + int nseg_to_follow = DIVROUNDUP(E->txdata->len + 1, S->n1_paclen - 1); if (nseg_to_follow < 2 || nseg_to_follow > 128) { text_color_set(DW_COLOR_ERROR); @@ -1261,8 +1302,8 @@ void dl_data_request (dlq_item_t *E) return; } - orig_offset = 0; - remaining_len = E->txdata->len; + int orig_offset = 0; + int remaining_len = E->txdata->len; // First segment. @@ -1505,6 +1546,80 @@ void dl_unregister_callsign (dlq_item_t *E) +/*------------------------------------------------------------------------------ + * + * Name: dl_outstanding_frames_request + * + * Purpose: Client app wants to know how many frames are still on their way + * to other station. This is handy for flow control. We would like + * to keep the pipeline filled sufficiently to take advantage of a + * large window size (MAXFRAMES). It is also good to know that the + * the last packet sent was actually received before we commence + * the disconnect. + * + * Inputs: E - Event from the queue. + * The caller will free it. + * + * Outputs: This gets back to the AGW server which sends the 'Y' reply. + * + * 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. + * + *------------------------------------------------------------------------------*/ + +void dl_outstanding_frames_request (dlq_item_t *E) +{ + ax25_dlsm_t *S; + int ok_to_create = 0; // must exist already. + + + if (s_debug_client_app) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("dl_outstanding_frames_request ( to %s )\n", E->addrs[PEERCALL]); + } + + S = get_link_handle (E->addrs, E->num_addr, E->chan, E->client, ok_to_create); + + if (S == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Can't get outstanding frames for %s -> %s, chan %d\n", E->addrs[OWNCALL], E->addrs[PEERCALL], E->chan); + server_outstanding_frames_reply (E->chan, E->client, E->addrs[OWNCALL], E->addrs[PEERCALL], 0); + return; + } + +// Add up these +// +// cdata_t *i_frame_queue; // Connected data from client which has not been transmitted yet. +// // Linked list. +// // The name is misleading because these are just blocks of +// // data, not "I frames" at this point. The name comes from +// // the protocol specification. +// +// cdata_t *txdata_by_ns[128]; // Data which has already been transmitted. +// // Indexed by N(S) in case it gets lost and needs to be sent again. +// // Cleared out when we get ACK for it. + + int count1 = 0; + cdata_t *incoming; + for (incoming = S->i_frame_queue; incoming != NULL; incoming = incoming->next) { + count1++; + } + + int count2 = 0; + int k; + for (k = 0; k < S->modulo; k++) { + if (S->txdata_by_ns[k] != NULL) { + count2++; + } + } + + server_outstanding_frames_reply (S->chan, S->client, S->addrs[OWNCALL], S->addrs[PEERCALL], count1 + count2); + +} // end dl_outstanding_frames_request + + + /*------------------------------------------------------------------------------ * * Name: dl_client_cleanup diff --git a/ax25_link.h b/src/ax25_link.h similarity index 96% rename from ax25_link.h rename to src/ax25_link.h index 00e8230..40fa401 100644 --- a/ax25_link.h +++ b/src/ax25_link.h @@ -65,6 +65,8 @@ void dl_register_callsign (dlq_item_t *E); void dl_unregister_callsign (dlq_item_t *E); +void dl_outstanding_frames_request (dlq_item_t *E); + void dl_client_cleanup (dlq_item_t *E); diff --git a/ax25_pad.c b/src/ax25_pad.c similarity index 95% rename from ax25_pad.c rename to src/ax25_pad.c index 23e061b..d8af765 100644 --- a/ax25_pad.c +++ b/src/ax25_pad.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011 , 2013, 2014, 2015 John Langner, WB2OSZ +// Copyright (C) 2011 , 2013, 2014, 2015, 2019 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,18 +87,18 @@ * http://www.aprs.org/aprs12/preemptive-digipeating.txt * http://www.aprs.org/aprs12/RR-bits.txt * - * I don't recall why I originally intended to set the source/destination C bits both to 1. + * I don't recall why I originally set the source & destination C bits both to 1. * Reviewing this 5 years later, after spending more time delving into the * AX.25 spec, I think it should be 1 for destination and 0 for source. * In practice you see all four combinations being used by APRS stations - * and no one really cares about these two bits. + * and everyone apparently ignores them for APRS. They do make a big + * difference for connected mode. * * The final octet of the Source has the form: * * C R R SSID 0, where, * - * C = command/response = 1 (originally, now I think it should be 0 for source.) - * (Haven't gone back to check to see what code actually does.) + * C = command/response = 0 * R R = Reserved = 1 1 * SSID = substation ID * 0 = zero (or 1 if no repeaters) @@ -373,10 +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 *pinfo; - char *pa; - char *saveptr; /* Used with strtok_r because strtok is not thread safe. */ int ssid_temp, heard_temp; char atemp[AX25_MAX_ADDR_LEN]; @@ -384,6 +381,10 @@ packet_t ax25_from_text (char *monitor, int strict) char info_part[AX25_MAX_INFO_LEN+1]; int info_len; + // text_color_set(DW_COLOR_DEBUG); + // dw_printf ("DEBUG: ax25_from_text ('%s', %d)\n", monitor, strict); + // fflush(stdout); sleep(1); + packet_t this_p = ax25_new (); #if AX25MEMDEBUG @@ -410,14 +411,15 @@ packet_t ax25_from_text (char *monitor, int strict) this_p->frame_data[AX25_DESTINATION*7+6] = SSID_H_MASK | SSID_RR_MASK; memset (this_p->frame_data + AX25_SOURCE*7, ' ' << 1, 6); - this_p->frame_data[AX25_SOURCE*7+6] = SSID_H_MASK | SSID_RR_MASK | SSID_LAST_MASK; + this_p->frame_data[AX25_SOURCE*7+6] = SSID_RR_MASK | SSID_LAST_MASK; this_p->frame_data[14] = AX25_UI_FRAME; this_p->frame_data[15] = AX25_PID_NO_LAYER_3; this_p->frame_len = 7 + 7 + 1 + 1; this_p->num_addr = (-1); - assert (ax25_get_num_addr(this_p) == 2); + (void) ax25_get_num_addr(this_p); // when num_addr is -1, this sets it properly. + assert (this_p->num_addr == 2); /* @@ -440,9 +442,10 @@ packet_t ax25_from_text (char *monitor, int strict) /* * Source address. - * Don't use traditional strtok because it is not thread safe. */ - pa = strtok_r (stuff, ">", &saveptr); + + char *pnxt = stuff; + char *pa = strsep (&pnxt, ">"); if (pa == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Failed to create packet from text. No source address\n"); @@ -465,7 +468,7 @@ packet_t ax25_from_text (char *monitor, int strict) * Destination address. */ - pa = strtok_r (NULL, ",", &saveptr); + pa = strsep (&pnxt, ","); if (pa == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Failed to create packet from text. No destination address\n"); @@ -487,11 +490,26 @@ packet_t ax25_from_text (char *monitor, int strict) /* * VIA path. */ - while (( pa = strtok_r (NULL, ",", &saveptr)) != NULL && this_p->num_addr < AX25_MAX_ADDRS ) { - int k; +// Originally this used strtok_r. +// strtok considers all adjacent delimiters to be a single delimiter. +// This is handy for varying amounts of whitespace. +// It will never return a zero length string. +// All was good until this bizarre case came along: - k = this_p->num_addr; +// AISAT-1>CQ,,::CQ-0 :From AMSAT INDIA & Exseed Space |114304|48|45|42{962 + +// Apparently there are two digipeater fields but they are empty. +// When we parsed this text representation, the extra commas were ignored rather +// than pointed out as being invalid. + +// Use strsep instead. This does not collapse adjacent delimiters. + + while (( pa = strsep (&pnxt, ",")) != NULL && this_p->num_addr < AX25_MAX_ADDRS ) { + + int k = this_p->num_addr; + + // printf ("DEBUG: get digi loop, num addr = %d, address = '%s'\n", k, pa);// FIXME if ( ! ax25_parse_addr (k, pa, strict, atemp, &ssid_temp, &heard_temp)) { text_color_set(DW_COLOR_ERROR); @@ -726,7 +744,7 @@ packet_t ax25_dup (packet_t copy_from) * out_heard - True if "*" found. * * Returns: True (1) if OK, false (0) if any error. - * When 0, out_addr, out_ssid, and out_heard are unpredictable. + * When 0, out_addr, out_ssid, and out_heard are undefined. * * *------------------------------------------------------------------------------*/ @@ -747,10 +765,17 @@ int ax25_parse_addr (int position, char *in_addr, int strict, char *out_addr, in *out_ssid = 0; *out_heard = 0; + // dw_printf ("ax25_parse_addr in: position=%d, '%s', strict=%d\n", position, in_addr, strict); + if (position < -1) position = -1; if (position > AX25_REPEATER_8) position = AX25_REPEATER_8; position++; /* Adjust for position_name above. */ + if (strlen(in_addr) == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("%sAddress \"%s\" is empty.\n", position_name[position], in_addr); + return 0; + } if (strict && strlen(in_addr) >= 2 && strncmp(in_addr, "qA", 2) == 0) { @@ -759,8 +784,7 @@ int ax25_parse_addr (int position, char *in_addr, int strict, char *out_addr, in dw_printf ("APRS Internet Servers. It should never appear when going over the radio.\n"); } - //dw_printf ("ax25_parse_addr in: %s\n", in_addr); - + // dw_printf ("ax25_parse_addr in: %s\n", in_addr); maxlen = strict ? 6 : (AX25_MAX_ADDR_LEN-1); p = in_addr; @@ -838,7 +862,7 @@ int ax25_parse_addr (int position, char *in_addr, int strict, char *out_addr, in return 0; } - //dw_printf ("ax25_parse_addr out: %s %d %d\n", out_addr, *out_ssid, *out_heard); + // dw_printf ("ax25_parse_addr out: '%s' %d %d\n", out_addr, *out_ssid, *out_heard); return (1); @@ -984,6 +1008,11 @@ void ax25_set_addr (packet_t this_p, int n, char *ad) //dw_printf ("ax25_set_addr (%d, %s) num_addr=%d\n", n, ad, this_p->num_addr); + if (strlen(ad) == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Set address error! Station address for position %d is empty!\n", n); + } + if (n >= 0 && n < this_p->num_addr) { //dw_printf ("ax25_set_addr , existing case\n"); @@ -1065,6 +1094,11 @@ void ax25_insert_addr (packet_t this_p, int n, char *ad) //dw_printf ("ax25_insert_addr (%d, %s)\n", n, ad); + if (strlen(ad) == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Set address error! Station address for position %d is empty!\n", n); + } + /* Don't do it if we already have the maximum number. */ /* Should probably return success/fail code but currently the caller doesn't care. */ @@ -1295,12 +1329,21 @@ void ax25_get_addr_with_ssid (packet_t this_p, int n, char *station) station[6] = '\0'; for (i=5; i>=0; i--) { - if (station[i] == ' ') + if (station[i] == '\0') { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Station address \"%s\" contains nul character. AX.25 protocol requires trailing ASCII spaces when less than 6 characters.\n", station); + } + else if (station[i] == ' ') station[i] = '\0'; else break; } + if (strlen(station) == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Station address, in position %d, is empty! This is not a valid AX.25 frame.\n", n); + } + ssid = ax25_get_ssid (this_p, n); if (ssid != 0) { snprintf (sstr, sizeof(sstr), "-%d", ssid); @@ -1375,6 +1418,11 @@ void ax25_get_addr_no_ssid (packet_t this_p, int n, char *station) break; } + if (strlen(station) == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Station address, in position %d, is empty! This is not a valid AX.25 frame.\n", n); + } + } /* end ax25_get_addr_no_ssid */ @@ -1914,6 +1962,8 @@ void ax25_format_addrs (packet_t this_p, char *result) } strcat (result, ":"); + + // dw_printf ("DEBUG ax25_format_addrs, num_addr = %d, result = '%s'\n", this_p->num_addr, result); } @@ -2021,7 +2071,7 @@ int ax25_pack (packet_t this_p, unsigned char result[AX25_MAX_PACKET_LEN]) * * Outputs: desc - Text description such as "I frame" or * "U frame SABME". - * Supply 40 bytes to be safe. + * Supply 56 bytes to be safe. * * cr - Command or response? * @@ -2037,7 +2087,7 @@ int ax25_pack (packet_t this_p, unsigned char result[AX25_MAX_PACKET_LEN]) // TODO: need someway to ensure caller allocated enough space. // Should pass in as parameter. -#define DESC_SIZ 40 +#define DESC_SIZ 56 ax25_frame_type_t ax25_frame_type (packet_t this_p, cmdres_t *cr, char *desc, int *pf, int *nr, int *ns) @@ -2308,16 +2358,30 @@ void ax25_hex_dump (packet_t this_p) dw_printf ("%s\n", cp_text); } + // Address fields must be only upper case letters and digits. + // If less than 6 characters, trailing positions are filled with ASCII space. + // Using all zero bits in one of these 6 positions is wrong. + // Any non printable characters will be printed as "." here. - dw_printf (" dest %c%c%c%c%c%c %2d c/r=%d res=%d last=%d\n", - fptr[0]>>1, fptr[1]>>1, fptr[2]>>1, fptr[3]>>1, fptr[4]>>1, fptr[5]>>1, + dw_printf (" dest %c%c%c%c%c%c %2d c/r=%d res=%d last=%d\n", + isprint(fptr[0]>>1) ? fptr[0]>>1 : '.', + isprint(fptr[1]>>1) ? fptr[1]>>1 : '.', + isprint(fptr[2]>>1) ? fptr[2]>>1 : '.', + isprint(fptr[3]>>1) ? fptr[3]>>1 : '.', + isprint(fptr[4]>>1) ? fptr[4]>>1 : '.', + isprint(fptr[5]>>1) ? fptr[5]>>1 : '.', (fptr[6]&SSID_SSID_MASK)>>SSID_SSID_SHIFT, (fptr[6]&SSID_H_MASK)>>SSID_H_SHIFT, (fptr[6]&SSID_RR_MASK)>>SSID_RR_SHIFT, fptr[6]&SSID_LAST_MASK); dw_printf (" source %c%c%c%c%c%c %2d c/r=%d res=%d last=%d\n", - fptr[7]>>1, fptr[8]>>1, fptr[9]>>1, fptr[10]>>1, fptr[11]>>1, fptr[12]>>1, + isprint(fptr[7]>>1) ? fptr[7]>>1 : '.', + isprint(fptr[8]>>1) ? fptr[8]>>1 : '.', + isprint(fptr[9]>>1) ? fptr[9]>>1 : '.', + isprint(fptr[10]>>1) ? fptr[10]>>1 : '.', + isprint(fptr[11]>>1) ? fptr[11]>>1 : '.', + isprint(fptr[12]>>1) ? fptr[12]>>1 : '.', (fptr[13]&SSID_SSID_MASK)>>SSID_SSID_SHIFT, (fptr[13]&SSID_H_MASK)>>SSID_H_SHIFT, (fptr[13]&SSID_RR_MASK)>>SSID_RR_SHIFT, @@ -2327,7 +2391,12 @@ void ax25_hex_dump (packet_t this_p) dw_printf (" digi %d %c%c%c%c%c%c %2d h=%d res=%d last=%d\n", n - 1, - fptr[n*7+0]>>1, fptr[n*7+1]>>1, fptr[n*7+2]>>1, fptr[n*7+3]>>1, fptr[n*7+4]>>1, fptr[n*7+5]>>1, + isprint(fptr[n*7+0]>>1) ? fptr[n*7+0]>>1 : '.', + isprint(fptr[n*7+1]>>1) ? fptr[n*7+1]>>1 : '.', + isprint(fptr[n*7+2]>>1) ? fptr[n*7+2]>>1 : '.', + isprint(fptr[n*7+3]>>1) ? fptr[n*7+3]>>1 : '.', + isprint(fptr[n*7+4]>>1) ? fptr[n*7+4]>>1 : '.', + isprint(fptr[n*7+5]>>1) ? fptr[n*7+5]>>1 : '.', (fptr[n*7+6]&SSID_SSID_MASK)>>SSID_SSID_SHIFT, (fptr[n*7+6]&SSID_H_MASK)>>SSID_H_SHIFT, (fptr[n*7+6]&SSID_RR_MASK)>>SSID_RR_SHIFT, diff --git a/ax25_pad.h b/src/ax25_pad.h similarity index 99% rename from ax25_pad.h rename to src/ax25_pad.h index 72155b2..22568f7 100644 --- a/ax25_pad.h +++ b/src/ax25_pad.h @@ -435,7 +435,7 @@ extern unsigned short ax25_m_m_crc (packet_t pp); extern void ax25_safe_print (char *, int, int ascii_only); -#define AX25_ALEVEL_TO_TEXT_SIZE 32 // overkill but safe. +#define AX25_ALEVEL_TO_TEXT_SIZE 40 // overkill but safe. extern int ax25_alevel_to_text (alevel_t alevel, char text[AX25_ALEVEL_TO_TEXT_SIZE]); diff --git a/ax25_pad2.c b/src/ax25_pad2.c similarity index 99% rename from ax25_pad2.c rename to src/ax25_pad2.c index 516fe9d..efba887 100644 --- a/ax25_pad2.c +++ b/src/ax25_pad2.c @@ -796,7 +796,7 @@ int main () for (pf = 0; pf <= 1; pf++) { - int cmin, cmax; + int cmin = 0, cmax = 1; switch (ftype) { // 0 = response, 1 = command @@ -867,7 +867,7 @@ int main () /* SREJ is only S frame which can have information part. */ - static char srej_info[] = { 1<<1, 2<<1, 3<<1, 4<<1 }; + static unsigned char srej_info[] = { 1<<1, 2<<1, 3<<1, 4<<1 }; ftype = frame_type_S_SREJ; for (pf = 0; pf <= 1; pf++) { diff --git a/ax25_pad2.h b/src/ax25_pad2.h similarity index 100% rename from ax25_pad2.h rename to src/ax25_pad2.h diff --git a/beacon.c b/src/beacon.c similarity index 92% rename from beacon.c rename to src/beacon.c index 952bc8a..3fea714 100644 --- a/beacon.c +++ b/src/beacon.c @@ -59,26 +59,6 @@ #include "mheard.h" -#if __WIN32__ - -/* - * Windows doesn't have localtime_r. - * It should have the equivalent localtime_s, with opposite parameter - * order, but I get undefined reference when trying to use it. - */ - -struct tm *localtime_r(time_t *clock, struct tm *res) -{ - struct tm *tm; - - tm = localtime (clock); - memcpy (res, tm, sizeof(struct tm)); - return (res); -} - -#endif - - /* * Save pointers to configuration settings. */ @@ -172,12 +152,19 @@ void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct * If a serious error is found, set type to BEACON_IGNORE and that * table entry should be ignored later on. */ + +// TODO: Better checking. +// We should really have a table for which keywords are are required, +// optional, or not allowed for each beacon type. Options which +// are not applicable are often silently ignored, causing confusion. + for (j=0; jnum_beacons; j++) { int chan = g_misc_config_p->beacon[j].sendto_chan; if (chan < 0) chan = 0; /* For IGate, use channel 0 call. */ - if (g_modem_config_p->achan[chan].valid) { + if (g_modem_config_p->achan[chan].medium == MEDIUM_RADIO || + g_modem_config_p->achan[chan].medium == MEDIUM_NETTNC) { if (strlen(g_modem_config_p->achan[chan].mycall) > 0 && strcasecmp(g_modem_config_p->achan[chan].mycall, "N0CALL") != 0 && @@ -207,6 +194,18 @@ void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct g_misc_config_p->beacon[j].btype = BEACON_IGNORE; continue; } + + /* INFO and INFOCMD are only for Custom Beacon. */ + + if (g_misc_config_p->beacon[j].custom_info != NULL || g_misc_config_p->beacon[j].custom_infocmd != NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: INFO or INFOCMD are allowed only for custom beacon.\n", g_misc_config_p->beacon[j].lineno); + dw_printf ("INFO and INFOCMD allow you to specify contents of the Information field so it\n"); + dw_printf ("so it would not make sense to use these with other beacon types which construct\n"); + dw_printf ("the Information field. Perhaps you want to use COMMENT or COMMENTCMD option.\n"); + //g_misc_config_p->beacon[j].btype = BEACON_IGNORE; + continue; + } break; case BEACON_TRACKER: @@ -221,8 +220,29 @@ void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: GPS must be configured to use TBEACON.\n", g_misc_config_p->beacon[j].lineno); g_misc_config_p->beacon[j].btype = BEACON_IGNORE; +#if __WIN32__ + dw_printf ("You must specify the GPSNMEA command in your configuration file.\n"); + dw_printf ("This contains the name of the serial port where the receiver is connected.\n"); +#else + dw_printf ("You must specify the source of the GPS data in your configuration file.\n"); + dw_printf ("It can be either GPSD, meaning the gpsd daemon, or GPSNMEA for\n"); + dw_printf ("for a serial port connection with exclusive use.\n"); +#endif + } } + + /* INFO and INFOCMD are only for Custom Beacon. */ + + if (g_misc_config_p->beacon[j].custom_info != NULL || g_misc_config_p->beacon[j].custom_infocmd != NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: INFO or INFOCMD are allowed only for custom beacon.\n", g_misc_config_p->beacon[j].lineno); + dw_printf ("INFO and INFOCMD allow you to specify contents of the Information field so it\n"); + dw_printf ("so it would not make sense to use these with other beacon types which construct\n"); + dw_printf ("the Information field. Perhaps you want to use COMMENT or COMMENTCMD option.\n"); + //g_misc_config_p->beacon[j].btype = BEACON_IGNORE; + continue; + } break; case BEACON_CUSTOM: @@ -363,7 +383,7 @@ void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct #else int e; - e = pthread_create (&beacon_tid, NULL, beacon_thread, (void *)0); + e = pthread_create (&beacon_tid, NULL, beacon_thread, NULL); if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("Could not create beacon thread"); @@ -1021,7 +1041,7 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo) /* Simulated reception from radio. */ memset (&alevel, 0xff, sizeof(alevel)); - dlq_rec_frame (bp->sendto_chan, 0, 0, pp, alevel, 0, ""); + dlq_rec_frame (bp->sendto_chan, 0, 0, pp, alevel, 0, 0, ""); break; } } diff --git a/beacon.h b/src/beacon.h similarity index 100% rename from beacon.h rename to src/beacon.h diff --git a/cdigipeater.c b/src/cdigipeater.c similarity index 97% rename from cdigipeater.c rename to src/cdigipeater.c index 9c40d95..edfa2dd 100644 --- a/cdigipeater.c +++ b/src/cdigipeater.c @@ -49,7 +49,7 @@ #include #include /* for isdigit, isupper */ #include "regex.h" -#include +#include #include "ax25_pad.h" #include "cdigipeater.h" @@ -129,8 +129,10 @@ void cdigipeater (int from_chan, packet_t pp) { int to_chan; + // 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].valid) ) { + if ( from_chan < 0 || from_chan >= MAX_CHANS || save_audio_config_p->achan[from_chan].medium != MEDIUM_RADIO) { text_color_set(DW_COLOR_ERROR); dw_printf ("cdigipeater: Did not expect to receive on invalid channel %d.\n", from_chan); return; diff --git a/cdigipeater.h b/src/cdigipeater.h similarity index 100% rename from cdigipeater.h rename to src/cdigipeater.h diff --git a/cm108.c b/src/cm108.c similarity index 72% rename from cm108.c rename to src/cm108.c index b0bc779..ad3b0b8 100644 --- a/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 John Langner, WB2OSZ +// Copyright (C) 2017,2019 John Langner, WB2OSZ // // Parts of this were adapted from "hamlib" which contains the notice: // @@ -31,10 +31,11 @@ * Description: * * There is an incresing demand for using the GPIO pins of USB audio devices for PTT. - * We have a couple commercial products: + * We have a few commercial products: * - * DMK URI http://www.dmkeng.com/URI_Order_Page.htm - * RB-USB RIM http://www.repeater-builder.com/products/usb-rim-lite.html + * 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 * * and homebrew projects which are all very similar. * @@ -47,7 +48,7 @@ * * 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 - * roundabout way. This is documented in the User Guide, section called, + * 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 @@ -98,10 +99,14 @@ 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"); +#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 ("Install it with \"sudo apt-get install libudev-dev\" or\n"); dw_printf ("\"sudo yum install libudev-devel\" then rebuild.\n"); +#endif return (0); } @@ -148,6 +153,7 @@ static int cm108_write (char *name, int iomask, int iodata); #define CMEDIA_PID1_MAX 0x000f #define CMEDIA_PID_CM108AH 0x0139 // CM108AH +#define CMEDIA_PID_CM108AH_alt 0x013c // CM108AH? - see issue 210 #define CMEDIA_PID_CM108B 0x0012 // CM108B #define CMEDIA_PID_CM119A 0x013a // CM119A #define CMEDIA_PID_CM119B 0x0013 // CM119B @@ -172,7 +178,8 @@ static int cm108_write (char *name, int iomask, int iodata); // CM119 0d8c 0008-000f * 8 // CM119A 0d8c 013a * 8 // CM119B 0d8c 0013 8 -// HS100 0d8c 013c 0 +// HS100 0d8c 013c 0 (issue 210 reported 013c +// being seen for CM108AH) // // SSS1621 0c76 1605 2 per ZL3AME, Can't find data sheet // SSS1623 0c76 1607,160b 2 per ZL3AME, Not in data sheet. @@ -195,6 +202,7 @@ static int cm108_write (char *name, int iomask, int iodata); #define GOOD_DEVICE(v,p) ( (v == CMEDIA_VID && ((p >= CMEDIA_PID1_MIN && p <= CMEDIA_PID1_MAX) \ || p == CMEDIA_PID_CM108AH \ + || p == CMEDIA_PID_CM108AH_alt \ || p == CMEDIA_PID_CM108B \ || p == CMEDIA_PID_CM119A \ || p == CMEDIA_PID_CM119B )) \ @@ -208,7 +216,7 @@ static int cm108_write (char *name, int iomask, int iodata); // Used to process regular expression matching results. -static void inline substr_se (char *dest, const char *src, int start, int endp1) +static void substr_se (char *dest, const char *src, int start, int endp1) { int len = endp1 - start; @@ -227,13 +235,19 @@ static void inline substr_se (char *dest, const char *src, int start, int endp1) */ struct thing_s { - int vid; // vendor id - int pid; // product id + 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 product[32]; // product name (e.g. manufacturer, model) char devnode_sound[22]; // e.g. /dev/snd/pcmC0D0p - char plughw[15]; // Above in more familiar format e.g. plughw:0,0 + 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_usb[25]; // e.g. /dev/bus/usb/001/012 + // This is what we use to match up audio and HID. }; int cm108_inventory (struct thing_s *things, int max_things); @@ -243,13 +257,12 @@ int cm108_inventory (struct thing_s *things, int max_things); * * Name: main * - * Purpose: Test program to list USB audio and HID devices. - * - * sudo apt-get install libudev-dev - * gcc -DCM108_MAIN textcolor.c -l udev + * Purpose: Useful utility to list USB audio and HID devices. * *------------------------------------------------------------------*/ +//#define EXTRA 1 + #define MAXX_THINGS 60 #ifdef CM108_MAIN @@ -262,28 +275,32 @@ int main (void) text_color_init (0); // Turn off text color. +// Take inventory of USB Audio adapters and other HID devices. + num_things = cm108_inventory (things, MAXX_THINGS); - dw_printf (" VID PID %-*s %-*s %-*s %-*s" + dw_printf (" VID PID %-*s %-*s %-*s %-*s %-*s" #if EXTRA " %-*s" #endif "\n", (int)sizeof(things[0].product), "Product", (int)sizeof(things[0].devnode_sound), "Sound", - (int)sizeof(things[0].plughw), "ADEVICE", + (int)sizeof(things[0].plughw)/5, "ADEVICE", + (int)sizeof(things[0].plughw2)/4, "ADEVICE", (int)sizeof(things[0].devnode_hidraw), "HID [ptt]" #if EXTRA , (int)sizeof(things[0].devnode_usb), "USB" #endif ); - dw_printf (" --- --- %-*s %-*s %-*s %-*s" + dw_printf (" --- --- %-*s %-*s %-*s %-*s %-*s" #if EXTRA " %-*s" #endif "\n", (int)sizeof(things[0].product), "-------", (int)sizeof(things[0].devnode_sound), "-----", - (int)sizeof(things[0].plughw), "-------", + (int)sizeof(things[0].plughw)/5, "-------", + (int)sizeof(things[0].plughw2)/4, "-------", (int)sizeof(things[0].devnode_hidraw), "---------" #if EXTRA , (int)sizeof(things[0].devnode_usb), "---" @@ -291,7 +308,7 @@ int main (void) ); for (i = 0; i < num_things; i++) { - dw_printf ("%2s %04x %04x %-*s %-*s %-*s %-*s" + dw_printf ("%2s %04x %04x %-*s %-*s %-*s %-*s %-*s" #if EXTRA " %-*s" #endif @@ -300,14 +317,62 @@ int main (void) things[i].vid, things[i].pid, (int)sizeof(things[i].product), things[i].product, (int)sizeof(things[i].devnode_sound), things[i].devnode_sound, - (int)sizeof(things[0].plughw), things[i].plughw, + (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 #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); } + static const char *suggested_names[] = {"Fred", "Wilma", "Pebbles", "Dino", "Barney", "Betty", "Bamm_Bamm" }; + 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"); + dw_printf ("that makes them unique such as product id or serial number. Unfortunately these devices don't have unique serial\n"); + dw_printf ("numbers so how can we tell them apart? A name can also be assigned based on the physical USB socket.\n"); + dw_printf ("Create a file like \"/etc/udev/rules.d/85-my-usb-audio.rules\" with the following contents and then reboot.\n"); + dw_printf ("\n"); + dw_printf ("SUBSYSTEM!=\"sound\", GOTO=\"my_usb_audio_end\"\n"); + dw_printf ("ACTION!=\"add\", GOTO=\"my_usb_audio_end\"\n"); + +// Consider only the 'devnode' paths that end with "card" and a number. +// Replace the number with a question mark. + + regex_t devpath_re; + char emsg[100]; + // Drop any "/sys" at the beginning. + int e = regcomp (&devpath_re, "(/devices/.+/card)[0-9]$", REG_EXTENDED); + if (e) { + regerror (e, &devpath_re, emsg, sizeof(emsg)); + text_color_set(DW_COLOR_ERROR); + dw_printf("INTERNAL ERROR: %s:%d: %s\n", __FILE__, __LINE__, emsg); + return (-1); + } + + for (i = 0; i < num_things; i++) { + if (i == 0 || strcmp(things[i].devpath,things[i-1].devpath) != 0) { + regmatch_t devpath_match[2]; + if (regexec (&devpath_re, things[i].devpath, 2, devpath_match, 0) == 0) { + char without_number[256]; + substr_se (without_number, things[i].devpath, devpath_match[1].rm_so, devpath_match[1].rm_eo); + dw_printf ("DEVPATH==\"%s?\", ATTR{id}=\"%s\"\n", without_number, suggested_names[iname]); + if (iname < 6) iname++; + } + } + } + dw_printf ("LABEL=\"my_usb_audio_end\"\n"); + dw_printf ("\n"); + return (0); } @@ -341,6 +406,10 @@ int cm108_inventory (struct thing_s *things, int max_things) struct udev_device *dev; struct udev_device *parentdev; + char const *pattrs_id = NULL; + char const *pattrs_number = NULL; + char card_devpath[128] = ""; + int num_things = 0; memset (things, 0, sizeof(struct thing_s) * max_things); @@ -360,11 +429,21 @@ int cm108_inventory (struct thing_s *things, int max_things) udev_enumerate_scan_devices(enumerate); devices = udev_enumerate_get_list_entry(enumerate); udev_list_entry_foreach(dev_list_entry, devices) { - const char *path; - path = udev_list_entry_get_name(dev_list_entry); + const char *path = udev_list_entry_get_name(dev_list_entry); dev = udev_device_new_from_syspath(udev, path); char const *devnode = udev_device_get_devnode(dev); - if (devnode != NULL) { + + if (devnode == NULL ) { + // I'm not happy with this but couldn't figure out how + // to get attributes from one level up from the pcmC?D?? node. + strlcpy (card_devpath, path, sizeof(card_devpath)); + pattrs_id = udev_device_get_sysattr_value(dev,"id"); + pattrs_number = udev_device_get_sysattr_value(dev,"number"); + //dw_printf (" >card_devpath = %s\n", card_devpath); + //dw_printf (" >>pattrs_id = %s\n", pattrs_id); + //dw_printf (" >>pattrs_number = %s\n", pattrs_number); + } + else { parentdev = udev_device_get_parent_with_subsystem_devtype( dev, "usb", "usb_device"); if (parentdev != NULL) { char const *p; @@ -379,9 +458,12 @@ int cm108_inventory (struct thing_s *things, int max_things) if (num_things < max_things) { things[num_things].vid = vid; things[num_things].pid = pid; + SAFE_STRCPY (things[num_things].card_name, pattrs_id); + SAFE_STRCPY (things[num_things].card_number, pattrs_number); SAFE_STRCPY (things[num_things].product, udev_device_get_sysattr_value(parentdev,"product")); SAFE_STRCPY (things[num_things].devnode_sound, devnode); SAFE_STRCPY (things[num_things].devnode_usb, udev_device_get_devnode(parentdev)); + strlcpy (things[num_things].devpath, card_devpath, sizeof(things[num_things].devpath)); num_things++; } udev_device_unref(parentdev); @@ -406,8 +488,7 @@ int cm108_inventory (struct thing_s *things, int max_things) udev_enumerate_scan_devices(enumerate); devices = udev_enumerate_get_list_entry(enumerate); udev_list_entry_foreach(dev_list_entry, devices) { - const char *path; - path = udev_list_entry_get_name(dev_list_entry); + const char *path = udev_list_entry_get_name(dev_list_entry); dev = udev_device_new_from_syspath(udev, path); char const *devnode = udev_device_get_devnode(dev); if (devnode != NULL) { @@ -440,6 +521,7 @@ int cm108_inventory (struct thing_s *things, int max_things) SAFE_STRCPY (things[num_things].product, udev_device_get_sysattr_value(parentdev,"product")); SAFE_STRCPY (things[num_things].devnode_hidraw, devnode); SAFE_STRCPY (things[num_things].devnode_usb, usb); + SAFE_STRCPY (things[num_things].devpath, udev_device_get_devpath(dev)); num_things++; } udev_device_unref(parentdev); @@ -453,6 +535,8 @@ int cm108_inventory (struct thing_s *things, int max_things) * Seeing the form /dev/snd/pcmC4D0p will be confusing to many because we * would generally something like plughw:4,0 for in the direwolf configuration file. * Construct the more familiar form. + * Previously we only used the numeric form. In version 1.6, the name is listed as well + * and we describe how to assign names based on the physical USB socket for repeatability. */ int i; regex_t pcm_re; @@ -473,6 +557,7 @@ int cm108_inventory (struct thing_s *things, int max_things) substr_se (c, things[i].devnode_sound, match[1].rm_so, match[1].rm_eo); substr_se (d, things[i].devnode_sound, match[2].rm_so, match[2].rm_eo); snprintf (things[i].plughw, sizeof(things[i].plughw), "plughw:%s,%s", c, d); + snprintf (things[i].plughw2, sizeof(things[i].plughw), "plughw:%s,%s", things[i].card_name, d); } } @@ -485,11 +570,16 @@ int cm108_inventory (struct thing_s *things, int max_things) * * Name: cm108_find_ptt * - * Purpose: Try to find /dev/hidraw corresponding to plughw:n,n + * Purpose: Try to find /dev/hidraw corresponding to a USB audio "card." * * Inputs: output_audio_device - * - Device name used in the ADEVICE configuration. - * This would generally be something like plughw:1,0 + * - Used in the ADEVICE configuration. + * This can take many forms such as: + * surround41:CARD=Fred,DEV=0 + * surround41:Fred,0 + * surround41:Fred + * plughw:2,3 + * In our case we just need to extract the card number or name. * * ptt_device_size - Size of result area to avoid buffer overflow. * @@ -506,15 +596,46 @@ void cm108_find_ptt (char *output_audio_device, char *ptt_device, int ptt_devic int num_things; int i; + //dw_printf ("DEBUG: cm108_find_ptt('%s')\n", output_audio_device); + strlcpy (ptt_device, "", ptt_device_size); num_things = cm108_inventory (things, MAXX_THINGS); - for (i = 0; i < num_things; i++) { + regex_t sound_re; + char emsg[100]; + int e = regcomp (&sound_re, ".+:(CARD=)?([A-Za-z0-9_]+)(,.*)?", REG_EXTENDED); + if (e) { + regerror (e, &sound_re, emsg, sizeof(emsg)); + text_color_set(DW_COLOR_ERROR); + dw_printf("INTERNAL ERROR: %s:%d: %s\n", __FILE__, __LINE__, emsg); + return; + } - if (GOOD_DEVICE(things[i].vid,things[i].pid) ) { - if (strcmp(output_audio_device, things[i].plughw) == 0) { - strlcpy (ptt_device, things[i].devnode_hidraw, ptt_device_size); + char num_or_name[64]; + strcpy (num_or_name, ""); + regmatch_t sound_match[4]; + if (regexec (&sound_re, output_audio_device, 4, sound_match, 0) == 0) { + substr_se (num_or_name, output_audio_device, sound_match[2].rm_so, sound_match[2].rm_eo); + //dw_printf ("DEBUG: Got '%s' from '%s'\n", num_or_name, output_audio_device); + } + if (strlen(num_or_name) == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Could not extract card number or name from %s\n", output_audio_device); + dw_printf ("Can't automatically find matching HID for PTT.\n"); + return; + } + + for (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); + strlcpy (ptt_device, things[i].devnode_hidraw, ptt_device_size); + if ( ! GOOD_DEVICE(things[i].vid,things[i].pid) ) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Warning: USB audio card %s (%s) is not a device known to work with GPIO PTT.\n", + things[i].card_number, things[i].card_name); } + return; } } @@ -548,6 +669,45 @@ 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) { int iomask; @@ -565,8 +725,8 @@ int cm108_set_gpio_pin (char *name, int num, int state) return (-1); } - iomask = 1 << (num - 1); - iodata = state << (num - 1); + iomask = 1 << (num - 1); // 0=input, 1=output + iodata = state << (num - 1); // 0=low, 1=high return (cm108_write (name, iomask, iodata)); @@ -671,8 +831,9 @@ static int cm108_write (char *name, int iomask, int iodata) io[0] = 0; io[1] = 0; - io[2] = iomask; - io[3] = iodata; +// Issue 210 - These were reversed. Fixed in 1.6. + io[2] = iodata; + io[3] = iomask; io[4] = 0; // Writing 4 bytes fails with errno 32, EPIPE, "broken pipe." @@ -709,4 +870,4 @@ static int cm108_write (char *name, int iomask, int iodata) /* end cm108.c */ - \ No newline at end of file + diff --git a/cm108.h b/src/cm108.h similarity index 100% rename from cm108.h rename to src/cm108.h diff --git a/config.c b/src/config.c similarity index 92% rename from config.c rename to src/config.c index 8a6ed49..8588a8c 100644 --- a/config.c +++ b/src/config.c @@ -79,8 +79,6 @@ -//#include "tq.h" - /* * Conversions from various units to meters. * There is some disagreement about the exact values for some of these. @@ -757,10 +755,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, for (channel=0; channelachan[channel].valid = 0; /* One or both channels will be */ - /* set to valid when corresponding */ + p_audio_config->achan[channel].medium = 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; + p_audio_config->achan[channel].v26_alternative = V26_UNSPECIFIED; p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ; /* -m option */ p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ; /* -s option */ p_audio_config->achan[channel].baud = DEFAULT_BAUD; /* -b option */ @@ -800,11 +799,12 @@ void config_init (char *fname, struct audio_s *p_audio_config, p_audio_config->achan[channel].fulldup = DEFAULT_FULLDUP; } + p_audio_config->fx25_auto_enable = AX25_N2_RETRY_DEFAULT / 2; + /* First channel should always be valid. */ /* If there is no ADEVICE, it uses default device in mono. */ - p_audio_config->achan[0].valid = 1; - + p_audio_config->achan[0].medium = MEDIUM_RADIO; memset (p_digi_config, 0, sizeof(struct digi_config_s)); // APRS digipeater p_digi_config->dedupe_time = DEFAULT_DEDUPE; @@ -854,6 +854,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, p_misc_config->agwpe_port = DEFAULT_AGWPE_PORT; p_misc_config->kiss_port = DEFAULT_KISS_PORT; p_misc_config->enable_kiss_pt = 0; /* -p option */ + p_misc_config->kiss_copy = 0; /* Defaults from http://info.aprs.net/index.php?title=SmartBeaconing */ @@ -884,7 +885,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, p_misc_config->kiss_serial_poll = 0; strlcpy (p_misc_config->gpsnmea_port, "", sizeof(p_misc_config->gpsnmea_port)); - strlcpy (p_misc_config->waypoint_port, "", sizeof(p_misc_config->waypoint_port)); + strlcpy (p_misc_config->waypoint_serial_port, "", sizeof(p_misc_config->waypoint_serial_port)); p_misc_config->log_daily_names = 0; strlcpy (p_misc_config->log_path, "", sizeof(p_misc_config->log_path)); @@ -901,7 +902,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, p_misc_config->maxframe_extended = AX25_K_MAXFRAME_EXTENDED_DEFAULT; /* Max frames to send before ACK. mod 128 "Window" size. */ - p_misc_config->maxv22 = AX25_N2_RETRY_DEFAULT / 2; /* Max SABME before falling back to SABM. */ + p_misc_config->maxv22 = AX25_N2_RETRY_DEFAULT / 3; /* Max SABME before falling back to SABM. */ p_misc_config->v20_addrs = NULL; /* Go directly to v2.0 for stations listed. */ p_misc_config->v20_count = 0; p_misc_config->noxid_addrs = NULL; /* Don't send XID to these stations. */ @@ -1017,7 +1018,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)].valid = 1; + p_audio_config->achan[ADEVFIRSTCHAN(adevice)].medium = 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)); @@ -1072,7 +1073,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)].valid = 1; + p_audio_config->achan[ADEVFIRSTCHAN(adevice)].medium = MEDIUM_RADIO; strlcpy (p_audio_config->adev[adevice].adevice_in, t, sizeof(p_audio_config->adev[adevice].adevice_in)); } @@ -1099,7 +1100,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)].valid = 1; + p_audio_config->achan[ADEVFIRSTCHAN(adevice)].medium = MEDIUM_RADIO; strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out)); } @@ -1146,9 +1147,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)].valid = 1; + p_audio_config->achan[ADEVFIRSTCHAN(adevice)].medium = MEDIUM_RADIO; if (n == 2) { - p_audio_config->achan[ADEVFIRSTCHAN(adevice) + 1].valid = 1; + p_audio_config->achan[ADEVFIRSTCHAN(adevice) + 1].medium = MEDIUM_RADIO; } } else { @@ -1178,7 +1179,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, channel = n; - if ( ! p_audio_config->achan[n].valid) { + if (p_audio_config->achan[n].medium != MEDIUM_RADIO) { if ( ! p_audio_config->adev[ACHAN2ADEV(n)].defined) { text_color_set(DW_COLOR_ERROR); @@ -1260,7 +1261,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, * Options: * mark:space - AFSK tones. Defaults based on speed. * num@offset - Multiple decoders on different frequencies. - * + * /9 - Divide sample rate by specified number. + * *9 - Upsample ratio for G3RUH. + * [A-Z+-]+ - Letters, plus, minus for the demodulator "profile." + * g3ruh - This modem type regardless of default for speed. + * v26a or v26b - V.26 alternative. a=original, b=MFJ compatible */ else if (strcasecmp(t, "MODEM") == 0) { @@ -1271,10 +1276,18 @@ void config_init (char *fname, struct audio_s *p_audio_config, dw_printf ("Line %d: Missing data transmission speed for MODEM command.\n", line); continue; } - n = atoi(t); + if (strcasecmp(t,"AIS") == 0) { + n = MAX_BAUD-1; // Hack - See special case later. + } + else if (strcasecmp(t,"EAS") == 0) { + n = MAX_BAUD-2; // Hack - See special case later. + } + else { + n = atoi(t); + } if (n >= MIN_BAUD && n <= MAX_BAUD) { p_audio_config->achan[channel].baud = n; - if (n != 300 && n != 1200 && n != 2400 && n != 4800 && n != 9600 && n != 19200) { + if (n != 300 && n != 1200 && n != 2400 && n != 4800 && n != 9600 && n != 19200 && n != MAX_BAUD-1 && n != MAX_BAUD-2) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Warning: Non-standard data rate of %d bits per second. Are you sure?\n", line, n); } @@ -1313,13 +1326,26 @@ void config_init (char *fname, struct audio_s *p_audio_config, p_audio_config->achan[channel].mark_freq = 0; p_audio_config->achan[channel].space_freq = 0; } + else if (p_audio_config->achan[channel].baud == MAX_BAUD-1) { + p_audio_config->achan[channel].modem_type = MODEM_AIS; + p_audio_config->achan[channel].mark_freq = 0; + p_audio_config->achan[channel].space_freq = 0; + } + else if (p_audio_config->achan[channel].baud == MAX_BAUD-2) { + p_audio_config->achan[channel].modem_type = MODEM_EAS; + p_audio_config->achan[channel].baud = 521; // Actually 520.83 but we have an integer field here. + // 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)); + } else { p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE; p_audio_config->achan[channel].mark_freq = 0; p_audio_config->achan[channel].space_freq = 0; } - /* Get mark frequency. */ + /* Get any options. */ t = split(NULL,0); if (t == NULL) { @@ -1331,6 +1357,9 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* old style */ + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Old style (pre version 1.2) format will no longer be supported in next version.\n", line); + n = atoi(t); /* Originally the upper limit was 3000. */ /* Version 1.0 increased to 5000 because someone */ @@ -1515,6 +1544,38 @@ void config_init (char *fname, struct audio_s *p_audio_config, } } + else if (*t == '*') { /* *upsample */ + int n = atoi(t+1); + + if (n >= 1 && n <= 4) { + p_audio_config->achan[channel].upsample = n; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Ignoring unreasonable upsample ratio of %d.\n", line, n); + } + } + + else if (strcasecmp(t, "G3RUH") == 0) { /* Force G3RUH modem regardless of default for speed. New in 1.6. */ + + p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE; + p_audio_config->achan[channel].mark_freq = 0; + p_audio_config->achan[channel].space_freq = 0; + } + + else if (strcasecmp(t, "V26A") == 0 || /* Compatible with direwolf versions <= 1.5. New in 1.6. */ + strcasecmp(t, "V26B") == 0) { /* Compatible with MFJ-2400. New in 1.6. */ + + if (p_audio_config->achan[channel].modem_type != MODEM_QPSK || + p_audio_config->achan[channel].baud != 2400) { + + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: %s option can only be used with 2400 bps PSK.\n", line, t); + continue; + } + p_audio_config->achan[channel].v26_alternative = (strcasecmp(t, "V26A") == 0) ? V26_A : V26_B; + } + else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unrecognized option for MODEM: %s\n", line, t); @@ -1552,7 +1613,9 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* * FIX_BITS n [ APRS | AX25 | NONE ] [ PASSALL ] * - * - Attempt to fix frames with bad FCS. + * - Attempt to fix frames with bad FCS. + * - n is maximum number of bits to attempt fixing. + * - Optional sanity check & allow everything even with bad FCS. */ else if (strcasecmp(t, "FIX_BITS") == 0) { @@ -1574,16 +1637,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, line, n, p_audio_config->achan[channel].fix_bits); } - if (p_audio_config->achan[channel].fix_bits > RETRY_INVERT_SINGLE) { + if (p_audio_config->achan[channel].fix_bits > DEFAULT_FIX_BITS) { 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, RETRY_INVERT_SINGLE); - } - - if (p_audio_config->achan[channel].fix_bits >= RETRY_INVERT_TWO_SEP) { - text_color_set(DW_COLOR_INFO); - dw_printf ("Line %d: Using a FIX_BITS value of %d will waste a lot of CPU power and produce wrong results.\n", - line, RETRY_INVERT_TWO_SEP); + line, DEFAULT_FIX_BITS); } t = split(NULL,0); @@ -1625,8 +1682,8 @@ void config_init (char *fname, struct audio_s *p_audio_config, * xxx serial-port [-]rts-or-dtr [ [-]rts-or-dtr ] * xxx GPIO [-]gpio-num * xxx LPT [-]bit-num - * PTT RIG model port - * PTT RIG AUTO port + * PTT RIG model port [ rate ] + * PTT RIG AUTO port [ rate ] * PTT CM108 [ [-]bit-num ] [ hid-device ] * * When model is 2, port would host:port like 127.0.0.1:4532 @@ -1727,6 +1784,13 @@ void config_init (char *fname, struct audio_s *p_audio_config, p_audio_config->achan[channel].octrl[ot].ptt_model = -1; } else { + if ( ! alldigits(t)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file line %d: A rig number, not a name, is required here.\n", line); + dw_printf ("For example, if you have a Yaesu FT-847, specify 101.\n"); + dw_printf ("See https://github.com/Hamlib/Hamlib/wiki/Supported-Radios for more details.\n"); + continue; + } int n = atoi(t); if (n < 1 || n > 9999) { text_color_set(DW_COLOR_ERROR); @@ -1744,6 +1808,19 @@ 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. + + t = split(NULL,0); + if (t != NULL) { + if ( ! alldigits(t)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file line %d: An optional number is required here for CAT serial port speed: %s\n", line, t); + continue; + } + int n = atoi(t); + p_audio_config->achan[channel].octrl[ot].ptt_rate = n; + } + t = split(NULL,0); if (t != NULL) { text_color_set(DW_COLOR_ERROR); @@ -2149,6 +2226,63 @@ void config_init (char *fname, struct audio_s *p_audio_config, } } +/* + * FX25TX n - Enable FX.25 transmission. Default off. + * 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. + */ + + else if (strcasecmp(t, "FX25TX") == 0) { + int n; + t = split(NULL,0); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing FEC mode for FX25TX command.\n", line); + continue; + } + n = atoi(t); + if (n >= 0 && n < 200) { + p_audio_config->fx25_xmit_enable = n; + } + else { + p_audio_config->fx25_xmit_enable = 1; + 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); + } + } + +/* + * FX25AUTO n - Enable Automatic use of FX.25 for connected mode. + * Automatically enable, for that session only, when an identical + * frame is sent more than this number of times. + * Default 5 based on half of default RETRY. + * 0 to disable feature. + * Current a global setting. Could be per channel someday. + */ + + else if (strcasecmp(t, "FX25AUTO") == 0) { + int n; + t = split(NULL,0); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing count for FX25AUTO command.\n", line); + continue; + } + n = atoi(t); + if (n >= 0 && n < 20) { + p_audio_config->fx25_auto_enable = n; + } + else { + p_audio_config->fx25_auto_enable = AX25_N2_RETRY_DEFAULT / 2; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Unreasonable count for connected mode automatic FX.25. Using %d.\n", + line, p_audio_config->fx25_auto_enable); + } + } + /* * ==================== APRS Digipeater parameters ==================== */ @@ -2169,6 +2303,12 @@ void config_init (char *fname, struct audio_s *p_audio_config, dw_printf ("Config file: Missing FROM-channel on line %d.\n", line); continue; } + if ( ! alldigits(t)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: '%s' is not allowed for FROM-channel. It must be a number.\n", + line, t); + continue; + } from_chan = atoi(t); if (from_chan < 0 || from_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); @@ -2176,7 +2316,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, MAX_CHANS-1, line); continue; } - if ( ! p_audio_config->achan[from_chan].valid) { + + // 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) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", line, from_chan); @@ -2189,6 +2333,12 @@ void config_init (char *fname, struct audio_s *p_audio_config, dw_printf ("Config file: Missing TO-channel on line %d.\n", line); continue; } + if ( ! alldigits(t)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: '%s' is not allowed for TO-channel. It must be a number.\n", + line, t); + continue; + } to_chan = atoi(t); if (to_chan < 0 || to_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); @@ -2196,7 +2346,9 @@ void config_init (char *fname, struct audio_s *p_audio_config, MAX_CHANS-1, line); continue; } - if ( ! p_audio_config->achan[to_chan].valid) { + + if (p_audio_config->achan[to_chan].medium != MEDIUM_RADIO && + p_audio_config->achan[to_chan].medium != MEDIUM_NETTNC) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", line, to_chan); @@ -2271,7 +2423,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, } /* - * DEDUPE - Time to suppress digipeating of duplicate packets. + * DEDUPE - Time to suppress digipeating of duplicate APRS packets. */ else if (strcasecmp(t, "DEDUPE") == 0) { @@ -2308,6 +2460,12 @@ void config_init (char *fname, struct audio_s *p_audio_config, dw_printf ("Config file: Missing FROM-channel on line %d.\n", line); continue; } + if ( ! alldigits(t)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: '%s' is not allowed for FROM-channel. It must be a number.\n", + line, t); + continue; + } from_chan = atoi(t); if (from_chan < 0 || from_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); @@ -2315,7 +2473,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, MAX_CHANS-1, line); continue; } - if ( ! p_audio_config->achan[from_chan].valid) { + + // Only radio channels are valid for regenerate. + + if (p_audio_config->achan[from_chan].medium != MEDIUM_RADIO) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", line, from_chan); @@ -2328,6 +2489,12 @@ void config_init (char *fname, struct audio_s *p_audio_config, dw_printf ("Config file: Missing TO-channel on line %d.\n", line); continue; } + if ( ! alldigits(t)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: '%s' is not allowed for TO-channel. It must be a number.\n", + line, t); + continue; + } to_chan = atoi(t); if (to_chan < 0 || to_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); @@ -2335,7 +2502,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, MAX_CHANS-1, line); continue; } - if ( ! p_audio_config->achan[to_chan].valid) { + if (p_audio_config->achan[to_chan].medium != MEDIUM_RADIO) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", line, to_chan); @@ -2367,6 +2534,12 @@ void config_init (char *fname, struct audio_s *p_audio_config, dw_printf ("Config file: Missing FROM-channel on line %d.\n", line); continue; } + if ( ! alldigits(t)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: '%s' is not allowed for FROM-channel. It must be a number.\n", + line, t); + continue; + } from_chan = atoi(t); if (from_chan < 0 || from_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); @@ -2374,10 +2547,17 @@ void config_init (char *fname, struct audio_s *p_audio_config, MAX_CHANS-1, line); continue; } - if ( ! p_audio_config->achan[from_chan].valid) { + + // For connected mode Link layer, only internal modems should be allowed. + // A network TNC probably would not provide information about channel status. + // 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) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", line, from_chan); + dw_printf ("Only internal modems can be used for connected mode packet.\n"); continue; } @@ -2387,6 +2567,12 @@ void config_init (char *fname, struct audio_s *p_audio_config, dw_printf ("Config file: Missing TO-channel on line %d.\n", line); continue; } + if ( ! alldigits(t)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: '%s' is not allowed for TO-channel. It must be a number.\n", + line, t); + continue; + } to_chan = atoi(t); if (to_chan < 0 || to_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); @@ -2394,10 +2580,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, MAX_CHANS-1, line); continue; } - if ( ! p_audio_config->achan[to_chan].valid) { + if (p_audio_config->achan[to_chan].medium != MEDIUM_RADIO) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", line, to_chan); + dw_printf ("Only internal modems can be used for connected mode packet.\n"); continue; } @@ -2442,11 +2629,24 @@ void config_init (char *fname, struct audio_s *p_audio_config, * to include IGate client side. Maybe it should be * renamed AFILTER to make it clearer after adding CFILTER. * + * Both internal modem and NET TNC channels allowed here. + * "IG" should be used for the IGate, NOT a virtual channel + * assigned to it. + * * CFILTER - Similar for connected moded digipeater. * + * Only internal modems can be used because they provide + * information about radio channel status. + * A remote network TNC might not provide the necessary + * status for correct operation. + * 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. * 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. */ else if (strcasecmp(t, "FILTER") == 0) { @@ -2471,12 +2671,19 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } - if ( ! p_audio_config->achan[from_chan].valid) { + if (p_audio_config->achan[from_chan].medium != MEDIUM_RADIO && + p_audio_config->achan[from_chan].medium != 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) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: Use 'IG' rather than %d for FROM-channel.\n", + line, from_chan); + continue; + } } t = split(NULL,0); @@ -2496,12 +2703,19 @@ void config_init (char *fname, struct audio_s *p_audio_config, MAX_CHANS-1, line); continue; } - if ( ! p_audio_config->achan[to_chan].valid) { + if (p_audio_config->achan[to_chan].medium != MEDIUM_RADIO && + p_audio_config->achan[to_chan].medium != 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) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file, line %d: Use 'IG' rather than %d for TO-channel.\n", + line, to_chan); + continue; + } } t = split(NULL,1); /* Take rest of line including spaces. */ @@ -2552,7 +2766,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } - if ( ! p_audio_config->achan[from_chan].valid) { + // 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) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", line, from_chan); @@ -2573,7 +2790,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, MAX_CHANS-1, line); continue; } - if ( ! p_audio_config->achan[to_chan].valid) { + if (p_audio_config->achan[to_chan].medium != MEDIUM_RADIO) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", line, to_chan); @@ -3730,7 +3947,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, MAX_CHANS-1, line); continue; } - if ( ! p_audio_config->achan[r].valid) { + + // 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) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TTOBJ DTMF receive channel %d is not valid.\n", line, r); @@ -3756,7 +3977,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].valid) { + else if (p_audio_config->achan[x].medium != MEDIUM_RADIO && + p_audio_config->achan[x].medium != 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; @@ -4332,6 +4554,16 @@ void config_init (char *fname, struct audio_s *p_audio_config, } } + +/* + * KISSCOPY - Data from network KISS client is copied to all others. + */ + + else if (strcasecmp(t, "KISSCOPY") == 0) { + p_misc_config->kiss_copy = 1; + } + + /* * GPSNMEA - Device name for reading from GPS receiver. */ @@ -4394,21 +4626,46 @@ void config_init (char *fname, struct audio_s *p_audio_config, } /* - * WAYPOINT - Generate WPL NMEA sentences for display on map. + * WAYPOINT - Generate WPL and AIS NMEA sentences for display on map. * * WAYPOINT serial-device [ formats ] + * WAYPOINT host:udpport [ formats ] * */ else if (strcasecmp(t, "waypoint") == 0) { + t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Config file: Missing device name for WAYPOINT on line %d.\n", line); + dw_printf ("Config file: Missing output device for WAYPOINT on line %d.\n", line); continue; } - else { - strlcpy (p_misc_config->waypoint_port, t, sizeof(p_misc_config->waypoint_port)); + + /* If there is a ':' in the name, split it into hostname:udpportnum. */ + /* Otherwise assume it is serial port name. */ + + char *p = strchr (t, ':'); + if (p != NULL) { + *p = '\0'; + int n = atoi(p+1); + if (n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) { + strlcpy (p_misc_config->waypoint_udp_hostname, t, sizeof(p_misc_config->waypoint_udp_hostname)); + if (strlen(p_misc_config->waypoint_udp_hostname) == 0) { + strlcpy (p_misc_config->waypoint_udp_hostname, "localhost", sizeof(p_misc_config->waypoint_udp_hostname)); + } + p_misc_config->waypoint_udp_portnum = n; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Invalid UDP port number %d for sending waypoints.\n", line, n); + } } + else { + strlcpy (p_misc_config->waypoint_serial_port, t, sizeof(p_misc_config->waypoint_serial_port)); + } + + /* Anthing remaining is the formats to enable. */ + t = split(NULL,1); if (t != NULL) { for ( ; *t != '\0' ; t++ ) { @@ -4425,6 +4682,9 @@ void config_init (char *fname, struct audio_s *p_audio_config, case 'K': p_misc_config->waypoint_formats |= WPT_FORMAT_KENWOOD; break; + case 'A': + p_misc_config->waypoint_formats |= WPT_FORMAT_AIS; + break; case ' ': case ',': break; @@ -4712,6 +4972,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, p_misc_config->maxframe_basic = n; } else { + p_misc_config->maxframe_basic = AX25_K_MAXFRAME_BASIC_DEFAULT; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid MAXFRAME value outside range of %d to %d. Using default %d.\n", line, AX25_K_MAXFRAME_BASIC_MIN, AX25_K_MAXFRAME_BASIC_MAX, p_misc_config->maxframe_basic); @@ -4737,6 +4998,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, p_misc_config->maxframe_extended = n; } else { + p_misc_config->maxframe_extended = AX25_K_MAXFRAME_EXTENDED_DEFAULT; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid EMAXFRAME value outside of range %d to %d. Using default %d.\n", line, AX25_K_MAXFRAME_EXTENDED_MIN, AX25_K_MAXFRAME_EXTENDED_MAX, p_misc_config->maxframe_extended); @@ -4932,7 +5194,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, } } - if (p_audio_config->achan[i].valid && strlen(p_igate_config->t2_login) > 0) { +/* 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)) { 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); @@ -4956,10 +5221,12 @@ void config_init (char *fname, struct audio_s *p_audio_config, // Apply default IS>RF IGate filter if none specified. New in 1.4. // This will handle eventual case of multiple transmit channels. - for (j=0; jachan[j].valid && strlen(p_igate_config->t2_login) > 0) { - if (p_digi_config->filter_str[MAX_CHANS][j] == NULL) { - p_digi_config->filter_str[MAX_CHANS][j] = strdup("i/30"); + 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_digi_config->filter_str[MAX_CHANS][j] == NULL) { + p_digi_config->filter_str[MAX_CHANS][j] = strdup("i/60"); + } } } } @@ -5047,7 +5314,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].valid) { + if ( n < 0 || n >= MAX_CHANS || p_audio_config->achan[n].medium == 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; @@ -5057,7 +5324,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].valid) { + if ( n < 0 || n >= MAX_CHANS || p_audio_config->achan[n].medium == 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; @@ -5068,7 +5335,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].valid) { + if ( n < 0 || n >= MAX_CHANS || p_audio_config->achan[n].medium == 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; @@ -5280,7 +5547,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].valid) { + if ( b->sendto_chan < 0 || b->sendto_chan >= MAX_CHANS || p_audio_config->achan[b->sendto_chan].medium == 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/config.h b/src/config.h similarity index 94% rename from config.h rename to src/config.h index 8a3c013..562d307 100644 --- a/config.h +++ b/src/config.h @@ -35,6 +35,7 @@ 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 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 */ /* after a while if nothing is reading from other end. */ @@ -64,10 +65,15 @@ struct misc_config_s { /* Default is 2947. */ - char waypoint_port[20]; /* Serial port name for sending NMEA waypoint sentences */ + char waypoint_serial_port[20]; /* Serial port name for sending NMEA waypoint sentences */ /* to a GPS map display or other mapping application. */ /* e.g. COM22, /dev/ttyACM0 */ /* Currently no option for setting non-standard speed. */ + /* This was done in 2014 and no one has complained yet. */ + + char waypoint_udp_hostname[80]; /* Destination host when using UDP. */ + + int waypoint_udp_portnum; /* UDP port. */ int waypoint_formats; /* Which sentence formats should be generated? */ @@ -75,6 +81,7 @@ struct misc_config_s { #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 */ int log_daily_names; /* True to generate new log file each day. */ diff --git a/decode_aprs.c b/src/decode_aprs.c similarity index 97% rename from decode_aprs.c rename to src/decode_aprs.c index 35c186b..3afa377 100644 --- a/decode_aprs.c +++ b/src/decode_aprs.c @@ -54,6 +54,7 @@ #include "dwgpsnmea.h" #include "decode_aprs.h" #include "telemetry.h" +#include "ais.h" #define TRUE 1 @@ -109,6 +110,7 @@ static void aprs_status_report (decode_aprs_t *A, char *, int); static void aprs_general_query (decode_aprs_t *A, char *, int, int quiet); static void aprs_directed_station_query (decode_aprs_t *A, char *addressee, char *query, int quiet); static void aprs_telemetry (decode_aprs_t *A, char *info, int info_len, int quiet); +static void aprs_user_defined (decode_aprs_t *A, char *, int); static void aprs_raw_touch_tone (decode_aprs_t *A, char *, int); static void aprs_morse_code (decode_aprs_t *A, char *, int); static void aprs_positionless_weather_report (decode_aprs_t *A, unsigned char *, int); @@ -164,7 +166,12 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet) A->g_quiet = quiet; - snprintf (A->g_msg_type, sizeof(A->g_msg_type), "Unknown APRS Data Type Indicator \"%c\"", *pinfo); + if (isprint(*pinfo)) { + snprintf (A->g_msg_type, sizeof(A->g_msg_type), "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); + } A->g_symbol_table = '/'; /* Default to primary table. */ A->g_symbol_code = ' '; /* What should we have for default symbol? */ @@ -219,14 +226,30 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet) if ( ( ! A->g_quiet ) && ( (int)strlen((char*)pinfo) != info_len) ) { text_color_set(DW_COLOR_ERROR); - dw_printf("'nul' character found in Information part. This should never happen.\n"); - dw_printf("It seems that %s is transmitting with defective software.\n", A->g_src); + dw_printf("'nul' character found in Information part. This should never happen with APRS.\n"); + dw_printf("If this is meant to be APRS, %s is transmitting with defective software.\n", A->g_src); if (strcmp((char*)pinfo, "4P") == 0) { dw_printf("The TM-D710 will do this intermittently. A firmware upgrade is needed to fix it.\n"); } } +/* + * Application might be in the destination field for most message types. + * MIC-E format has part of location in the destination field. + */ + + switch (*pinfo) { /* "DTI" data type identifier. */ + + case '\'': /* Old Mic-E Data */ + case '`': /* Current Mic-E Data */ + break; + + default: + decode_tocall (A, dest); + break; + } + switch (*pinfo) { /* "DTI" data type identifier. */ case '!': /* Position without timestamp (no APRS messaging). */ @@ -318,17 +341,8 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet) break; case '{': /* user defined data */ - /* http://www.aprs.org/aprs11/expfmts.txt */ - if (strncmp((char*)pinfo, "{tt", 3) == 0) { - aprs_raw_touch_tone (A, (char*)pinfo, info_len); - } - else if (strncmp((char*)pinfo, "{mc", 3) == 0) { - aprs_morse_code (A, (char*)pinfo, info_len); - } - else { - //aprs_user_defined (A, pinfo, info_len); - } + aprs_user_defined (A, (char*)pinfo, info_len); break; case 't': /* Raw touch tone data - NOT PART OF STANDARD */ @@ -373,22 +387,6 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet) symbols_from_dest_or_src (*pinfo, A->g_src, dest, &A->g_symbol_table, &A->g_symbol_code); } - -/* - * Application might be in the destination field for most message types. - * MIC-E format has part of location in the destination field. - */ - - switch (*pinfo) { /* "DTI" data type identifier. */ - - case '\'': /* Old Mic-E Data */ - case '`': /* Current Mic-E Data */ - break; - - default: - decode_tocall (A, dest); - break; - } } /* end decode_aprs */ @@ -478,6 +476,7 @@ void decode_aprs_print (decode_aprs_t *A) { * Any example was checked for each hemihemisphere using * http://www.amsat.org/cgi-bin/gridconv */ +// FIXME soften language about upper case. if (strlen(A->g_maidenhead) > 0) { @@ -920,20 +919,24 @@ static void aprs_ll_pos_time (decode_aprs_t *A, unsigned char *info, int ilen) static void aprs_raw_nmea (decode_aprs_t *A, unsigned char *info, int ilen) { - if (strncmp((char*)info, "$GPRMC,", 7) == 0) + if (strncmp((char*)info, "$GPRMC,", 7) == 0 || + strncmp((char*)info, "$GNRMC,", 7) == 0) { float speed_knots = G_UNKNOWN; (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)); } - else if (strncmp((char*)info, "$GPGGA,", 7) == 0) + else if (strncmp((char*)info, "$GPGGA,", 7) == 0 || + strncmp((char*)info, "$GNGGA,", 7) == 0) { float alt_meters = G_UNKNOWN; int num_sat = 0; (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)); } // TODO (low): add a few other sentence types. @@ -1377,11 +1380,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-400DR", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ')') { strlcpy (A->g_mfr, "Yaesu FTM-100D", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '(') { strlcpy (A->g_mfr, "Yaesu FT2D", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '0') { strlcpy (A->g_mfr, "Yaesu FT3D", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '`' && *(plast-1) == ' ' && *plast == '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, "Mic-Emsg", sizeof(A->g_mfr)); pfirst++; } + else if (*pfirst == '\'' && *(plast-1) == '(' && *plast == '8') { strlcpy (A->g_mfr, "Anytone D878UV", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '3') { strlcpy (A->g_mfr, "Byonics TinyTrack3", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '4') { strlcpy (A->g_mfr, "Byonics TinyTrack4", sizeof(A->g_mfr)); pfirst++; plast-=2; } @@ -1961,6 +1968,7 @@ static void aprs_status_report (decode_aprs_t *A, char *info, int ilen) /* * Do we have format with 6 character Maidenhead locator? */ + else if (get_maidenhead (A, pm6->mhead6) == 6) { memset (A->g_maidenhead, 0, sizeof(A->g_maidenhead)); @@ -2304,6 +2312,54 @@ static void aprs_telemetry (decode_aprs_t *A, char *info, int ilen, int quiet) } /* end aprs_telemetry */ +/*------------------------------------------------------------------ + * + * Function: aprs_user_defined + * + * Purpose: Decode user defined data. + * + * Inputs: info - Pointer to Information field. + * ilen - Information field length. + * + * Description: APRS Protocol Specification, Chapter 18 + * User IDs allocated here: http://www.aprs.org/aprs11/expfmts.txt + * + *------------------------------------------------------------------*/ + +static void aprs_user_defined (decode_aprs_t *A, char *info, int ilen) +{ + if (strncmp(info, "{tt", 3) == 0) { // Historical. Should probably use DT. + aprs_raw_touch_tone (A, info, ilen); + } + else if (strncmp(info, "{mc", 3) == 0) { // Historical. Should probably use DM. + aprs_morse_code (A, info, ilen); + } + else if (info[0] == '{' && info[1] == USER_DEF_USER_ID && info[2] == USER_DEF_TYPE_AIS) { + double lat, lon; + 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), + &lat, &lon, &knots, &course, &alt_meters, &(A->g_symbol_table), &(A->g_symbol_code), + A->g_comment, sizeof(A->g_comment)); + + A->g_lat = lat; + A->g_lon = lon; + A->g_speed_mph = DW_KNOTS_TO_MPH(knots); + A->g_course = course; + A->g_altitude_ft = DW_METERS_TO_FEET(alt_meters); + strcpy (A->g_mfr, ""); + } + else if (strncmp(info, "{{", 2) == 0) { + snprintf (A->g_msg_type, sizeof(A->g_msg_type), "User-Defined Experimental"); + } + else { + snprintf (A->g_msg_type, sizeof(A->g_msg_type), "User-Defined Data"); + } + +} /* end aprs_user_defined */ + + /*------------------------------------------------------------------ * * Function: aprs_raw_touch_tone @@ -3723,7 +3779,7 @@ static int data_extension_comment (decode_aprs_t *A, char *pdext) // Dec. 2016 tocalls.txt has 153 destination addresses. -#define MAX_TOCALLS 200 +#define MAX_TOCALLS 250 static struct tocalls_s { unsigned char len; @@ -3734,10 +3790,12 @@ static struct tocalls_s { static int num_tocalls = 0; // Make sure the array is null terminated. -// If search order is changed, do the same in symbols.c +// If search order is changed, do the same in symbols.c for consistency. static const char *search_locations[] = { - (const char *) "tocalls.txt", + (const char *) "tocalls.txt", // CWD + (const char *) "data/tocalls.txt", // Windows with CMake + (const char *) "../data/tocalls.txt", // ? #ifndef __WIN32__ (const char *) "/usr/local/share/direwolf/tocalls.txt", (const char *) "/usr/share/direwolf/tocalls.txt", @@ -3749,7 +3807,7 @@ static const char *search_locations[] = { // path as well. (const char *) "/opt/local/share/direwolf/tocalls.txt", #endif - (const char *) NULL + (const char *) NULL // Important - Indicates end of list. }; static int tocall_cmp (const void *px, const void *py) @@ -4630,6 +4688,9 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) * Outputs: stdout * * Description: Compile like this to make a standalone test program. + * Just run "make" and this will be built along with everything else. + * The point I'm trying to make is that DECAMAIN must be defined + * to enable the main program here for a standalone application. * * gcc -o decode_aprs -DDECAMAIN decode_aprs.c ax25_pad.c ... * @@ -4748,6 +4809,7 @@ int main (int argc, char *argv[]) } } + // If you don't like the text colors, use 0 instead of 1 here. text_color_init(1); text_color_set(DW_COLOR_INFO); diff --git a/decode_aprs.h b/src/decode_aprs.h similarity index 100% rename from decode_aprs.h rename to src/decode_aprs.h diff --git a/dedupe.c b/src/dedupe.c similarity index 100% rename from dedupe.c rename to src/dedupe.c diff --git a/dedupe.h b/src/dedupe.h similarity index 100% rename from dedupe.h rename to src/dedupe.h diff --git a/demod.c b/src/demod.c similarity index 85% rename from demod.c rename to src/demod.c index 3953622..281367b 100644 --- a/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 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2019 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 @@ -47,7 +47,6 @@ #include "tune.h" #include "fsk_demod_state.h" #include "fsk_gen_filter.h" -#include "fsk_fast_filter.h" #include "hdlc_rec.h" #include "textcolor.h" #include "demod_9600.h" @@ -63,7 +62,7 @@ static struct audio_s *save_audio_config_p; // TODO: temp experiment. -static int upsample = 2; // temp experiment. + static int zerostuff = 1; // temp experiment. // Current state of all the decoders. @@ -94,7 +93,6 @@ static int sample_count[MAX_CHANS][MAX_SUBCHANS]; int demod_init (struct audio_s *pa) { - //int j; int chan; /* Loop index over number of radio channels. */ char profile; @@ -108,7 +106,7 @@ int demod_init (struct audio_s *pa) for (chan = 0; chan < MAX_CHANS; chan++) { - if (save_audio_config_p->achan[chan].valid) { + if (save_audio_config_p->achan[chan].medium == MEDIUM_RADIO) { char *p; char just_letters[16]; @@ -122,7 +120,6 @@ int demod_init (struct audio_s *pa) * This can be increased by: * Multiple frequencies. * Multiple letters (not sure if I will continue this). - * New interleaved decoders. * * num_slicers is set to max by the "+" option. */ @@ -136,6 +133,20 @@ int demod_init (struct audio_s *pa) break; case MODEM_AFSK: + case MODEM_EAS: + + if (save_audio_config_p->achan[chan].modem_type == MODEM_EAS) { + if (save_audio_config_p->achan[chan].fix_bits != RETRY_NONE) { + text_color_set(DW_COLOR_INFO); + dw_printf ("Channel %d: FIX_BITS option has been turned off for EAS.\n", chan); + save_audio_config_p->achan[chan].fix_bits = RETRY_NONE; + } + if (save_audio_config_p->achan[chan].passall != 0) { + text_color_set(DW_COLOR_INFO); + dw_printf ("Channel %d: PASSALL option has been turned off for EAS.\n", chan); + save_audio_config_p->achan[chan].passall = 0; + } + } /* * Tear apart the profile and put it back together in a normalized form: @@ -315,48 +326,6 @@ int demod_init (struct audio_s *pa) save_audio_config_p->achan[chan].num_subchan = num_letters; -/* - * Quick hack with special case for another experiment. - * Do this in a more general way if it turns out to be useful. - */ - save_audio_config_p->achan[chan].interleave = 1; - if (strcasecmp(save_audio_config_p->achan[chan].profiles, "EE") == 0) { - save_audio_config_p->achan[chan].interleave = 2; - save_audio_config_p->achan[chan].decimate = 1; - } - else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "EEE") == 0) { - save_audio_config_p->achan[chan].interleave = 3; - save_audio_config_p->achan[chan].decimate = 1; - } - else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "EEEE") == 0) { - save_audio_config_p->achan[chan].interleave = 4; - save_audio_config_p->achan[chan].decimate = 1; - } - else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "EEEEE") == 0) { - save_audio_config_p->achan[chan].interleave = 5; - save_audio_config_p->achan[chan].decimate = 1; - } - else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GG") == 0) { - save_audio_config_p->achan[chan].interleave = 2; - save_audio_config_p->achan[chan].decimate = 1; - } - else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GGG") == 0) { - save_audio_config_p->achan[chan].interleave = 3; - save_audio_config_p->achan[chan].decimate = 1; - } - else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GGG+") == 0) { - save_audio_config_p->achan[chan].interleave = 3; - save_audio_config_p->achan[chan].decimate = 1; - } - else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GGGG") == 0) { - save_audio_config_p->achan[chan].interleave = 4; - save_audio_config_p->achan[chan].decimate = 1; - } - else if (strcasecmp(save_audio_config_p->achan[chan].profiles, "GGGGG") == 0) { - save_audio_config_p->achan[chan].interleave = 5; - save_audio_config_p->achan[chan].decimate = 1; - } - if (save_audio_config_p->achan[chan].num_subchan != num_letters) { text_color_set(DW_COLOR_ERROR); dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_subchan(%d) != strlen(\"%s\")\n", @@ -385,7 +354,7 @@ int demod_init (struct audio_s *pa) dw_printf (" %d.%d: %c %d & %d\n", chan, d, profile, mark, space); } - demod_afsk_init (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / (save_audio_config_p->achan[chan].decimate * save_audio_config_p->achan[chan].interleave), + demod_afsk_init (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate, save_audio_config_p->achan[chan].baud, mark, space, @@ -520,6 +489,31 @@ int demod_init (struct audio_s *pa) case MODEM_QPSK: // New for 1.4 + // 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. + + // In version 1.6 we add a choice for the user. + // If neither one was explicitly specified, print a message and take + // a default. My current thinking is that we default to direwolf <= 1.5 + // compatible for version 1.6 and MFJ compatible after that. + + if (save_audio_config_p->achan[chan].v26_alternative == V26_UNSPECIFIED) { + + 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 ("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"); + dw_printf ("The default is now MFJ-2400 compatibility mode.\n"); + + save_audio_config_p->achan[chan].v26_alternative = V26_DEFAULT; + } + + // TODO: See how much CPU this takes on ARM and decide if we should have different defaults. if (strlen(save_audio_config_p->achan[chan].profiles) == 0) { @@ -539,6 +533,12 @@ int demod_init (struct audio_s *pa) save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec); if (save_audio_config_p->achan[chan].decimate != 1) dw_printf (" / %d", save_audio_config_p->achan[chan].decimate); + + if (save_audio_config_p->achan[chan].v26_alternative == V26_B) + dw_printf (", compatible with MFJ-2400"); + else + dw_printf (", compatible with earlier direwolf"); + if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF) dw_printf (", DTMF decoder enabled"); dw_printf (".\n"); @@ -556,6 +556,7 @@ int demod_init (struct audio_s *pa) // save_audio_config_p->achan[chan].modem_type, profile); demod_psk_init (save_audio_config_p->achan[chan].modem_type, + save_audio_config_p->achan[chan].v26_alternative, save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate, save_audio_config_p->achan[chan].baud, profile, @@ -610,6 +611,7 @@ int demod_init (struct audio_s *pa) // save_audio_config_p->achan[chan].modem_type, profile); demod_psk_init (save_audio_config_p->achan[chan].modem_type, + save_audio_config_p->achan[chan].v26_alternative, save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate, save_audio_config_p->achan[chan].baud, profile, @@ -630,9 +632,26 @@ int demod_init (struct audio_s *pa) case MODEM_BASEBAND: case MODEM_SCRAMBLE: + case MODEM_AIS: default: /* Not AFSK */ { + // For AIS we will accept only a good CRC without any fixup attempts. + // Even with that, there are still a lot of CRC false matches with random noise. + + if (save_audio_config_p->achan[chan].modem_type == MODEM_AIS) { + if (save_audio_config_p->achan[chan].fix_bits != RETRY_NONE) { + text_color_set(DW_COLOR_INFO); + dw_printf ("Channel %d: FIX_BITS option has been turned off for AIS.\n", chan); + save_audio_config_p->achan[chan].fix_bits = RETRY_NONE; + } + if (save_audio_config_p->achan[chan].passall != 0) { + text_color_set(DW_COLOR_INFO); + dw_printf ("Channel %d: PASSALL option has been turned off for AIS.\n", chan); + save_audio_config_p->achan[chan].passall = 0; + } + } + if (strcmp(save_audio_config_p->achan[chan].profiles, "") == 0) { /* Apply default if not set earlier. */ @@ -643,25 +662,73 @@ int demod_init (struct audio_s *pa) /* We want higher performance to be the default. */ /* "MODEM 9600 -" can be used on very slow CPU if necessary. */ -//#ifndef __arm__ strlcpy (save_audio_config_p->achan[chan].profiles, "+", sizeof(save_audio_config_p->achan[chan].profiles)); -//#endif } -#ifdef TUNE_UPSAMPLE - upsample = TUNE_UPSAMPLE; -#endif - #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. + */ + + float ratio = (float)(save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec) + / (float)(save_audio_config_p->achan[chan].baud); + +/* + * Set reasonable upsample ratio if user did not override. + */ + + if (save_audio_config_p->achan[chan].upsample == 0) { + + if (ratio < 5) { + + // 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. + + save_audio_config_p->achan[chan].upsample = 4; + } + else if (ratio < 10) { + + // 48000 / 9600 is 5.00 + // Need more reasearch. Treat like above for now. + + save_audio_config_p->achan[chan].upsample = 4; + } + else if (ratio < 15) { + + // ... + + save_audio_config_p->achan[chan].upsample = 2; + } + else { // >= 15 + // + // An example of this might be ..... + // Probably no benefit. + + save_audio_config_p->achan[chan].upsample = 1; + } + } + +#ifdef TUNE_UPSAMPLE + save_audio_config_p->achan[chan].upsample = TUNE_UPSAMPLE; +#endif + text_color_set(DW_COLOR_DEBUG); - dw_printf ("Channel %d: %d baud, K9NG/G3RUH, %s, %d sample rate x %d", - chan, save_audio_config_p->achan[chan].baud, + dw_printf ("Channel %d: %d baud, %s, %s, %d sample rate x %d", + chan, + save_audio_config_p->achan[chan].baud, + save_audio_config_p->achan[chan].modem_type == MODEM_AIS ? "AIS" : "K9NG/G3RUH", save_audio_config_p->achan[chan].profiles, - save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, upsample); + save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, + save_audio_config_p->achan[chan].upsample); if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF) dw_printf (", DTMF decoder enabled"); dw_printf (".\n"); @@ -669,6 +736,7 @@ int demod_init (struct audio_s *pa) struct demodulator_state_s *D; D = &demodulator_state[chan][0]; // first subchannel + save_audio_config_p->achan[chan].num_subchan = 1; save_audio_config_p->achan[chan].num_slicers = 1; @@ -681,12 +749,6 @@ int demod_init (struct audio_s *pa) } - /* 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. */ - - float ratio = (float)(save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec) - / (float)(save_audio_config_p->achan[chan].baud); - text_color_set(DW_COLOR_INFO); dw_printf ("The ratio of audio samples per sec (%d) to data rate in baud (%d) is %.1f\n", save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, @@ -698,6 +760,9 @@ int demod_init (struct audio_s *pa) } else if (ratio < 5) { dw_printf ("This is on the low side for best performance. Can you use a higher sample rate?\n"); + if (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec == 44100) { + dw_printf ("For example, can you use 48000 rather than 44100?\n"); + } } else if (ratio < 6) { dw_printf ("Increasing the sample rate should improve decoder performance.\n"); @@ -709,7 +774,9 @@ int demod_init (struct audio_s *pa) dw_printf ("This is a suitable ratio for good performance.\n"); } - demod_9600_init (upsample * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, save_audio_config_p->achan[chan].baud, D); + 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->achan[chan].baud, D); if (strchr(save_audio_config_p->achan[chan].profiles, '+') != NULL) { @@ -901,6 +968,7 @@ void demod_process_sample (int chan, int subchan, int sam) break; case MODEM_AFSK: + case MODEM_EAS: if (save_audio_config_p->achan[chan].decimate > 1) { @@ -934,6 +1002,7 @@ void demod_process_sample (int chan, int subchan, int sam) case MODEM_BASEBAND: case MODEM_SCRAMBLE: + case MODEM_AIS: default: if (zerostuff) { @@ -942,17 +1011,17 @@ void demod_process_sample (int chan, int subchan, int sam) /* 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 * upsample, D); + demod_9600_process_sample (chan, sam * save_audio_config_p->achan[chan].upsample, D); } else { /* Linear interpolation. */ static int prev_sam; - switch (upsample) { + switch (save_audio_config_p->achan[chan].upsample) { case 1: demod_9600_process_sample (chan, sam, D); break; @@ -1016,7 +1085,8 @@ alevel_t demod_get_audio_level (int chan, int subchan) alevel.rec = (int) (( D->alevel_rec_peak - D->alevel_rec_valley ) * 50.0f + 0.5f); - if (save_audio_config_p->achan[chan].modem_type == MODEM_AFSK) { + if (save_audio_config_p->achan[chan].modem_type == MODEM_AFSK || + save_audio_config_p->achan[chan].modem_type == MODEM_EAS) { /* For AFSK, we have mark and space amplitudes. */ diff --git a/demod.h b/src/demod.h similarity index 100% rename from demod.h rename to src/demod.h diff --git a/demod_9600.c b/src/demod_9600.c similarity index 89% rename from demod_9600.c rename to src/demod_9600.c index e94f8aa..2f98983 100644 --- a/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 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2015, 2019 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 @@ -44,14 +44,24 @@ #include #include +// Fine tuning for different demodulator types. + +#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" -#include "fsk_demod_state.h" #include "hdlc_rec.h" #include "demod_9600.h" #include "textcolor.h" #include "dsp.h" + + static float slice_point[MAX_SUBCHANS]; @@ -113,7 +123,9 @@ static inline float agc (float in, float fast_attack, float slow_decay, float *p * * Purpose: Initialize the 9600 (or higher) baud demodulator. * - * Inputs: samples_per_sec - Number of samples per second. + * 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. * @@ -125,12 +137,13 @@ static inline float agc (float in, float fast_attack, float slow_decay, float *p * *----------------------------------------------------------------*/ -void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s *D) +void demod_9600_init (enum modem_t modem_type, int samples_per_sec, int baud, struct demodulator_state_s *D) { float fc; int j; memset (D, 0, sizeof(struct demodulator_state_s)); + D->modem_type = modem_type; D->num_slicers = 1; // Multiple profiles in future? @@ -141,23 +154,37 @@ void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s // case 'K': // upsample x3 with filtering. // case 'L': // upsample x4 with filtering. - D->lp_filter_len_bits = 76 * 9600.0 / (44100.0 * 2.0); + + D->lp_filter_len_bits = 1.0; // 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) (( D->lp_filter_len_bits * (float)samples_per_sec / baud) + 0.5f); - D->lp_window = BP_WINDOW_HAMMING; - D->lpf_baud = 0.62; + D->lp_window = BP_WINDOW_COSINE; + + D->lpf_baud = 1.00; D->agc_fast_attack = 0.080; D->agc_slow_decay = 0.00012; D->pll_locked_inertia = 0.89; D->pll_searching_inertia = 0.67; + // break; // } +#if 0 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("---------- %s (%d, %d) -----------\n", __func__, samples_per_sec, baud); + dw_printf ("filter_len_bits = %.2f\n", D->lp_filter_len_bits); + dw_printf ("lp_filter_size = %d\n", D->lp_filter_size); + dw_printf ("lp_window = %d\n", D->lp_window); + dw_printf ("lpf_baud = %.2f\n", D->lpf_baud); + dw_printf ("samples per bit = %.1f\n", (double)samples_per_sec / baud); +#endif + D->pll_step_per_sample = (int) round(TICKS_PER_PLL_CYCLE * (double) baud / (double)samples_per_sec); @@ -194,7 +221,7 @@ void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s //dw_printf ("demod_9600_init: call gen_lowpass(fc=%.2f, , size=%d, )\n", fc, D->lp_filter_size); - gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window); + (void)gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window, 0); /* Version 1.2: Experiment with different slicing levels. */ @@ -390,7 +417,7 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D if (chan == 0) { if (1) { - //if (hdlc_rec_gathering (chan, subchan, slice)) { + //if (D->slicer[slice].data_detect) { char fname[30]; int slice = 0; @@ -481,15 +508,14 @@ void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D * * Results??? TBD * + * Version 1.6: New experiment where filter size to extract clock is not the same + * as filter to extract the data bit value. + * *--------------------------------------------------------------------*/ __attribute__((hot)) inline static void nudge_pll (int chan, int subchan, int slice, float demod_out_f, struct demodulator_state_s *D) { - -/* - */ - D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll; // Perform the add as unsigned to avoid signed overflow error. @@ -499,7 +525,8 @@ inline static void nudge_pll (int chan, int subchan, int slice, float demod_out_ /* Overflow. Was large positive, wrapped around, now large negative. */ - hdlc_rec_bit (chan, subchan, slice, demod_out_f > 0, 1, D->slicer[slice].lfsr); + hdlc_rec_bit (chan, subchan, slice, demod_out_f > 0, D->modem_type == MODEM_SCRAMBLE, D->slicer[slice].lfsr); + pll_dcd_each_symbol2 (D, chan, subchan, slice); } /* @@ -510,11 +537,11 @@ inline static void nudge_pll (int chan, int subchan, int slice, float demod_out_ // Note: Test for this demodulator, not overall for channel. - float target = 0; + pll_dcd_signal_transition2 (D, slice, D->slicer[slice].data_clock_pll); - target = D->pll_step_per_sample * demod_out_f / (demod_out_f - D->slicer[slice].prev_demod_out_f); + float target = D->pll_step_per_sample * demod_out_f / (demod_out_f - D->slicer[slice].prev_demod_out_f); - if (hdlc_rec_gathering (chan, subchan, slice)) { + if (D->slicer[slice].data_detect) { D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia + target * (1.0f - D->pll_locked_inertia) ); } else { @@ -526,7 +553,7 @@ inline static void nudge_pll (int chan, int subchan, int slice, float demod_out_ #if DEBUG5 //if (chan == 0) { - if (hdlc_rec_gathering (chan,subchan,slice)) { + if (D->slicer[slice].data_detect) { char fname[30]; diff --git a/demod_9600.h b/src/demod_9600.h similarity index 76% rename from demod_9600.h rename to src/demod_9600.h index a764711..ac3e747 100644 --- a/demod_9600.h +++ b/src/demod_9600.h @@ -6,7 +6,7 @@ #include "fsk_demod_state.h" -void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s *D); +void demod_9600_init (enum modem_t modem_type, int samples_per_sec, int baud, struct demodulator_state_s *D); void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D); diff --git a/demod_afsk.c b/src/demod_afsk.c similarity index 72% rename from demod_afsk.c rename to src/demod_afsk.c index 80bd40e..7a007d1 100644 --- a/demod_afsk.c +++ b/src/demod_afsk.c @@ -63,13 +63,11 @@ #define MAX(a,b) ((a)>(b)?(a):(b)) -#ifndef GEN_FFF - /* Quick approximation to sqrt(x*x+y*y) */ /* No benefit for regular PC. */ /* Should help with microcomputer platform. */ - +#if 0 // not using anymore __attribute__((hot)) __attribute__((always_inline)) static inline float z (float x, float y) { @@ -83,6 +81,7 @@ static inline float z (float x, float y) return (y * .941246f + x * .41f); } } +#endif /* Add sample to buffer and shift the rest down. */ @@ -137,8 +136,6 @@ static inline float agc (float in, float fast_attack, float slow_decay, float *p return (0.0f); } -#endif // ifndef GEN_FFF - /* * for multi-slicer experiment. @@ -197,99 +194,10 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, #endif - if (profile == 'F') { - - if (baud != DEFAULT_BAUD || - mark_freq != DEFAULT_MARK_FREQ || - space_freq!= DEFAULT_SPACE_FREQ || - samples_per_sec != DEFAULT_SAMPLES_PER_SEC) { - - text_color_set(DW_COLOR_INFO); - dw_printf ("Note: Decoder 'F' works only for %d baud, %d/%d tones, %d samples/sec.\n", - DEFAULT_BAUD, DEFAULT_MARK_FREQ, DEFAULT_SPACE_FREQ, DEFAULT_SAMPLES_PER_SEC); - dw_printf ("Using Decoder 'A' instead.\n"); - profile = 'A'; - } - } - D->profile = profile; // so we know whether to take fast path later. switch (profile) { - case 'A': - case 'F': - - /* Original. 52 taps, truncated bandpass, IIR lowpass */ - /* 'F' is the fast version for low end processors. */ - /* It is a special case that works only for a particular */ - /* baud rate, tone pair, and sampling rate. */ - - D->use_prefilter = 0; - - D->ms_filter_len_bits = 1.415; /* 52 @ 44100, 1200 */ - D->ms_window = BP_WINDOW_TRUNCATED; - - //D->bp_window = BP_WINDOW_TRUNCATED; - - D->lpf_use_fir = 0; - D->lpf_iir = 0.195; - - D->agc_fast_attack = 0.250; - D->agc_slow_decay = 0.00012; - D->hysteresis = 0.005; - - D->pll_locked_inertia = 0.700; - D->pll_searching_inertia = 0.580; - break; - - case 'B': - - /* Original bandpass. Use FIR lowpass instead. */ - - D->use_prefilter = 0; - - D->ms_filter_len_bits = 1.415; /* 52 @ 44100, 1200 */ - D->ms_window = BP_WINDOW_TRUNCATED; - - //D->bp_window = BP_WINDOW_TRUNCATED; - - D->lpf_use_fir = 1; - D->lpf_baud = 1.09; - D->lp_filter_len_bits = D->ms_filter_len_bits; - D->lp_window = BP_WINDOW_TRUNCATED; - - D->agc_fast_attack = 0.370; - D->agc_slow_decay = 0.00014; - D->hysteresis = 0.003; - - D->pll_locked_inertia = 0.620; - D->pll_searching_inertia = 0.350; - break; - - case 'C': - - /* Cosine window, 76 taps for bandpass, FIR lowpass. */ - - D->use_prefilter = 0; - - D->ms_filter_len_bits = 2.068; /* 76 @ 44100, 1200 */ - D->ms_window = BP_WINDOW_COSINE; - - //D->bp_window = BP_WINDOW_COSINE; - - D->lpf_use_fir = 1; - D->lpf_baud = 1.09; - 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.005; - - D->pll_locked_inertia = 0.620; - D->pll_searching_inertia = 0.350; - break; - case 'D': /* Prefilter, Cosine window, FIR lowpass. Tweeked for 300 baud. */ @@ -317,6 +225,7 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, D->pll_searching_inertia = 0.350; break; + case 'F': // removed obsolete. treat as E for now. case 'E': /* 1200 baud - Started out similar to C but add prefilter. */ @@ -354,34 +263,6 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, D->pll_searching_inertia = 0.50; break; - case 'G': - - /* 1200 baud - Started out same as E but add 3 way interleave. */ - /* Version 1.3 - EXPERIMENTAL - Needs more fine tuning. */ - - //D->bp_window = BP_WINDOW_COSINE; /* The name says BP but it is used for all of them. */ - - D->use_prefilter = 1; /* first, a bandpass filter. */ - D->prefilter_baud = 0.15; - D->pre_filter_len_bits = 128 * 1200. / (44100. / 3.); - D->pre_window = BP_WINDOW_TRUNCATED; - - D->ms_filter_len_bits = 25 * 1200. / (44100. / 3.); - D->ms_window = BP_WINDOW_COSINE; - - D->lpf_use_fir = 1; - D->lpf_baud = 1.16; - D->lp_filter_len_bits = 21 * 1200. / (44100. / 3.); - D->lp_window = BP_WINDOW_TRUNCATED; - - D->agc_fast_attack = 0.130; - D->agc_slow_decay = 0.00013; - D->hysteresis = 0.01; - - D->pll_locked_inertia = 0.73; - D->pll_searching_inertia = 0.64; - break; - default: text_color_set(DW_COLOR_ERROR); @@ -395,6 +276,9 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, #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 @@ -417,14 +301,25 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, #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. + * Probably makes no difference because the DPLL keeps it in sync. + * + * A fraction if a Hz would make no difference for the filters. */ - - D->pll_step_per_sample = (int) round((TICKS_PER_PLL_CYCLE * (double)baud) / ((double)samples_per_sec)); + if (baud == 521) { + D->pll_step_per_sample = (int) round((TICKS_PER_PLL_CYCLE * (double)520.83) / ((double)samples_per_sec)); + } + else { + 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. @@ -450,7 +345,7 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, assert (D->ms_filter_size >= 4); //assert (D->lp_filter_size >= 4); - if (D->pre_filter_size > MAX_FILTER_SIZE) + 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); @@ -460,7 +355,7 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, exit (1); } - if (D->ms_filter_size > MAX_FILTER_SIZE) + 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); @@ -470,6 +365,8 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, exit (1); } + + if (D->lp_filter_size > MAX_FILTER_SIZE) { text_color_set (DW_COLOR_ERROR); @@ -502,10 +399,6 @@ 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, BP_WINDOW_HAMMING); - //gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, BP_WINDOW_BLACKMAN); - //gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, BP_WINDOW_COSINE); - //gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, D->bp_window); gen_bandpass (f1, f2, D->pre_filter, D->pre_filter_size, D->pre_window); } @@ -525,46 +418,8 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, dw_printf (" j shape M sin M cos \n"); #endif - float Gs = 0, Gc = 0; - - for (j=0; jms_filter_size; j++) { - float am; - float center; - float shape = 1.0f; /* Shape is an attempt to smooth out the */ - /* abrupt edges in hopes of reducing */ - /* overshoot and ringing. */ - /* My first thought was to use a cosine shape. */ - /* Should investigate Hamming and Blackman */ - /* windows mentioned in the literature. */ - /* http://en.wikipedia.org/wiki/Window_function */ - - center = 0.5f * (D->ms_filter_size - 1); - am = ((float)(j - center) / (float)samples_per_sec) * ((float)mark_freq) * (2.0f * (float)M_PI); - - shape = window (D->ms_window, D->ms_filter_size, j); - - D->m_sin_table[j] = sinf(am) * shape; - D->m_cos_table[j] = cosf(am) * shape; - - Gs += D->m_sin_table[j] * sinf(am); - Gc += D->m_cos_table[j] * cosf(am); - -#if DEBUG1 - dw_printf ("%6d %6.2f %6.2f %6.2f\n", j, shape, D->m_sin_table[j], D->m_cos_table[j]) ; -#endif - } - - -/* Normalize for unity gain */ - -#if DEBUG1 - dw_printf ("Before normalizing, Gs = %.2f, Gc = %.2f\n", Gs, Gc) ; -#endif - for (j=0; jms_filter_size; j++) { - D->m_sin_table[j] = D->m_sin_table[j] / Gs; - D->m_cos_table[j] = D->m_cos_table[j] / Gc; - } + 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); @@ -572,40 +427,8 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, dw_printf ("Space\n"); dw_printf (" j shape S sin S cos\n"); #endif - Gs = 0; - Gc = 0; - for (j=0; jms_filter_size; j++) { - float as; - float center; - float shape = 1.0f; - - center = 0.5 * (D->ms_filter_size - 1); - as = ((float)(j - center) / (float)samples_per_sec) * ((float)space_freq) * (2.0f * (float)M_PI); - - shape = window (D->ms_window, D->ms_filter_size, j); - - D->s_sin_table[j] = sinf(as) * shape; - D->s_cos_table[j] = cosf(as) * shape; - - Gs += D->s_sin_table[j] * sinf(as); - Gc += D->s_cos_table[j] * cosf(as); - -#if DEBUG1 - dw_printf ("%6d %6.2f %6.2f %6.2f\n", j, shape, D->s_sin_table[j], D->s_cos_table[j] ) ; -#endif - } - - -/* Normalize for unity gain */ - -#if DEBUG1 - dw_printf ("Before normalizing, Gs = %.2f, Gc = %.2f\n", Gs, Gc) ; -#endif - for (j=0; jms_filter_size; j++) { - D->s_sin_table[j] = D->s_sin_table[j] / Gs; - D->s_cos_table[j] = D->s_cos_table[j] / Gc; - } + 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. @@ -616,7 +439,11 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, if (D->lpf_use_fir) { float fc; fc = baud * D->lpf_baud / (float)samples_per_sec; - gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window); + 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. } /* @@ -678,7 +505,6 @@ failed experiment space_gain[j] = space_gain[j-1] * step; } -#ifndef GEN_FFF #if 0 text_color_set(DW_COLOR_DEBUG); for (j=0; juse_prefilter) { float cleaner; 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); + push_sample (cleaner, D->ms_in_cb, D->ms_filter_size + extra); } else { - push_sample (fsam, D->ms_in_cb, D->ms_filter_size); + push_sample (fsam, D->ms_in_cb, D->ms_filter_size + extra); } /* * Next we have bandpass filters for the mark and space tones. - * - * This takes a lot of computation. - * It's not a problem on a typical (Intel x86 based) PC. - * Dire Wolf takes only about 2 or 3% of the CPU time. - * - * It might be too much for a little microcomputer to handle. - * - * Here we have an optimized case for the default values. */ - - -// TODO1.2: is this right or do we need to store profile in the modulator info? - - - if (D->profile == toupper(FFF_PROFILE)) { - - /* ========== Faster for default values on slower processors. ========== */ - - m_sum1 = CALC_M_SUM1(D->ms_in_cb); - m_sum2 = CALC_M_SUM2(D->ms_in_cb); - m_amp = z(m_sum1,m_sum2); - - s_sum1 = CALC_S_SUM1(D->ms_in_cb); - s_sum2 = CALC_S_SUM2(D->ms_in_cb); - s_amp = z(s_sum1,s_sum2); - } - else { - - /* ========== General case to handle all situations. ========== */ - /* * find amplitude of "Mark" tone. */ @@ -913,9 +634,6 @@ void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulat s_amp = sqrtf(s_sum1 * s_sum1 + s_sum2 * s_sum2); - /* ========== End of general case. ========== */ - } - /* * Apply some low pass filtering BEFORE the AGC to remove @@ -978,7 +696,7 @@ void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulat * Here is an excellent explanation: * http://www.febo.com/packet/layer-one/transmit.html * - * Under real conditions, we find that the higher tone has a + * 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. @@ -1054,7 +772,7 @@ void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulat #if DEBUG4 if (chan == 0) { - if (hdlc_rec_gathering (chan, subchan)) { + if (D->slicer[slice].data_detect) { char fname[30]; @@ -1131,14 +849,18 @@ 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. */ - + /* Overflow - this is where we sample. */ hdlc_rec_bit (chan, subchan, slice, demod_data, 0, -1); + pll_dcd_each_symbol2 (D, chan, subchan, slice); } + // Transitions nudge the DPLL phase toward the incoming signal. + if (demod_data != D->slicer[slice].prev_demod_data) { - if (hdlc_rec_gathering (chan, subchan, slice)) { + pll_dcd_signal_transition2 (D, slice, D->slicer[slice].data_clock_pll); + + if (D->slicer[slice].data_detect) { D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia); } else { @@ -1154,7 +876,4 @@ inline static void nudge_pll (int chan, int subchan, int slice, int demod_data, } /* end nudge_pll */ -#endif /* GEN_FFF */ - - /* end demod_afsk.c */ diff --git a/demod_afsk.h b/src/demod_afsk.h similarity index 100% rename from demod_afsk.h rename to src/demod_afsk.h diff --git a/demod_psk.c b/src/demod_psk.c similarity index 52% rename from demod_psk.c rename to src/demod_psk.c index ee660ef..f01ee21 100644 --- a/demod_psk.c +++ b/src/demod_psk.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. -// -// Copyright (C) 2016 John Langner, WB2OSZ +// +// Copyright (C) 2016, 2019 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,35 +20,17 @@ //#define DEBUG1 1 /* display debugging info */ -//#define DEBUG3 1 /* print carrier detect changes. */ - -//#define DEBUG4 1 /* capture PSK demodulator output to log files */ - - /*------------------------------------------------------------------ * * Module: demod_psk.c * - * Purpose: Demodulator for Phase Shift Keying (PSK). + * Purpose: Demodulator for 2400 and 4800 bits per second Phase Shift Keying (PSK). * - * This is my initial attempt at implementing a 2400 bps mode. - * The MFJ-2400 & AEA PK232-2400 used V.26 / Bell 201 so I will follow that precedent. - * - * * Input: Audio samples from either a file or the "sound card." * * Outputs: Calls hdlc_rec_bit() for each bit demodulated. * - * Current Status: New for Version 1.4. - * - * Don't know if this is correct and/or compatible with - * other implementations. - * There is a lot of stuff going on here with phase - * shifting, gray code, bit order for the dibit, NRZI and - * bit-stuffing for HDLC. Plenty of opportunity for - * misinterpreting a protocol spec or just stupid mistakes. - * * References: MFJ-2400 Product description and manual: * * http://www.mfjenterprises.com/Product.php?productid=MFJ-2400 @@ -63,8 +45,7 @@ * http://www.brazoriacountyares.org/winlink-collection/TNC%20manuals/Kantronics/2400_modem_operators_guide@rgf.pdf * * - * The MFJ and AEA both use the EXAR XR-2123 PSK modem chip. - * The Kantronics has a P423 ??? + * From what I'm able to gather, they all used the EXAR XR-2123 PSK modem chip. * * Can't find the chip specs on the EXAR website so Google it. * @@ -79,19 +60,19 @@ * "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 other references to an alternative B which uses other multiples of 45. - * The XR-2123 data sheet mentions only multiples of 90. That's what I went with. - * - * The XR-2123 does not perform the scrambling as specified in V.26 so I wonder if - * the vendors implemented it in software or just left it out. - * I left out scrambling for now. Eventually, I'd like to get my hands on an old - * 2400 bps TNC for compatibility testing. + * There are ealier 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. * * https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-V.27bis-198811-I!!PDF-E&type=items * https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-V.27ter-198811-I!!PDF-E&type=items * + * Compatibility: + * V.26 has two variations, A and B. Initially I implemented the A alternative. + * It later turned out that the MFJ-2400 used the B alternative. In version 1.6 you have a + * choice between compatibility with MFJ (and probably the others) or the original implementation. + * *---------------------------------------------------------------*/ #include "direwolf.h" @@ -105,10 +86,15 @@ #include #include +// Fine tuning for different demodulator types. + +#define DCD_THRESH_ON 30 // Hysteresis: Can miss 2 out of 32 for detecting lock. +#define DCD_THRESH_OFF 6 // Might want a little more fine tuning. +#define DCD_GOOD_WIDTH 512 +#include "fsk_demod_state.h" // Values above override defaults. #include "audio.h" #include "tune.h" -#include "fsk_demod_state.h" #include "fsk_gen_filter.h" #include "hdlc_rec.h" #include "textcolor.h" @@ -116,6 +102,15 @@ #include "dsp.h" + + + +static const int phase_to_gray_v26[4] = {0, 1, 3, 2}; +static const int phase_to_gray_v27[8] = {1, 0, 2, 3, 7, 6, 4, 5}; + + +static int phase_shift_to_symbol (float phase_shift, int bits_per_symbol, int *bit_quality); + /* Add sample to buffer and shift the rest down. */ __attribute__((hot)) __attribute__((always_inline)) @@ -134,6 +129,8 @@ static inline float convolve (const float *__restrict__ data, const float *__res float sum = 0.0; int j; +//Does pragma make any difference? Annoying warning on Mac. +//#pragma GCC ivdep for (j=0; jmodem_type = modem_type; - D->num_slicers = 1; // Haven't thought about this yet. Is it even applicable? - + D->u.psk.v26_alt = v26_alt; + D->num_slicers = 1; // Haven't thought about this yet. Is it even applicable? #ifdef TUNE_PROFILE @@ -210,10 +208,9 @@ void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char if (modem_type == MODEM_QPSK) { + assert (D->u.psk.v26_alt != V26_UNSPECIFIED); + correct_baud = bps / 2; - // Originally I thought of scaling it to the data rate, - // e.g. 2400 bps -> 1800 Hz, but decided to make it a - // constant since it is the same for V.26 and V.27. carrier_freq = 1800; #if DEBUG1 @@ -225,11 +222,11 @@ void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char case 'P': /* Self correlation technique. */ - D->use_prefilter = 0; /* No bandpass filter. */ + D->u.psk.use_prefilter = 0; /* No bandpass filter. */ - D->lpf_baud = 0.60; - D->lp_filter_len_bits = 39. * 1200. / 44100.; - D->lp_window = BP_WINDOW_COSINE; + D->u.psk.lpf_baud = 0.60; + D->u.psk.lp_filter_width_sym = 1.061; // 39. * 1200. / 44100.; + D->u.psk.lp_window = BP_WINDOW_COSINE; D->pll_locked_inertia = 0.95; D->pll_searching_inertia = 0.50; @@ -238,14 +235,14 @@ void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char case 'Q': /* Self correlation technique. */ - D->use_prefilter = 1; /* Add a bandpass filter. */ - D->prefilter_baud = 1.3; - D->pre_filter_len_bits = 55. * 1200. / 44100.; - D->pre_window = BP_WINDOW_COSINE; + D->u.psk.use_prefilter = 1; /* Add a bandpass filter. */ + D->u.psk.prefilter_baud = 1.3; + D->u.psk.pre_filter_width_sym = 1.497; // 55. * 1200. / 44100.; + D->u.psk.pre_window = BP_WINDOW_COSINE; - D->lpf_baud = 0.60; - D->lp_filter_len_bits = 39. * 1200. / 44100.; - D->lp_window = BP_WINDOW_COSINE; + D->u.psk.lpf_baud = 0.60; + D->u.psk.lp_filter_width_sym = 1.061; // 39. * 1200. / 44100.; + D->u.psk.lp_window = BP_WINDOW_COSINE; D->pll_locked_inertia = 0.87; D->pll_searching_inertia = 0.50; @@ -259,13 +256,13 @@ void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char case 'R': /* Mix with local oscillator. */ - D->psk_use_lo = 1; + D->u.psk.psk_use_lo = 1; - D->use_prefilter = 0; /* No bandpass filter. */ + D->u.psk.use_prefilter = 0; /* No bandpass filter. */ - D->lpf_baud = 0.70; - D->lp_filter_len_bits = 37. * 1200. / 44100.; - D->lp_window = BP_WINDOW_TRUNCATED; + D->u.psk.lpf_baud = 0.70; + D->u.psk.lp_filter_width_sym = 1.007; // 37. * 1200. / 44100.; + D->u.psk.lp_window = BP_WINDOW_TRUNCATED; D->pll_locked_inertia = 0.925; D->pll_searching_inertia = 0.50; @@ -274,16 +271,16 @@ void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char case 'S': /* Mix with local oscillator. */ - D->psk_use_lo = 1; + D->u.psk.psk_use_lo = 1; - D->use_prefilter = 1; /* Add a bandpass filter. */ - D->prefilter_baud = 0.55; - D->pre_filter_len_bits = 74. * 1200. / 44100.; - D->pre_window = BP_WINDOW_FLATTOP; + D->u.psk.use_prefilter = 1; /* Add a bandpass filter. */ + D->u.psk.prefilter_baud = 0.55; + D->u.psk.pre_filter_width_sym = 2.014; // 74. * 1200. / 44100.; + D->u.psk.pre_window = BP_WINDOW_FLATTOP; - D->lpf_baud = 0.60; - D->lp_filter_len_bits = 39. * 1200. / 44100.; - D->lp_window = BP_WINDOW_COSINE; + D->u.psk.lpf_baud = 0.60; + D->u.psk.lp_filter_width_sym = 1.061; // 39. * 1200. / 44100.; + D->u.psk.lp_window = BP_WINDOW_COSINE; D->pll_locked_inertia = 0.925; D->pll_searching_inertia = 0.50; @@ -291,11 +288,11 @@ void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char break; } - D->ms_filter_len_bits = 1.25; // Delay line > 13/12 * symbol period + D->u.psk.delay_line_width_sym = 1.25; // Delay line > 13/12 * symbol period - D->coffs = (int) round( (11.f / 12.f) * (float)samples_per_sec / (float)correct_baud ); - D->boffs = (int) round( (float)samples_per_sec / (float)correct_baud ); - D->soffs = (int) round( (13.f / 12.f) * (float)samples_per_sec / (float)correct_baud ); + D->u.psk.coffs = (int) round( (11.f / 12.f) * (float)samples_per_sec / (float)correct_baud ); + D->u.psk.boffs = (int) round( (float)samples_per_sec / (float)correct_baud ); + D->u.psk.soffs = (int) round( (13.f / 12.f) * (float)samples_per_sec / (float)correct_baud ); } else { @@ -312,11 +309,11 @@ void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char case 'T': /* Self correlation technique. */ - D->use_prefilter = 0; /* No bandpass filter. */ + D->u.psk.use_prefilter = 0; /* No bandpass filter. */ - D->lpf_baud = 1.15; - D->lp_filter_len_bits = 32. * 1200. / 44100.; - D->lp_window = BP_WINDOW_COSINE; + D->u.psk.lpf_baud = 1.15; + D->u.psk.lp_filter_width_sym = 0.871; // 32. * 1200. / 44100.; + D->u.psk.lp_window = BP_WINDOW_COSINE; D->pll_locked_inertia = 0.95; D->pll_searching_inertia = 0.50; @@ -325,14 +322,14 @@ void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char case 'U': /* Self correlation technique. */ - D->use_prefilter = 1; /* Add a bandpass filter. */ - D->prefilter_baud = 0.9; - D->pre_filter_len_bits = 21. * 1200. / 44100.; - D->pre_window = BP_WINDOW_FLATTOP; + D->u.psk.use_prefilter = 1; /* Add a bandpass filter. */ + D->u.psk.prefilter_baud = 0.9; + D->u.psk.pre_filter_width_sym = 0.571; // 21. * 1200. / 44100.; + D->u.psk.pre_window = BP_WINDOW_FLATTOP; - D->lpf_baud = 1.15; - D->lp_filter_len_bits = 32. * 1200. / 44100.; - D->lp_window = BP_WINDOW_COSINE; + D->u.psk.lpf_baud = 1.15; + D->u.psk.lp_filter_width_sym = 0.871; // 32. * 1200. / 44100.; + D->u.psk.lp_window = BP_WINDOW_COSINE; D->pll_locked_inertia = 0.87; D->pll_searching_inertia = 0.50; @@ -346,13 +343,13 @@ void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char case 'V': /* Mix with local oscillator. */ - D->psk_use_lo = 1; + D->u.psk.psk_use_lo = 1; - D->use_prefilter = 0; /* No bandpass filter. */ + D->u.psk.use_prefilter = 0; /* No bandpass filter. */ - D->lpf_baud = 0.85; - D->lp_filter_len_bits = 31. * 1200. / 44100.; - D->lp_window = BP_WINDOW_COSINE; + D->u.psk.lpf_baud = 0.85; + D->u.psk.lp_filter_width_sym = 0.844; // 31. * 1200. / 44100.; + D->u.psk.lp_window = BP_WINDOW_COSINE; D->pll_locked_inertia = 0.925; D->pll_searching_inertia = 0.50; @@ -361,16 +358,16 @@ void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char case 'W': /* Mix with local oscillator. */ - D->psk_use_lo = 1; + D->u.psk.psk_use_lo = 1; - D->use_prefilter = 1; /* Add a bandpass filter. */ - D->prefilter_baud = 0.85; - D->pre_filter_len_bits = 31. * 1200. / 44100.; - D->pre_window = BP_WINDOW_COSINE; + D->u.psk.use_prefilter = 1; /* Add a bandpass filter. */ + D->u.psk.prefilter_baud = 0.85; + D->u.psk.pre_filter_width_sym = 0.844; // 31. * 1200. / 44100.; + D->u.psk.pre_window = BP_WINDOW_COSINE; - D->lpf_baud = 0.85; - D->lp_filter_len_bits = 31. * 1200. / 44100.; - D->lp_window = BP_WINDOW_COSINE; + D->u.psk.lpf_baud = 0.85; + D->u.psk.lp_filter_width_sym = 0.844; // 31. * 1200. / 44100.; + D->u.psk.lp_window = BP_WINDOW_COSINE; D->pll_locked_inertia = 0.925; D->pll_searching_inertia = 0.50; @@ -378,42 +375,36 @@ void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char break; } - D->ms_filter_len_bits = 1.25; // Delay line > 10/9 * symbol period + D->u.psk.delay_line_width_sym = 1.25; // Delay line > 10/9 * symbol period - D->coffs = (int) round( (8.f / 9.f) * (float)samples_per_sec / (float)correct_baud ); - D->boffs = (int) round( (float)samples_per_sec / (float)correct_baud ); - D->soffs = (int) round( (10.f / 9.f) * (float)samples_per_sec / (float)correct_baud ); + D->u.psk.coffs = (int) round( (8.f / 9.f) * (float)samples_per_sec / (float)correct_baud ); + D->u.psk.boffs = (int) round( (float)samples_per_sec / (float)correct_baud ); + D->u.psk.soffs = (int) round( (10.f / 9.f) * (float)samples_per_sec / (float)correct_baud ); } - if (D->psk_use_lo) { - D->lo_step = (int) round( 256. * 256. * 256. * 256. * carrier_freq / (double)samples_per_sec); + if (D->u.psk.psk_use_lo) { + D->u.psk.lo_step = (int) round( 256. * 256. * 256. * 256. * carrier_freq / (double)samples_per_sec); + +// Our own sin table for speed later. - assert (MAX_FILTER_SIZE >= 256); for (j = 0; j < 256; j++) { - D->m_sin_table[j] = sinf(2.f * (float)M_PI * j / 256.f); + D->u.psk.sin_table256[j] = sinf(2.f * (float)M_PI * j / 256.f); } } #ifdef TUNE_PRE_BAUD - D->prefilter_baud = TUNE_PRE_BAUD; + D->u.psk.prefilter_baud = TUNE_PRE_BAUD; #endif #ifdef TUNE_PRE_WINDOW - D->pre_window = TUNE_PRE_WINDOW; + D->u.psk.pre_window = TUNE_PRE_WINDOW; #endif - - #ifdef TUNE_LPF_BAUD - D->lpf_baud = TUNE_LPF_BAUD; + D->u.psk.lpf_baud = TUNE_LPF_BAUD; #endif #ifdef TUNE_LP_WINDOW - D->lp_window = TUNE_LP_WINDOW; -#endif - - -#ifdef TUNE_HYST - D->hysteresis = TUNE_HYST; + D->u.psk.lp_window = TUNE_LP_WINDOW; #endif #if defined(TUNE_PLL_SEARCHING) @@ -435,44 +426,41 @@ void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char * Convert number of symbol times to number of taps. */ - D->pre_filter_size = (int) round( D->pre_filter_len_bits * (float)samples_per_sec / (float)correct_baud ); - D->ms_filter_size = (int) round( D->ms_filter_len_bits * (float)samples_per_sec / (float)correct_baud ); - D->lp_filter_size = (int) round( D->lp_filter_len_bits * (float)samples_per_sec / (float)correct_baud ); + D->u.psk.pre_filter_taps = (int) round( D->u.psk.pre_filter_width_sym * (float)samples_per_sec / (float)correct_baud ); + D->u.psk.delay_line_taps = (int) round( D->u.psk.delay_line_width_sym * (float)samples_per_sec / (float)correct_baud ); + D->u.psk.lp_filter_taps = (int) round( D->u.psk.lp_filter_width_sym * (float)samples_per_sec / (float)correct_baud ); -#ifdef TUNE_PRE_FILTER_SIZE - D->pre_filter_size = TUNE_PRE_FILTER_SIZE; +#ifdef TUNE_PRE_FILTER_TAPS + D->u.psk.pre_filter_taps = TUNE_PRE_FILTER_TAPS; #endif -#ifdef TUNE_LP_FILTER_SIZE - D->lp_filter_size = TUNE_LP_FILTER_SIZE; +#ifdef TUNE_lp_filter_taps + D->u.psk.lp_filter_taps = TUNE_lp_filter_taps; #endif - if (D->pre_filter_size > MAX_FILTER_SIZE) - { + if (D->u.psk.pre_filter_taps > 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 ("Calculated pre filter size of %d is too large.\n", D->u.psk.pre_filter_taps); 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) - { + if (D->u.psk.delay_line_taps > 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 ("Calculated delay line size of %d is too large.\n", D->u.psk.delay_line_taps); 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) - { + if (D->u.psk.lp_filter_taps > 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 ("Calculated low pass filter size of %d is too large.\n", D->u.psk.lp_filter_taps); 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); @@ -482,14 +470,17 @@ void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char /* * Optionally apply a bandpass ("pre") filter to attenuate * frequencies outside the range of interest. + * It's a tradeoff. Attenuate frequencies outside the the range of interest + * but also distort the signal. This demodulator is not compuationally + * intensive so we can usually run both in parallel. */ - if (D->use_prefilter) { + if (D->u.psk.use_prefilter) { float f1, f2; - f1 = carrier_freq - D->prefilter_baud * correct_baud; - f2 = carrier_freq + D->prefilter_baud * correct_baud; -#if 0 + f1 = carrier_freq - D->u.psk.prefilter_baud * correct_baud; + f2 = carrier_freq + D->u.psk.prefilter_baud * correct_baud; +#if DEBUG1 text_color_set(DW_COLOR_DEBUG); dw_printf ("Generating prefilter %.0f to %.0f Hz.\n", (double)f1, (double)f2); #endif @@ -502,15 +493,15 @@ void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char 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->u.psk.pre_filter, D->u.psk.pre_filter_taps, D->u.psk.pre_window); } /* * Now the lowpass filter. */ - float fc = correct_baud * D->lpf_baud / (float)samples_per_sec; - gen_lowpass (fc, D->lp_filter, D->lp_filter_size, D->lp_window); + 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); /* * No point in having multiple numbers for signal level. @@ -519,10 +510,98 @@ void demod_psk_init (enum modem_t modem_type, int samples_per_sec, int bps, char D->alevel_mark_peak = -1; D->alevel_space_peak = -1; +#if 0 + // QPSK - CSV format to make plot. + + printf ("Phase shift degrees, bit 0, quality 0, bit 1, quality 1\n"); + for (int degrees = 0; degrees <= 360; degrees++) { + float a = degrees * M_PI * 2./ 360.; + int bit_quality[3]; + + int new_gray = phase_shift_to_symbol (a, 2, bit_quality); + + float offset = 3 * 1.5; + printf ("%d, ", degrees); + printf ("%.3f, ", offset + (new_gray & 1)); offset -= 1.5; + printf ("%.3f, ", offset + (bit_quality[0] / 100.)); offset -= 1.5; + printf ("%.3f, ", offset + ((new_gray >> 1) & 1)); offset -= 1.5; + printf ("%.3f\n", offset + (bit_quality[1] / 100.)); + } +#endif + +#if 0 + // 8-PSK - CSV format to make plot. + + printf ("Phase shift degrees, bit 0, quality 0, bit 1, quality 1, bit 2, quality 2\n"); + for (int degrees = 0; degrees <= 360; degrees++) { + float a = degrees * M_PI * 2./ 360.; + int bit_quality[3]; + + int new_gray = phase_shift_to_symbol (a, 3, bit_quality); + + float offset = 5 * 1.5; + printf ("%d, ", degrees); + printf ("%.3f, ", offset + (new_gray & 1)); offset -= 1.5; + printf ("%.3f, ", offset + (bit_quality[0] / 100.)); offset -= 1.5; + printf ("%.3f, ", offset + ((new_gray >> 1) & 1)); offset -= 1.5; + printf ("%.3f, ", offset + (bit_quality[1] / 100.)); offset -= 1.5; + printf ("%.3f, ", offset + ((new_gray >> 2) & 1)); offset -= 1.5; + printf ("%.3f\n", offset + (bit_quality[2] / 100.)); + } +#endif } /* demod_psk_init */ +/*------------------------------------------------------------------- + * + * Name: phase_shift_to_symbol + * + * Purpose: Translate phase shift, between two symbols, into 2 or 3 bits. + * + * Inputs: phase_shift - in radians. + * + * bits_per_symbol - 2 for QPSK, 3 for 8PSK. + * + * Outputs: bit_quality[] - Value of 0 (at threshold) to 100 (perfect) for each bit. + * + * Returns: 2 or 3 bit symbol value in Gray code. + * + *--------------------------------------------------------------------*/ + +__attribute__((hot)) __attribute__((always_inline)) +static inline int phase_shift_to_symbol (float phase_shift, int bits_per_symbol, int * __restrict__ bit_quality) +{ +// Number of different symbol states. + assert (bits_per_symbol == 2 || bits_per_symbol == 3); + int N = 1 << bits_per_symbol; + assert (N == 4 || N == 8); + +// Scale angle to 1 per symbol then separate into integer and fractional parts. + float a = phase_shift * (float)N / (M_PI * 2.0f); + while (a >= (float)N) a -= (float)N; + while (a < 0.) a += (float)N; + int i = (int)a; + if (i == N) i = N-1; // Should be < N. Watch out for possible roundoff errors. + float f = a - (float)i; + assert (i >= 0 && i < N); + assert (f >= -0.001f && f <= 1.001f); + +// Interpolate between the ideal angles to get a level of certainty. + int result = 0; + for (int b = 0; b < bits_per_symbol; b++) { + float demod = bits_per_symbol == 2 ? + ((phase_to_gray_v26[i] >> b) & 1) * (1.0f - f) + ((phase_to_gray_v26[(i+1)&3] >> b) & 1) * f : + ((phase_to_gray_v27[i] >> b) & 1) * (1.0f - f) + ((phase_to_gray_v27[(i+1)&7] >> b) & 1) * f; +// Slice to get boolean value and quality measurement. + if (demod >= 0.5f) result |= 1<= 0 && chan < MAX_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); - /* Scale to nice number for plotting during debug. */ - fsam = sam / 16384.0f; - + float fsam = sam / 16384.0f; /* * Optional bandpass filter before the phase detector. */ - if (D->use_prefilter) { - push_sample (fsam, D->raw_cb, D->pre_filter_size); - fsam = convolve (D->raw_cb, D->pre_filter, D->pre_filter_size); + if (D->u.psk.use_prefilter) { + push_sample (fsam, D->u.psk.audio_in, D->u.psk.pre_filter_taps); + fsam = convolve (D->u.psk.audio_in, D->u.psk.pre_filter, D->u.psk.pre_filter_taps); } - if (D->psk_use_lo) { - float a, delta; - int id; + if (D->u.psk.psk_use_lo) { /* * Mix with local oscillator to obtain phase. * The absolute phase doesn't matter. * We are just concerned with the change since the previous symbol. */ - sam_x_cos = fsam * D->m_sin_table[((D->lo_phase >> 24) + 64) & 0xff]; + float sam_x_cos = fsam * D->u.psk.sin_table256[((D->u.psk.lo_phase >> 24) + 64) & 0xff]; + push_sample (sam_x_cos, D->u.psk.I_raw, D->u.psk.lp_filter_taps); + float I = convolve (D->u.psk.I_raw, D->u.psk.lp_filter, D->u.psk.lp_filter_taps); - sam_x_sin = fsam * D->m_sin_table[(D->lo_phase >> 24) & 0xff]; + float sam_x_sin = fsam * D->u.psk.sin_table256[(D->u.psk.lo_phase >> 24) & 0xff]; + push_sample (sam_x_sin, D->u.psk.Q_raw, D->u.psk.lp_filter_taps); + float Q = convolve (D->u.psk.Q_raw, D->u.psk.lp_filter, D->u.psk.lp_filter_taps); - push_sample (sam_x_cos, D->m_amp_cb, D->lp_filter_size); - I = convolve (D->m_amp_cb, D->lp_filter, D->lp_filter_size); + float a = my_atan2f(I,Q); - push_sample (sam_x_sin, D->s_amp_cb, D->lp_filter_size); - Q = convolve (D->s_amp_cb, D->lp_filter, D->lp_filter_size); + // This is just a delay line of one symbol time. - a = my_atan2f(I,Q); - push_sample (a, D->ms_in_cb, D->ms_filter_size); + push_sample (a, D->u.psk.delay_line, D->u.psk.delay_line_taps); + float delta = a - D->u.psk.delay_line[D->u.psk.boffs]; - delta = a - D->ms_in_cb[D->boffs]; - - /* 256 units/cycle makes modulo processing easier. */ - /* Make sure it is positive before truncating to integer. */ - - id = ((int)((delta / (2.f * (float)M_PI) + 1.f) * 256.f)) & 0xff; - + int gray; + int bit_quality[3]; if (D->modem_type == MODEM_QPSK) { - demod_phase_shift = ((id + 32) >> 6) & 0x3; + if (D->u.psk.v26_alt == V26_B) { + gray = phase_shift_to_symbol (delta + (float)(-M_PI/4), 2, bit_quality);; // MFJ compatible + } + else { + gray = phase_shift_to_symbol (delta, 2, bit_quality); // Classic + } } else { - demod_phase_shift = ((id + 16) >> 5) & 0x7; + gray = phase_shift_to_symbol (delta, 3, bit_quality);; // 8-PSK } - nudge_pll (chan, subchan, slice, demod_phase_shift, D); + nudge_pll (chan, subchan, slice, gray, D, bit_quality); - D->lo_phase += D->lo_step; + D->u.psk.lo_phase += D->u.psk.lo_step; } else { /* * Correlate with previous symbol. We are looking for the phase shift. */ - push_sample (fsam, D->ms_in_cb, D->ms_filter_size); + push_sample (fsam, D->u.psk.delay_line, D->u.psk.delay_line_taps); - sam_x_cos = fsam * D->ms_in_cb[D->coffs]; - sam_x_sin = fsam * D->ms_in_cb[D->soffs]; + float sam_x_cos = fsam * D->u.psk.delay_line[D->u.psk.coffs]; + push_sample (sam_x_cos, D->u.psk.I_raw, D->u.psk.lp_filter_taps); + float I = convolve (D->u.psk.I_raw, D->u.psk.lp_filter, D->u.psk.lp_filter_taps); - push_sample (sam_x_cos, D->m_amp_cb, D->lp_filter_size); - I = convolve (D->m_amp_cb, D->lp_filter, D->lp_filter_size); + float sam_x_sin = fsam * D->u.psk.delay_line[D->u.psk.soffs]; + push_sample (sam_x_sin, D->u.psk.Q_raw, D->u.psk.lp_filter_taps); + float Q = convolve (D->u.psk.Q_raw, D->u.psk.lp_filter, D->u.psk.lp_filter_taps); - push_sample (sam_x_sin, D->s_amp_cb, D->lp_filter_size); - Q = convolve (D->s_amp_cb, D->lp_filter, D->lp_filter_size); + int gray; + int bit_quality[3]; + float delta = my_atan2f(I,Q); if (D->modem_type == MODEM_QPSK) { - -#if 1 // Speed up special case. - if (I > 0) { - if (Q > 0) - demod_phase_shift = 0; /* 0 to 90 degrees, etc. */ - else - demod_phase_shift = 1; + if (D->u.psk.v26_alt == V26_B) { + gray = phase_shift_to_symbol (delta + (float)(M_PI/2), 2, bit_quality); // MFJ compatible } else { - if (Q > 0) - demod_phase_shift = 3; - else - demod_phase_shift = 2; + gray = phase_shift_to_symbol (delta + (float)(3*M_PI/4), 2, bit_quality); // Classic } -#else - a = my_atan2f(I,Q); - int id = ((int)((a / (2.f * (float)M_PI) + 1.f) * 256.f)) & 0xff; - // 128 compensates for 180 degree phase shift due - // to 1 1/2 carrier cycles per symbol period. - demod_phase_shift = ((id + 128) >> 6) & 0x3; -#endif } else { - float a; - int idelta; - - a = my_atan2f(I,Q); - idelta = ((int)((a / (2.f * (float)M_PI) + 1.f) * 256.f)) & 0xff; - // 32 (90 degrees) compensates for 1800 carrier vs. 1800 baud. - // 16 is to set threshold between constellation points. - demod_phase_shift = ((idelta - 32 - 16) >> 5) & 0x7; + gray = phase_shift_to_symbol (delta + (float)(3*M_PI/2), 3, bit_quality); } - - nudge_pll (chan, subchan, slice, demod_phase_shift, D); + nudge_pll (chan, subchan, slice, gray, D, bit_quality); } -#if DEBUG4 - - if (chan == 0) { - - if (1) { - //if (hdlc_rec_gathering (chan, subchan, slice)) { - char fname[30]; - - - if (demod_log_fp == NULL) { - log_file_seq++; - snprintf (fname, sizeof(fname), "demod/%04d.csv", log_file_seq); - //if (log_file_seq == 1) mkdir ("demod", 0777); - if (log_file_seq == 1) mkdir ("demod"); - - demod_log_fp = fopen (fname, "w"); - text_color_set(DW_COLOR_DEBUG); - dw_printf ("Starting demodulator log file %s\n", fname); - fprintf (demod_log_fp, "Audio, sin, cos, *cos, *sin, I, Q, phase, Clock\n"); - } - - fprintf (demod_log_fp, "%.3f, %.3f, %.3f, %.3f, %.3f, %.3f, %.2f, %.2f, %.2f\n", - fsam + 2, - - D->ms_in_cb[D->soffs] + 6, - - D->ms_in_cb[D->coffs] + 6, - sam_x_cos + 8, - sam_x_sin + 10, - 2 * I + 12, - 2 * Q + 12, - demod_phase_shift * 2. / 3. + 14., - (D->slicer[slice].data_clock_pll & 0x80000000) ? .5 : .0); - - fflush (demod_log_fp); - } - else { - if (demod_log_fp != NULL) { - fclose (demod_log_fp); - demod_log_fp = NULL; - } - } - } -#endif - - } /* end demod_psk_process_sample */ -static const int phase_to_gray_v26[4] = {0, 1, 3, 2}; -static const int phase_to_gray_v27[8] = {1, 0, 2, 3, 7, 6, 4, 5}; - __attribute__((hot)) -inline static void nudge_pll (int chan, int subchan, int slice, int demod_bits, struct demodulator_state_s *D) +static void nudge_pll (int chan, int subchan, int slice, int demod_bits, struct demodulator_state_s *D, int *bit_quality) { /* * Finally, a PLL is used to sample near the centers of the data bits. * - * D points to a demodulator for a channel/subchannel pair so we don't - * have to keep recalculating it. + * D points to a demodulator for a channel/subchannel pair. * * D->data_clock_pll is a SIGNED 32 bit variable. * When it overflows from a large positive value to a negative value, we @@ -786,12 +784,8 @@ inline static void nudge_pll (int chan, int subchan, int slice, int demod_bits, * Be a little more agressive about adjusting the PLL * phase when searching for a signal. * Don't change it as much when locked on to a signal. - * - * I don't think the optimal value will depend on the audio sample rate - * because this happens for each transition from the demodulator. */ - D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll; // Perform the add as unsigned to avoid signed overflow error. @@ -804,26 +798,19 @@ inline static void nudge_pll (int chan, int subchan, int slice, int demod_bits, if (D->modem_type == MODEM_QPSK) { - int gray = phase_to_gray_v26[ demod_bits ]; + int gray = demod_bits; -#if DEBUG4 - text_color_set(DW_COLOR_DEBUG); - - dw_printf ("a=%.2f deg, delta=%.2f deg, phaseshift=%d, bits= %d %d \n", - a * 360 / (2*M_PI), delta * 360 / (2*M_PI), demod_bits, (gray >> 1) & 1, gray & 1); - - //dw_printf ("phaseshift=%d, bits= %d %d \n", demod_bits, (gray >> 1) & 1, gray & 1); -#endif - hdlc_rec_bit (chan, subchan, slice, (gray >> 1) & 1, 0, -1); - hdlc_rec_bit (chan, subchan, slice, gray & 1, 0, -1); + hdlc_rec_bit (chan, subchan, slice, (gray >> 1) & 1, 0, bit_quality[1]); + hdlc_rec_bit (chan, subchan, slice, gray & 1, 0, bit_quality[0]); } else { - int gray = phase_to_gray_v27[ demod_bits ]; + int gray = demod_bits; - hdlc_rec_bit (chan, subchan, slice, (gray >> 2) & 1, 0, -1); - hdlc_rec_bit (chan, subchan, slice, (gray >> 1) & 1, 0, -1); - hdlc_rec_bit (chan, subchan, slice, gray & 1, 0, -1); + hdlc_rec_bit (chan, subchan, slice, (gray >> 2) & 1, 0, bit_quality[2]); + hdlc_rec_bit (chan, subchan, slice, (gray >> 1) & 1, 0, bit_quality[1]); + hdlc_rec_bit (chan, subchan, slice, gray & 1, 0, bit_quality[0]); } + pll_dcd_each_symbol2 (D, chan, subchan, slice); } /* @@ -837,7 +824,9 @@ inline static void nudge_pll (int chan, int subchan, int slice, int demod_bits, if (demod_bits != D->slicer[slice].prev_demod_data) { - if (hdlc_rec_gathering (chan, subchan, slice)) { + pll_dcd_signal_transition2 (D, slice, D->slicer[slice].data_clock_pll); + + if (D->slicer[slice].data_detect) { D->slicer[slice].data_clock_pll = (int)floorf((float)(D->slicer[slice].data_clock_pll) * D->pll_locked_inertia); } else { diff --git a/src/demod_psk.h b/src/demod_psk.h new file mode 100644 index 0000000..134b199 --- /dev/null +++ b/src/demod_psk.h @@ -0,0 +1,7 @@ + +/* demod_psk.h */ + + +void demod_psk_init (enum modem_t modem_type, enum v26_e v26_alt, int samples_per_sec, int bps, char profile, struct demodulator_state_s *D); + +void demod_psk_process_sample (int chan, int subchan, int sam, struct demodulator_state_s *D); diff --git a/digipeater.c b/src/digipeater.c similarity index 98% rename from digipeater.c rename to src/digipeater.c index 36970d7..1e4c814 100644 --- a/digipeater.c +++ b/src/digipeater.c @@ -62,7 +62,7 @@ #include #include /* for isdigit, isupper */ #include "regex.h" -#include +#include #include "ax25_pad.h" #include "digipeater.h" @@ -149,11 +149,15 @@ void digipeater (int from_chan, packet_t pp) // dw_printf ("digipeater()\n"); - assert (from_chan >= 0 && from_chan < MAX_CHANS); - if ( ! save_audio_config_p->achan[from_chan].valid) { + + // 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)) { text_color_set(DW_COLOR_ERROR); - dw_printf ("digipeater: Did not expect to receive on invalid channel %d.\n", from_chan); + dw_printf ("APRS digipeater: Did not expect to receive on invalid channel %d.\n", from_chan); } diff --git a/digipeater.h b/src/digipeater.h similarity index 100% rename from digipeater.h rename to src/digipeater.h diff --git a/direwolf.c b/src/direwolf.c similarity index 78% rename from direwolf.c rename to src/direwolf.c index f38863c..456b16f 100644 --- a/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 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019, 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 @@ -24,13 +24,15 @@ * * Purpose: Main program for "Dire Wolf" which includes: * - * AFSK modem using the "sound card." + * Various DSP modems using the "sound card." * AX.25 encoder/decoder. * APRS data encoder / decoder. * APRS digipeater. * KISS TNC emulator. * APRStt (touch tone input) gateway * Internet Gateway (IGate) + * Ham Radio of Things - IoT with Ham Radio + * FX.25 Forward Error Correction. * * *---------------------------------------------------------------*/ @@ -58,6 +60,8 @@ #endif #if __WIN32__ +#include +#include #else #include #include @@ -90,6 +94,7 @@ #include "ax25_pad.h" #include "xid.h" #include "decode_aprs.h" +#include "encode_aprs.h" #include "textcolor.h" #include "server.h" #include "kiss.h" @@ -118,6 +123,8 @@ #include "mheard.h" #include "ax25_link.h" #include "dtime_now.h" +#include "fx25.h" +#include "dwsock.h" //static int idx_decoded = 0; @@ -177,6 +184,7 @@ static int d_p_opt = 0; /* "-d p" option for dumping packets over radio. */ static int q_h_opt = 0; /* "-q h" Quiet, suppress the "heard" line with audio level. */ static int q_d_opt = 0; /* "-q d" Quiet, suppress the printing of decoded of APRS packets. */ +static int A_opt_ais_to_obj = 0; /* "-A" Convert received AIS to APRS "Object Report." */ int main (int argc, char *argv[]) @@ -190,7 +198,7 @@ int main (int argc, char *argv[]) struct digi_config_s digi_config; struct cdigi_config_s cdigi_config; struct igate_config_s igate_config; - int r_opt = 0, n_opt = 0, b_opt = 0, B_opt = 0, D_opt = 0; /* Command line options. */ + int r_opt = 0, n_opt = 0, b_opt = 0, B_opt = 0, D_opt = 0, U_opt = 0; /* Command line options. */ char P_opt[16]; char l_opt_logdir[80]; char L_opt_logfile[80]; @@ -199,6 +207,9 @@ int main (int argc, char *argv[]) int t_opt = 1; /* Text color option. */ int a_opt = 0; /* "-a n" interval, in seconds, for audio statistics report. 0 for none. */ + int g_opt = 0; /* G3RUH mode, ignoring default for speed. */ + int j_opt = 0; /* 2400 bps PSK compatible with direwolf <= 1.5 */ + int J_opt = 0; /* 2400 bps PSK compatible MFJ-2400 and maybe others. */ int d_k_opt = 0; /* "-d k" option for serial port KISS. Can be repeated for more detail. */ int d_n_opt = 0; /* "-d n" option for Network KISS. Can be repeated for more detail. */ @@ -211,9 +222,14 @@ int main (int argc, char *argv[]) #if USE_HAMLIB 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 E_tx_opt = 0; /* "-E n" Error rate % for clobbering trasmit 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. */ + strlcpy(l_opt_logdir, "", sizeof(l_opt_logdir)); strlcpy(L_opt_logfile, "", sizeof(L_opt_logfile)); strlcpy(P_opt, "", sizeof(P_opt)); @@ -244,9 +260,18 @@ int main (int argc, char *argv[]) /* * Pre-scan the command line options for the text color option. * We need to set this before any text output. + * Default will be no colors if stdout is not a terminal (i.e. piped into + * something else such as "tee") but command line can override this. */ - t_opt = 1; /* 1 = normal, 0 = no text colors. */ +#if __WIN32__ + t_opt = _isatty(_fileno(stdout)) > 0; +#else + t_opt = isatty(fileno(stdout)); +#endif + /* 1 = normal, 0 = no text colors. */ + /* 2, 3, ... alternate escape sequences for different terminals. */ + for (j=1; j= 1) { __cpuid (cpuinfo, 1); //dw_printf ("debug: cpuinfo = %x, %x, %x, %x\n", cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]); + // https://en.wikipedia.org/wiki/CPUID if ( ! ( cpuinfo[3] & (1 << 25))) { text_color_set(DW_COLOR_ERROR); dw_printf ("------------------------------------------------------------------\n"); dw_printf ("This version requires a minimum of a Pentium 3 or equivalent.\n"); dw_printf ("If you are seeing this message, you are probably using a computer\n"); - dw_printf ("from the previous century. See comments in Makefile.win for\n"); - dw_printf ("information on how you can recompile it for use with your antique.\n"); + dw_printf ("from the previous Century. See instructions in User Guide for\n"); + dw_printf ("information on how you can compile it for use with your antique.\n"); dw_printf ("------------------------------------------------------------------\n"); } } @@ -352,7 +387,7 @@ int main (int argc, char *argv[]) /* ':' following option character means arg is required. */ - c = getopt_long(argc, argv, "P:B:D:c:pxr:b:n:d:q:t:Ul:L:Sa:E:T:", + c = getopt_long(argc, argv, "hP:B:gjJD:U:c:pxr:b:n:d:q:t:ul:L:Sa:E:T:e:X:A", long_options, &option_index); if (c == -1) break; @@ -395,8 +430,17 @@ int main (int argc, char *argv[]) #endif case 'B': /* -B baud rate and modem properties. */ - - B_opt = atoi(optarg); + /* Also implies modem type based on speed. */ + /* Special case "AIS" rather than number. */ + if (strcasecmp(optarg, "AIS") == 0) { + B_opt = 12345; // See special case below. + } + else if (strcasecmp(optarg, "EAS") == 0) { + B_opt = 23456; // See special case below. + } + else { + B_opt = atoi(optarg); + } if (B_opt < MIN_BAUD || B_opt > MAX_BAUD) { text_color_set(DW_COLOR_ERROR); dw_printf ("Use a more reasonable data baud rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD); @@ -404,13 +448,28 @@ int main (int argc, char *argv[]) } break; + case 'g': /* -g G3RUH modem, overriding default mode for speed. */ + + g_opt = 1; + break; + + case 'j': /* -j V.26 compatible with earlier direwolf. */ + + j_opt = 1; + break; + + case 'J': /* -J V.26 compatible with MFJ-2400. */ + + J_opt = 1; + break; + case 'P': /* -P for modem profile. */ //debug: dw_printf ("Demodulator profile set to \"%s\"\n", optarg); strlcpy (P_opt, optarg, sizeof(P_opt)); break; - case 'D': /* -D decrease AFSK demodulator sample rate */ + case 'D': /* -D divide AFSK demodulator sample rate */ D_opt = atoi(optarg); if (D_opt < 1 || D_opt > 8) { @@ -420,6 +479,16 @@ int main (int argc, char *argv[]) } break; + case 'U': /* -U multiply G3RUH demodulator sample rate (upsample) */ + + U_opt = atoi(optarg); + if (U_opt < 1 || U_opt > 4) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Crazy value for -U. \n"); + exit (EXIT_FAILURE); + } + break; + case 'x': /* -x for transmit calibration tones. */ xmit_calibrate_option = 1; @@ -458,9 +527,10 @@ int main (int argc, char *argv[]) } break; + case 'h': // -h for help case '?': - /* Unknown option message was already printed. */ + /* For '?' unknown option message was already printed. */ usage (argv); break; @@ -495,6 +565,7 @@ int main (int argc, char *argv[]) #if USE_HAMLIB case 'h': d_h_opt++; break; // Hamlib verbose level. #endif + case 'x': d_x_opt++; break; // FX.25 default: break; } } @@ -509,6 +580,7 @@ int main (int argc, char *argv[]) switch (*p) { case 'h': q_h_opt = 1; break; case 'd': q_d_opt = 1; break; + case 'x': d_x_opt = 0; break; // Defaults to minimal info. This silences. default: break; } } @@ -518,7 +590,7 @@ int main (int argc, char *argv[]) break; - case 'U': /* Print UTF-8 test and exit. */ + case 'u': /* Print UTF-8 test and exit. */ dw_printf ("\n UTF-8 test string: ma%c%cana %c%c F%c%c%c%ce\n\n", 0xc3, 0xb1, @@ -571,6 +643,21 @@ int main (int argc, char *argv[]) strlcpy (T_opt_timestamp, optarg, sizeof(T_opt_timestamp)); break; + case 'e': /* -e Receive Bit Error Rate (BER). */ + + e_recv_ber = atof(optarg); + break; + + case 'X': + + X_fx25_xmit_enable = atoi(optarg); + break; + + case 'A': // -A convert AIS to APRS object + + A_opt_ais_to_obj = 1; + break; + default: /* Should not be here. */ @@ -605,20 +692,25 @@ int main (int argc, char *argv[]) symbols_init (); + (void)dwsock_init(); + config_init (config_file, &audio_config, &digi_config, &cdigi_config, &tt_config, &igate_config, &misc_config); if (r_opt != 0) { audio_config.adev[0].samples_per_sec = r_opt; } + if (n_opt != 0) { audio_config.adev[0].num_channels = n_opt; if (n_opt == 2) { - audio_config.achan[1].valid = 1; + audio_config.achan[1].medium = MEDIUM_RADIO; } } + if (b_opt != 0) { audio_config.adev[0].bits_per_sample = b_opt; } + if (B_opt != 0) { audio_config.achan[0].baud = B_opt; @@ -654,6 +746,20 @@ int main (int argc, char *argv[]) dw_printf ("Bit rate should be standard 4800 rather than specified %d.\n", audio_config.achan[0].baud); } } + else if (audio_config.achan[0].baud == 12345) { + audio_config.achan[0].modem_type = MODEM_AIS; + audio_config.achan[0].baud = 9600; + audio_config.achan[0].mark_freq = 0; + audio_config.achan[0].space_freq = 0; + } + else if (audio_config.achan[0].baud == 23456) { + audio_config.achan[0].modem_type = MODEM_EAS; + audio_config.achan[0].baud = 521; // Actually 520.83 but we have an integer field here. + // 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)); + } else { audio_config.achan[0].modem_type = MODEM_SCRAMBLE; audio_config.achan[0].mark_freq = 0; @@ -661,11 +767,44 @@ int main (int argc, char *argv[]) } } + if (g_opt) { + + // Force G3RUH mode, overriding default for speed. + // Example: -B 2400 -g + + audio_config.achan[0].modem_type = MODEM_SCRAMBLE; + audio_config.achan[0].mark_freq = 0; + audio_config.achan[0].space_freq = 0; + } + + if (j_opt) { + + // V.26 compatible with earlier versions of direwolf. + // Example: -B 2400 -j or simply -j + + audio_config.achan[0].v26_alternative = V26_A; + audio_config.achan[0].modem_type = MODEM_QPSK; + audio_config.achan[0].mark_freq = 0; + audio_config.achan[0].space_freq = 0; + audio_config.achan[0].baud = 2400; + } + if (J_opt) { + + // V.26 compatible with MFJ and maybe others. + // Example: -B 2400 -J or simply -J + + audio_config.achan[0].v26_alternative = V26_B; + audio_config.achan[0].modem_type = MODEM_QPSK; + audio_config.achan[0].mark_freq = 0; + audio_config.achan[0].space_freq = 0; + audio_config.achan[0].baud = 2400; + } + + audio_config.statistics_interval = a_opt; if (strlen(P_opt) > 0) { /* -P for modem profile. */ - /* TODO: Not yet documented. Should probably since it is consistent with atest. */ strlcpy (audio_config.achan[0].profiles, P_opt, sizeof(audio_config.achan[0].profiles)); } @@ -674,6 +813,13 @@ int main (int argc, char *argv[]) audio_config.achan[0].decimate = D_opt; } + if (U_opt != 0) { + // Increase G3RUH audio sampling rate to improve performance. + // The value is normally determined automatically based on audio + // sample rate and baud. This allows override for experimentation. + audio_config.achan[0].upsample = U_opt; + } + strlcpy(audio_config.timestamp_format, T_opt_timestamp, sizeof(audio_config.timestamp_format)); // temp - only xmit errors. @@ -705,6 +851,10 @@ int main (int argc, char *argv[]) } + audio_config.recv_ber = e_recv_ber; + + audio_config.fx25_xmit_enable = X_fx25_xmit_enable; + /* * Open the audio source @@ -727,6 +877,7 @@ int main (int argc, char *argv[]) * Initialize the demodulator(s) and HDLC decoder. */ multi_modem_init (&audio_config); + fx25_init (d_x_opt); /* * Initialize the touch tone decoder & APRStt gateway. @@ -857,7 +1008,7 @@ int main (int argc, char *argv[]) // TODO: Use only one printf per line so output doesn't get jumbled up with stuff from other threads. -void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum) +void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, int is_fx25, retry_t retries, char *spectrum) { char stemp[500]; @@ -874,7 +1025,12 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev assert (pp != NULL); // 1.1J+ strlcpy (display_retries, "", sizeof(display_retries)); - if (audio_config.achan[chan].fix_bits != RETRY_NONE || audio_config.achan[chan].passall) { + + if (is_fx25) { + ; + } + else if (audio_config.achan[chan].fix_bits != RETRY_NONE || audio_config.achan[chan].passall) { + assert (retries >= RETRY_NONE && retries <= RETRY_MAX); snprintf (display_retries, sizeof(display_retries), " [%s] ", retry_text[(int)retries]); } @@ -1081,6 +1237,8 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev * * Suppress printed decoding if "-q d" option used. */ + char ais_obj_packet[300]; + strcpy (ais_obj_packet, ""); if (ax25_is_aprs(pp)) { @@ -1115,6 +1273,37 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev mheard_save_rf (chan, &A, pp, alevel, retries); +// For AIS, we have an option to convert the NMEA format, in User Defined data, +// into an APRS "Object Report" and send that to the clients as well. + +// FIXME: partial implementation. + + static const char user_def_da[4] = { '{', USER_DEF_USER_ID, USER_DEF_TYPE_AIS, '\0' }; + + if (strncmp((char*)pinfo, user_def_da, 3) == 0) { + + waypoint_send_ais((char*)pinfo + 3); + + if (A_opt_ais_to_obj && A.g_lat != G_UNKNOWN && A.g_lon != G_UNKNOWN) { + + char ais_obj_info[256]; + (void)encode_object (A.g_name, 0, time(NULL), + A.g_lat, A.g_lon, 0, // no ambiguity + A.g_symbol_table, A.g_symbol_code, + 0, 0, 0, "", // power, height, gain, direction. + // Unknown not handled properly. + // Should encode_object take floating point here? + (int)(A.g_course+0.5), (int)(DW_MPH_TO_KNOTS(A.g_speed_mph)+0.5), + 0, 0, 0, A.g_comment, // freq, tone, offset + ais_obj_info, sizeof(ais_obj_info)); + + snprintf (ais_obj_packet, sizeof(ais_obj_packet), "%s>%s%1d%1d:%s", A.g_src, APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, ais_obj_info); + + dw_printf ("[%d.AIS] %s\n", chan, ais_obj_packet); + + // This will be sent to client apps after the User Defined Data representation. + } + } // Convert to NMEA waypoint sentence if we have a location. @@ -1141,13 +1330,28 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev 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 + if (A_opt_ais_to_obj && strlen(ais_obj_packet) != 0) { + packet_t ao_pp = ax25_from_text (ais_obj_packet, 1); + if (ao_pp != NULL) { + unsigned char ao_fbuf[AX25_MAX_PACKET_LEN]; + 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); + ax25_delete (ao_pp); + } + } + /* * If it came from DTMF decoder, send it to APRStt gateway. * Otherwise, it is a candidate for IGate and digipeater. * - * TODO: It might be useful to have some way to simulate touch tone - * sequences with BEACON sendto=R... for testing. + * TODO: It would be useful to have some way to simulate touch tone + * sequences with BEACON sendto=R0 for testing. */ + if (subchan == -1) { if (tt_config.gateway_enabled && info_len >= 2) { aprs_tt_sequence (chan, (char*)(pinfo+1)); @@ -1177,6 +1381,9 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev * Use only those with correct CRC; We don't want to spread corrupted data! */ +// TODO: Should also use anything received with FX.25 because it is known to be good. +// Our earlier "fix bits" hack could allow corrupted information to get thru. + if (ax25_is_aprs(pp) && retries == RETRY_NONE) { digipeater (chan, pp); @@ -1254,7 +1461,15 @@ static void usage (char **argv) dw_printf (" 2400 bps uses QPSK based on V.26 standard.\n"); dw_printf (" 4800 bps uses 8PSK based on V.27 standard.\n"); dw_printf (" 9600 bps and up uses K9NG/G3RUH standard.\n"); + dw_printf (" AIS for ship Automatic Identification System.\n"); + dw_printf (" EAS for Emergency Alert System (EAS) Specific Area Message Encoding (SAME).\n"); + dw_printf (" -g Force G3RUH modem regardless of speed.\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 (" -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 (" -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"); @@ -1271,19 +1486,23 @@ static void usage (char **argv) #if USE_HAMLIB dw_printf (" h h = hamlib increase verbose level.\n"); #endif + dw_printf (" x x = FX.25 increase verbose level.\n"); dw_printf (" -q Quiet (suppress output) options:\n"); dw_printf (" h h = Heard line with the audio level.\n"); dw_printf (" d d = Decoding of APRS packets.\n"); - dw_printf (" -t n Text colors. 1=normal, 0=disabled.\n"); + dw_printf (" x x = Silence FX.25 information.\n"); + dw_printf (" -t n Text colors. 0=disabled. 1=default. 2,3,4,... alternatives.\n"); + dw_printf (" Use 9 to test compatibility with your terminal.\n"); dw_printf (" -a n Audio statistics interval in seconds. 0 to disable.\n"); #if __WIN32__ #else dw_printf (" -p Enable pseudo terminal for KISS protocol.\n"); #endif dw_printf (" -x Send Xmit level calibration tones.\n"); - dw_printf (" -U Print UTF-8 test string and exit.\n"); + dw_printf (" -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"); + dw_printf (" -e ber Receive Bit Error Rate (BER), e.g. 1e-5\n"); dw_printf ("\n"); dw_printf ("After any options, there can be a single command line argument for the source of\n"); @@ -1291,9 +1510,12 @@ static void usage (char **argv) dw_printf ("\n"); #if __WIN32__ + dw_printf ("Complete documentation can be found in the 'doc' folder\n"); #else - dw_printf ("Complete documentation can be found in /usr/local/share/doc/direwolf.\n"); + // TODO: Could vary by platform and build options. + 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"); exit (EXIT_FAILURE); } diff --git a/direwolf.h b/src/direwolf.h similarity index 96% rename from direwolf.h rename to src/direwolf.h index 514bcc5..efc329b 100644 --- a/direwolf.h +++ b/src/direwolf.h @@ -4,6 +4,11 @@ // TODO: include this file first before anything else in each .c file. +#ifdef NDEBUG +#undef NDEBUG // Because it would disable assert(). +#endif + + #ifndef DIREWOLF_H #define DIREWOLF_H 1 @@ -27,6 +32,7 @@ #define _WIN32_WINNT 0x0501 /* Minimum OS version is XP. */ #define WINVER 0x0501 /* Minimum OS version is XP. */ +#include #include #endif @@ -107,15 +113,17 @@ #define SLEEP_MS(n) usleep((n)*1000) #endif - #if __WIN32__ + #define PTW32_STATIC_LIB //#include "pthreads/pthread.h" -#define gmtime_r( _clock, _result ) \ - ( *(_result) = *gmtime( (_clock) ), \ - (_result) ) + +// This enables definitions of localtime_r and gmtime_r in system time.h. +//#define _POSIX_THREAD_SAFE_FUNCTIONS 1 +#define _POSIX_C_SOURCE 1 + #else -#include + #include #endif diff --git a/dlq.c b/src/dlq.c similarity index 93% rename from dlq.c rename to src/dlq.c index 5131674..2f21f6d 100644 --- a/dlq.c +++ b/src/dlq.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2014, 2015, 2016 John Langner, WB2OSZ +// Copyright (C) 2014, 2015, 2016, 2018 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 @@ -209,6 +209,9 @@ void dlq_init (void) * display of audio level line. * Use -2 to indicate DTMF message.) * + * is_fx25 - Was it from FX.25? Need to know because + * meaning of retries is different. + * * retries - Level of bit correction used. * * spectrum - Display of how well multiple decoders did. @@ -219,7 +222,7 @@ void dlq_init (void) * *--------------------------------------------------------------------*/ -void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum) +void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, int is_fx25, retry_t retries, char *spectrum) { struct dlq_item_s *pnew; @@ -264,6 +267,7 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev pnew->subchan = subchan; pnew->pp = pp; pnew->alevel = alevel; + pnew->is_fx25 = is_fx25; pnew->retries = retries; if (spectrum == NULL) strlcpy(pnew->spectrum, "", sizeof(pnew->spectrum)); @@ -553,7 +557,69 @@ void dlq_disconnect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int append_to_queue (pnew); -} /* end dlq_connect_request */ +} /* end dlq_disconnect_request */ + + + +/*------------------------------------------------------------------- + * + * Name: dlq_outstanding_frames_request + * + * Purpose: Client application wants to know number of outstanding information + * frames supplied, supplied by the client, that have not yet been + * delivered to the remote station. + * + * Inputs: addrs - Source (owncall), destination (peercall) + * + * num_addr - Number of addresses. Should be 2. + * If more they will be ignored. + * + * chan - Channel, 0 is first. + * + * client - Client application instance. We could have multiple + * applications, all on the same channel, connecting + * to different stations. We need to know which one + * should get the results. + * + * Outputs: Request is appended to queue for processing by + * the data link state machine. + * + * Description: The data link state machine will count up all information frames + * for the given source(mycall) / destination(remote) / channel link. + * A 'Y' response will be sent back to the client application. + * + *--------------------------------------------------------------------*/ + +void dlq_outstanding_frames_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client) +{ + struct dlq_item_s *pnew; +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("dlq_outstanding_frames_request (...)\n"); +#endif + + assert (chan >= 0 && chan < MAX_CHANS); + +/* Allocate a new queue item. */ + + pnew = (struct dlq_item_s *) calloc (sizeof(struct dlq_item_s), 1); + s_new_count++; + + pnew->type = DLQ_OUTSTANDING_FRAMES_REQUEST; + pnew->chan = chan; + memcpy (pnew->addrs, addrs, sizeof(pnew->addrs)); + pnew->num_addr = num_addr; + pnew->client = client; + +/* Put it into queue. */ + + append_to_queue (pnew); + +} /* end dlq_outstanding_frames_request */ + + + + /*------------------------------------------------------------------- diff --git a/dlq.h b/src/dlq.h similarity index 88% rename from dlq.h rename to src/dlq.h index 336870c..8771636 100644 --- a/dlq.h +++ b/src/dlq.h @@ -35,7 +35,7 @@ typedef struct cdata_s { /* Types of things that can be in queue. */ -typedef enum dlq_type_e {DLQ_REC_FRAME, DLQ_CONNECT_REQUEST, DLQ_DISCONNECT_REQUEST, DLQ_XMIT_DATA_REQUEST, DLQ_REGISTER_CALLSIGN, DLQ_UNREGISTER_CALLSIGN, DLQ_CHANNEL_BUSY, DLQ_SEIZE_CONFIRM, DLQ_CLIENT_CLEANUP} dlq_type_t; +typedef enum dlq_type_e {DLQ_REC_FRAME, DLQ_CONNECT_REQUEST, DLQ_DISCONNECT_REQUEST, DLQ_XMIT_DATA_REQUEST, DLQ_REGISTER_CALLSIGN, DLQ_UNREGISTER_CALLSIGN, DLQ_OUTSTANDING_FRAMES_REQUEST, DLQ_CHANNEL_BUSY, DLQ_SEIZE_CONFIRM, DLQ_CLIENT_CLEANUP} dlq_type_t; /* A queue item. */ @@ -68,7 +68,11 @@ typedef struct dlq_item_s { alevel_t alevel; /* Audio level. */ + int is_fx25; /* Was it from FX.25? */ + retry_t retries; /* Effort expended to get a valid CRC. */ + /* Bits changed for regular AX.25. */ + /* Number of bytes fixed for FX.25. */ char spectrum[MAX_SUBCHANS*MAX_SLICERS+1]; /* "Spectrum" display for multi-decoders. */ @@ -102,12 +106,14 @@ void dlq_init (void); -void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, retry_t retries, char *spectrum); +void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alevel, int is_fx25, retry_t retries, char *spectrum); void dlq_connect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int pid); void dlq_disconnect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client); +void dlq_outstanding_frames_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client); + void dlq_xmit_data_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num_addr, int chan, int client, int pid, char *xdata_ptr, int xdata_len); void dlq_register_callsign (char addr[AX25_MAX_ADDR_LEN], int chan, int client); diff --git a/dsp.c b/src/dsp.c similarity index 64% rename from dsp.c rename to src/dsp.c index fa16305..6ba7094 100644 --- a/dsp.c +++ b/src/dsp.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013, 2015 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2015, 2019 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 @@ -51,7 +51,7 @@ // Don't remove this. It serves as a reminder that an experiment is underway. -#if defined(TUNE_MS_FILTER_SIZE) || defined(TUNE_AGC_FAST) || defined(TUNE_LPF_BAUD) || defined(TUNE_PLL_LOCKED) || defined(TUNE_PROFILE) +#if defined(TUNE_MS_FILTER_SIZE) || defined(TUNE_MS2_FILTER_SIZE) || defined(TUNE_AGC_FAST) || defined(TUNE_LPF_BAUD) || defined(TUNE_PLL_LOCKED) || defined(TUNE_PROFILE) #define DEBUG1 1 // Don't remove this. #endif @@ -118,13 +118,16 @@ float window (bp_window_t type, int size, int j) * Inputs: fc - Cutoff frequency as fraction of sampling frequency. * filter_size - Number of filter taps. * wtype - Window type, BP_WINDOW_HAMMING, etc. + * lp_delay_fract - Fudge factor for the delay value. * * Outputs: lp_filter * + * Returns: Signal delay thru the filter in number of audio samples. + * *----------------------------------------------------------------*/ -void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype) +int gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype, float lp_delay_fract) { int j; float G; @@ -171,14 +174,69 @@ void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype 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); + +} /* end gen_lowpass */ + + +#undef DEBUG1 + /*------------------------------------------------------------------ * * Name: gen_bandpass * - * Purpose: Generate band pass filter kernel. + * Purpose: Generate band pass filter kernel for the prefilter. + * This is NOT for the mark/space filters. * * Inputs: f1 - Lower cutoff frequency as fraction of sampling frequency. * f2 - Upper cutoff frequency... @@ -187,10 +245,10 @@ void gen_lowpass (float fc, float *lp_filter, int filter_size, bp_window_t wtype * * Outputs: bp_filter * - * Reference: http://www.labbookpages.co.uk/audio/firWindowing.html + * Reference: http://www.labbookpages.co.uk/audio/firWindowing.html * * Does it need to be an odd length? - * + * *----------------------------------------------------------------*/ @@ -201,7 +259,6 @@ void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_win float G; float center = 0.5 * (filter_size - 1); - #if DEBUG1 text_color_set(DW_COLOR_DEBUG); @@ -229,7 +286,8 @@ void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_win #if DEBUG1 dw_printf ("%6d %6.2f %6.3f %6.3f\n", j, shape, sinc, bp_filter[j] ) ; #endif - } + } + /* * Normalize bandpass for unity gain in middle of passband. @@ -249,6 +307,66 @@ void gen_bandpass (float f1, float f2, float *bp_filter, int filter_size, bp_win for (j=0; j #include #include +#include #if __WIN32__ #error Not for Windows @@ -54,13 +55,12 @@ #include -// Debian bug report: direwolf (1.2-1) FTBFS with libgps22 as part of the gpsd transition (#803605): -// dwgps.c claims to only support GPSD_API_MAJOR_VERSION 5, but also builds successfully with -// GPSD_API_MAJOR_VERSION 6 provided by libgps22 when the attached patch is applied. -// Also compatible with API 7 with conditional compilation later. -#if GPSD_API_MAJOR_VERSION < 5 || GPSD_API_MAJOR_VERSION > 7 +// 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 #error libgps API version might be incompatible. #endif @@ -115,7 +115,7 @@ static void * read_gpsd_thread (void *arg); * shared region via dwgps_put_data. * * The application calls dwgps_read to get the most - 8 recent information. + * recent information. * *--------------------------------------------------------------------*/ @@ -125,7 +125,7 @@ static void * read_gpsd_thread (void *arg); * Originally, I wanted to use the shared memory interface to gpsd * because it is simpler and more efficient. Just access it when we * actually need the data and we don't have a lot of extra unnecessary - * busy work going on. + * busy work going on constantly polling it when we don't need the information. * * The current version of gpsd, supplied with Raspian (Wheezy), is 3.6 from back in * May 2012, is missing support for the shared memory interface. @@ -141,13 +141,28 @@ static void * read_gpsd_thread (void *arg); * cd gpsd-3.11 * scons prefix=/usr libdir=lib/arm-linux-gnueabihf shm_export=True python=False * sudo scons udev-install - * + * * For now, we will use the socket interface. Maybe get back to this again someday. * * Update: January 2016. * * I'm told that the shared memory interface might work in Raspian, Jessie version. * Haven't tried it yet. + * + * June 2020: This is how to build the most recent. + * + * Based on https://www.satsignal.eu/raspberry-pi/UpdatingGPSD.html + * + * git clone https://gitlab.com/gpsd/gpsd.git gpsd-gitlab + * cd gpsd-gitlab + * scons --config=force + * scons + * sudo scons install + * + * The problem we have here is that the library is put in /usr/local/lib and direwolf + * can't find it there. Solution is to define environment variable: + * + * export LD_LIBRARY_PATH=/use/local/lib */ @@ -193,7 +208,7 @@ int dwgpsd_init (struct misc_config_s *pconfig, int debug) gps_stream(&gpsdata, WATCH_ENABLE | WATCH_JSON, NULL); - e = pthread_create (&read_gps_tid, NULL, read_gpsd_thread, (void *)(long)arg); + e = pthread_create (&read_gps_tid, NULL, read_gpsd_thread, (void *)(ptrdiff_t)arg); if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("Could not create GPS reader thread"); @@ -231,7 +246,7 @@ int dwgpsd_init (struct misc_config_s *pconfig, int debug) * *--------------------------------------------------------------------*/ -#define TIMEOUT 30 +#define TIMEOUT 15 #if ENABLE_GPSD @@ -254,10 +269,19 @@ static void * read_gpsd_thread (void *arg) while (1) { +// Example code found here: +// https://lists.nongnu.org/archive/html/gpsd-dev/2017-11/msg00001.html + if ( ! gps_waiting(&gpsdata, TIMEOUT * 1000000)) { text_color_set(DW_COLOR_ERROR); - dw_printf ("GPSD: Timeout waiting for GPS data.\n"); - /* Fall thru to read which should get error and bail out. */ + dw_printf ("------------------------------------------\n"); + dw_printf ("dwgpsd: Timeout waiting for GPS data.\n"); + dw_printf ("Is GPSD daemon running?\n"); + dw_printf ("Troubleshooting tip: Try running cgps or xgps.\n"); + dw_printf ("------------------------------------------\n"); + info.fix = DWFIX_ERROR; + SLEEP_MS(5000); + continue; } // https://github.com/wb2osz/direwolf/issues/196 @@ -289,16 +313,56 @@ static void * read_gpsd_thread (void *arg) break; // Jump out of loop and terminate thread. } +#if GPSD_API_MAJOR_VERSION >= 9 + +// The gps.h revision history says: +// * mark altitude in gps_fix_t as deprecated and undefined +// This seems really stupid to me. +// If it is deprecated and undefined then take it out. Someone trying to use +// it would get a compile error and know that something needs to be done. +// Instead we all just go merrily on our way using a field that is [allegedly] undefined. +// Why not simply add more variables with different definitions of altitude +// and keep the original variable working as it always did? +// If it is truly undefined, as the comment would have us believe, numerous +// people will WASTE VAST AMOUNTS OF TIME pondering why altitude is now broken in +// their applications. + +#define stupid_altitude altMSL +#else +#define stupid_altitude altitude +#endif + +#if GPSD_API_MAJOR_VERSION >= 10 + +// They did it again. Whimsical incompatibilities that cause +// pain and aggravation for everyone trying to use this library. +// +// error: ‘struct gps_data_t’ has no member named ‘status’ +// +// Yes, I can understand that it is a more logical place but it breaks +// all existing code that uses this. +// I'm really getting annoyed about wasting so much time on keeping up with all +// of these incompatibilities that are completely unnecessary. + +#define stupid_status fix.status +#else +#define stupid_status status +#endif + + + if (s_debug >= 3) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("gpsdata: status=%d, mode=%d, lat=%.6f, lon=%.6f, track=%.1f, speed=%.1f, alt=%.0f\n", + gpsdata.stupid_status, gpsdata.fix.mode, + gpsdata.fix.latitude, gpsdata.fix.longitude, + gpsdata.fix.track, gpsdata.fix.speed, gpsdata.fix.stupid_altitude); + } + + // Inform user about change in fix status. + switch (gpsdata.fix.mode) { default: case MODE_NOT_SEEN: - if (info.fix >= DWFIX_2D) { - text_color_set(DW_COLOR_INFO); - dw_printf ("GPSD: Lost location fix.\n"); - } - info.fix = DWFIX_NOT_SEEN; - break; - case MODE_NO_FIX: if (info.fix >= DWFIX_2D) { text_color_set(DW_COLOR_INFO); @@ -324,21 +388,25 @@ static void * read_gpsd_thread (void *arg) break; } - /* Data is available. */ - // TODO: what is gpsdata.status? +// Oct. 2020 - 'status' is always zero for latest version of libgps so we can't use that anymore. - if (gpsdata.status >= STATUS_FIX && gpsdata.fix.mode >= MODE_2D) { - - info.dlat = isnan(gpsdata.fix.latitude) ? G_UNKNOWN : gpsdata.fix.latitude; - info.dlon = isnan(gpsdata.fix.longitude) ? G_UNKNOWN : gpsdata.fix.longitude; - info.track = isnan(gpsdata.fix.track) ? G_UNKNOWN : gpsdata.fix.track; - info.speed_knots = isnan(gpsdata.fix.speed) ? G_UNKNOWN : (MPS_TO_KNOTS * gpsdata.fix.speed); + 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; + // 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; if (gpsdata.fix.mode >= MODE_3D) { - info.altitude = isnan(gpsdata.fix.altitude) ? G_UNKNOWN : gpsdata.fix.altitude; + info.altitude = isfinite(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. } + // Otherwise keep the last known location which is better than totally lost. + // Caller knows location is outdated if info.fix == DWFIX_NO_FIX. + info.timestamp = time(NULL); if (s_debug >= 2) { @@ -372,6 +440,7 @@ void dwgpsd_term (void) { #if ENABLE_GPSD + gps_stream (&gpsdata, WATCH_DISABLE, NULL); gps_close (&gpsdata); #endif diff --git a/dwgpsd.h b/src/dwgpsd.h similarity index 100% rename from dwgpsd.h rename to src/dwgpsd.h diff --git a/dwgpsnmea.c b/src/dwgpsnmea.c similarity index 96% rename from dwgpsnmea.c rename to src/dwgpsnmea.c index 6ce6963..840ab65 100644 --- a/dwgpsnmea.c +++ b/src/dwgpsnmea.c @@ -26,6 +26,21 @@ * * Description: This version is available for all operating systems. * + * + * TODO: GPS is no longer the only game in town. + * "GNSS" is often seen as a more general term to include + * other similar systems. Some receivers will receive + * multiple types at the same time and combine them + * for greater accuracy and reliability. + * + * We can now see NMEA sentences with other "Talker IDs." + * + * $GPxxx = GPS + * $GLxxx = GLONASS + * $GAxxx = Galileo + * $GBxxx = BeiDou + * $GNxxx = Any combination + * *---------------------------------------------------------------*/ @@ -39,6 +54,7 @@ #include #include #include +#include #include "textcolor.h" @@ -136,7 +152,7 @@ int dwgpsnmea_init (struct misc_config_s *pconfig, int debug) if (s_gpsnmea_port_fd != MYFDERROR) { #if __WIN32__ - read_gps_th = (HANDLE)_beginthreadex (NULL, 0, read_gpsnmea_thread, (void*)(long)s_gpsnmea_port_fd, 0, NULL); + read_gps_th = (HANDLE)_beginthreadex (NULL, 0, read_gpsnmea_thread, (void*)(ptrdiff_t)s_gpsnmea_port_fd, 0, NULL); if (read_gps_th == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not create GPS NMEA listening thread.\n"); @@ -144,7 +160,7 @@ int dwgpsnmea_init (struct misc_config_s *pconfig, int debug) } #else int e; - e = pthread_create (&read_gps_tid, NULL, read_gpsnmea_thread, (void*)(long)s_gpsnmea_port_fd); + e = pthread_create (&read_gps_tid, NULL, read_gpsnmea_thread, (void*)(ptrdiff_t)s_gpsnmea_port_fd); if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("Could not create GPS NMEA listening thread."); @@ -201,7 +217,7 @@ static unsigned __stdcall read_gpsnmea_thread (void *arg) static void * read_gpsnmea_thread (void *arg) #endif { - MYFDTYPE fd = (MYFDTYPE)(long)arg; + MYFDTYPE fd = (MYFDTYPE)(ptrdiff_t)arg; // Maximum length of message from GPS receiver is 82 according to some people. // Make buffer considerably larger to be safe. @@ -215,7 +231,7 @@ static void * read_gpsnmea_thread (void *arg) if (s_debug >= 2) { text_color_set(DW_COLOR_DEBUG); - dw_printf ("read_gpsnmea_thread (%d)\n", (int)(long)arg); + dw_printf ("read_gpsnmea_thread (%d)\n", (int)(ptrdiff_t)arg); } dwgps_clear (&info); @@ -274,7 +290,8 @@ static void * read_gpsnmea_thread (void *arg) /* Process sentence. */ - if (strncmp(gps_msg, "$GPRMC", 6) == 0) { + 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); @@ -318,7 +335,8 @@ static void * read_gpsnmea_thread (void *arg) } } - else if (strncmp(gps_msg, "$GPGGA", 6) == 0) { + 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); @@ -691,6 +709,7 @@ dwfix_t dwgpsnmea_gpgga (char *sentence, int quiet, double *odlat, double *odlon return (DWFIX_ERROR); } + // TODO: num sat... /* diff --git a/dwgpsnmea.h b/src/dwgpsnmea.h similarity index 100% rename from dwgpsnmea.h rename to src/dwgpsnmea.h diff --git a/sock.c b/src/dwsock.c similarity index 91% rename from sock.c rename to src/dwsock.c index 37ff25e..6324a2f 100644 --- a/sock.c +++ b/src/dwsock.c @@ -21,7 +21,7 @@ /*------------------------------------------------------------------ * - * Module: sock.c + * Module: dwsock.c * * Purpose: Functions for TCP sockets. * @@ -54,7 +54,7 @@ #include #include //#include -#include +#include #endif @@ -66,14 +66,14 @@ #include #include "textcolor.h" -#include "sock.h" +#include "dwsock.h" static void shuffle (struct addrinfo *host[], int nhosts); /*------------------------------------------------------------------- * - * Name: sock_init + * Name: dwsock_init * * Purpose: Preparation before using socket interface. * @@ -97,7 +97,7 @@ static void shuffle (struct addrinfo *host[], int nhosts); * *--------------------------------------------------------------------*/ -int sock_init(void) +int dwsock_init(void) { #if __WIN32__ WSADATA wsadata; @@ -119,7 +119,7 @@ int sock_init(void) #endif return (0); -} /* end sock_init */ +} /* end dwsock_init */ @@ -141,7 +141,7 @@ int sock_init(void) * debug - Print debugging information. * * Outputs: ipaddr_str - The IP address, in text form, is placed here in case - * the caller wants it. Should be SOCK_IPADDR_LEN bytes. + * the caller wants it. Should be DWSOCK_IPADDR_LEN bytes. * * Returns: Socket Handle / file descriptor or -1 for error. * @@ -160,7 +160,7 @@ int sock_init(void) * *--------------------------------------------------------------------*/ -int sock_connect (char *hostname, char *port, char *description, int allow_ipv6, int debug, char ipaddr_str[SOCK_IPADDR_LEN]) +int dwsock_connect (char *hostname, char *port, char *description, int allow_ipv6, int debug, char ipaddr_str[DWSOCK_IPADDR_LEN]) { #define MAX_HOSTS 50 @@ -172,7 +172,7 @@ int sock_connect (char *hostname, char *port, char *description, int allow_ipv6, int err; int server_sock = -1; - strlcpy (ipaddr_str, "???", SOCK_IPADDR_LEN); + strlcpy (ipaddr_str, "???", DWSOCK_IPADDR_LEN); memset (&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; /* Allow either IPv4 or IPv6. */ @@ -212,7 +212,7 @@ int sock_connect (char *hostname, char *port, char *description, int allow_ipv6, if (debug) { text_color_set(DW_COLOR_DEBUG); - sock_ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, SOCK_IPADDR_LEN); + dwsock_ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, DWSOCK_IPADDR_LEN); dw_printf (" %s\n", ipaddr_str); } @@ -226,7 +226,7 @@ int sock_connect (char *hostname, char *port, char *description, int allow_ipv6, text_color_set(DW_COLOR_DEBUG); dw_printf ("addresses for hostname:\n"); for (n=0; nai_family, hosts[n]->ai_addr, ipaddr_str, SOCK_IPADDR_LEN); + dwsock_ia_to_text (hosts[n]->ai_family, hosts[n]->ai_addr, ipaddr_str, DWSOCK_IPADDR_LEN); dw_printf (" %s\n", ipaddr_str); } } @@ -242,7 +242,7 @@ int sock_connect (char *hostname, char *port, char *description, int allow_ipv6, #endif ai = hosts[n]; - sock_ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, SOCK_IPADDR_LEN); + dwsock_ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, DWSOCK_IPADDR_LEN); is = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); #if __WIN32__ if (is == INVALID_SOCKET) { @@ -313,13 +313,13 @@ int sock_connect (char *hostname, char *port, char *description, int allow_ipv6, return (server_sock); -} /* end sock_connect */ +} /* end dwsock_connect */ /*------------------------------------------------------------------- * - * Name: sock_bind + * Name: dwsock_bind * * Purpose: We also have a bunch of duplicate code for the server side. * @@ -366,7 +366,7 @@ static void shuffle (struct addrinfo *host[], int nhosts) /*------------------------------------------------------------------- * - * Name: sock_ia_to_text + * Name: dwsock_ia_to_text * * Purpose: Convert binary IP Address to text form. * @@ -392,7 +392,7 @@ static void shuffle (struct addrinfo *host[], int nhosts) * *--------------------------------------------------------------------*/ -char *sock_ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t StringBufSize) +char *dwsock_ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t StringBufSize) { struct sockaddr_in *sa4; struct sockaddr_in6 *sa6; @@ -433,6 +433,19 @@ char *sock_ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t Stri } return pStringBuf; -} /* end sock_ia_to_text */ +} /* end dwsock_ia_to_text */ -/* end sock.c */ + +void dwsock_close (int fd) +{ +#if __WIN32__ + closesocket (fd); +#else + close (fd); +#endif +} + + + + +/* end dwsock.c */ diff --git a/src/dwsock.h b/src/dwsock.h new file mode 100644 index 0000000..23e63e1 --- /dev/null +++ b/src/dwsock.h @@ -0,0 +1,21 @@ + +/* dwsock.h - Socket helper functions. */ + +#ifndef DWSOCK_H +#define DWSOCK_H 1 + +#define DWSOCK_IPADDR_LEN 48 // Size of string to hold IPv4 or IPv6 address. + // I think 40 would be adequate but we'll make + // it a little larger just to be safe. + // Use INET6_ADDRSTRLEN (from netinet/in.h) instead? + +int dwsock_init (void); + +int dwsock_connect (char *hostname, char *port, char *description, int allow_ipv6, int debug, char *ipaddr_str); + /* 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); + +void dwsock_close (int fd); + +#endif \ No newline at end of file diff --git a/encode_aprs.c b/src/encode_aprs.c similarity index 91% rename from encode_aprs.c rename to src/encode_aprs.c index f46bca8..225cb08 100644 --- a/encode_aprs.c +++ b/src/encode_aprs.c @@ -759,6 +759,63 @@ int encode_object (char *name, int compressed, time_t thyme, double lat, double } /* end encode_object */ + +/*------------------------------------------------------------------ + * + * Name: encode_message + * + * Purpose: Construct info part for APRS "message" format. + * + * 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 + * caller, to avoid buffer overflow. + * + * Outputs: presult - Stored here. + * + * Returns: Number of characters in result. + * + * Description: + * + *----------------------------------------------------------------*/ + + +typedef struct aprs_message_s { + char dti; /* : Data Type Indicator */ + char addressee[9]; /* Fixed width 9 characters. */ + char sep; /* : separator */ + char text; + } aprs_message_t; + +int encode_message (char *addressee, char *text, char *id, char *presult, size_t result_size) +{ + aprs_message_t *p = (aprs_message_t *) presult; + int n; + + p->dti = ':'; + + memset (p->addressee, ' ', sizeof(p->addressee)); + n = strlen(addressee); + if (n > (int)(sizeof(p->addressee))) n = sizeof(p->addressee); + memcpy (p->addressee, addressee, n); + + p->sep = ':'; + p->text = '\0'; + + strlcat (presult, text, result_size); + if (strlen(id) > 0) { + strlcat (presult, "{", result_size); + strlcat (presult, id, result_size); + } + + return (strlen(presult)); + +} /* end encode_message */ + + + + /*------------------------------------------------------------------ * * Name: main @@ -767,7 +824,7 @@ int encode_object (char *name, int compressed, time_t thyme, double lat, double * * Description: Just a smattering, not an organized test. * - * $ rm a.exe ; gcc -DEN_MAIN encode_aprs.c latlong.c textcolor.c ; ./a.exe + * $ rm a.exe ; gcc -DEN_MAIN encode_aprs.c latlong.c textcolor.c misc.a ; ./a.exe * *----------------------------------------------------------------*/ @@ -881,9 +938,35 @@ int main (int argc, char *argv[]) exit (EXIT_FAILURE); } + +/*********** Message. ***********/ + + + encode_message ("N2GH", "some stuff", "", result, sizeof(result)); + dw_printf ("%s\n", result); + if (strcmp(result, ":N2GH :some stuff") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; } + + + encode_message ("N2GH", "other stuff", "12345", result, sizeof(result)); + dw_printf ("%s\n", result); + if (strcmp(result, ":N2GH :other stuff{12345") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; } + + + encode_message ("WB2OSZ-123", "other stuff", "12345", result, sizeof(result)); + dw_printf ("%s\n", result); + if (strcmp(result, ":WB2OSZ-12:other stuff{12345") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; } + + + if (errors != 0) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Encode APRS test FAILED with %d errors.\n", errors); + exit (EXIT_FAILURE); + } + text_color_set (DW_COLOR_REC); dw_printf ("Encode APRS test PASSED with no errors.\n"); exit (EXIT_SUCCESS); + } /* end main */ diff --git a/encode_aprs.h b/src/encode_aprs.h similarity index 86% rename from encode_aprs.h rename to src/encode_aprs.h index b100e11..dc7b8bd 100644 --- a/encode_aprs.h +++ b/src/encode_aprs.h @@ -14,3 +14,4 @@ int encode_object (char *name, int compressed, time_t thyme, double lat, double float freq, float tone, float offset, char *comment, char *presult, size_t result_size); +int encode_message (char *addressee, char *text, char *id, char *presult, size_t result_size); diff --git a/fcs_calc.c b/src/fcs_calc.c similarity index 100% rename from fcs_calc.c rename to src/fcs_calc.c diff --git a/fcs_calc.h b/src/fcs_calc.h similarity index 100% rename from fcs_calc.h rename to src/fcs_calc.h diff --git a/fsk_demod_agc.h b/src/fsk_demod_agc.h similarity index 100% rename from fsk_demod_agc.h rename to src/fsk_demod_agc.h diff --git a/src/fsk_demod_state.h b/src/fsk_demod_state.h new file mode 100644 index 0000000..f7b5650 --- /dev/null +++ b/src/fsk_demod_state.h @@ -0,0 +1,449 @@ +/* fsk_demod_state.h */ + +#ifndef FSK_DEMOD_STATE_H + +#include "rpack.h" + +#include "audio.h" // for enum modem_t + +/* + * Demodulator state. + * The name of the file is from we only had FSK. Now we have other techniques. + * Different copy is required for each channel & subchannel being processed concurrently. + */ + +// TODO1.2: change prefix from BP_ to DSP_ + +typedef enum bp_window_e { BP_WINDOW_TRUNCATED, + BP_WINDOW_COSINE, + BP_WINDOW_HAMMING, + BP_WINDOW_BLACKMAN, + BP_WINDOW_FLATTOP } bp_window_t; + + +struct demodulator_state_s +{ +/* + * These are set once during initialization. + */ + enum modem_t modem_type; // MODEM_AFSK, MODEM_8PSK, etc. + +// enum v26_e v26_alt; // Which alternative when V.26. + + char profile; // 'A', 'B', etc. Upper case. + // Only needed to see if we are using 'F' to take fast path. + +#define TICKS_PER_PLL_CYCLE ( 256.0 * 256.0 * 256.0 * 256.0 ) + + int pll_step_per_sample; // PLL is advanced by this much each audio sample. + // Data is sampled when it overflows. + + + int ms_filter_size; /* Size of mark & space filters, in audio samples. */ + /* Started off as a guess of one bit length */ + /* but 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. + * Second is frequency as ratio to baud rate for FIR. + */ + int lpf_use_fir; /* 0 for IIR, 1 for FIR. */ + + float lpf_iir; /* Only if using IIR. */ + + float lpf_baud; /* Cutoff frequency as fraction of baud. */ + /* Intuitively we'd expect this to be somewhere */ + /* in the range of 0.5 to 1. */ + /* In practice, it turned out a little larger */ + /* for profiles B, C, D. */ + + float lp_filter_len_bits; /* Length in number of bit 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. */ + + int lp_filter_delay; /* Number of samples that the low pass filter */ + /* delays the signal. */ + + /* New in 1.6. */ + + +/* + * Automatic gain control. Fast attack and slow decay factors. + */ + float agc_fast_attack; + float agc_slow_decay; + +/* + * Use a longer term view for reporting signal levels. + */ + float quick_attack; + float sluggish_decay; + +/* + * Hysteresis before final demodulator 0 / 1 decision. + */ + float hysteresis; + int num_slicers; /* >1 for multiple slicers. */ + +/* + * Phase Locked Loop (PLL) inertia. + * Larger number means less influence by signal transitions. + */ + float pll_locked_inertia; + float pll_searching_inertia; + + +/* + * Optional band pass pre-filter before mark/space detector. + */ + int use_prefilter; /* True to enable it. */ + + float prefilter_baud; /* Cutoff frequencies, as fraction of */ + /* baud rate, beyond tones used. */ + /* Example, if we used 1600/1800 tones at */ + /* 300 baud, and this was 0.5, the cutoff */ + /* frequencies would be: */ + /* lower = min(1600,1800) - 0.5 * 300 = 1450 */ + /* upper = max(1600,1800) + 0.5 * 300 = 1950 */ + + float pre_filter_len_bits; /* Length in number of bit times. */ + + int pre_filter_size; /* Size of pre filter, in audio samples. */ + + float pre_filter[MAX_FILTER_SIZE] __attribute__((aligned(16))); + + +/* + * Kernel for the mark and space detection filters. + */ + + float m_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); + float m_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); + + float s_sin_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); + float s_cos_table[MAX_FILTER_SIZE] __attribute__((aligned(16))); + + +/* + * The rest are continuously updated. + */ + + 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 + * AGC uses "fast" attack and "slow" decay. + */ + + float alevel_rec_peak; + float alevel_rec_valley; + 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; + + +/* + * For the PLL and data bit timing. + * starting in version 1.2 we can have multiple slicers for one demodulator. + * Each slicer has its own PLL and HDLC decoder. + */ + +/* + * Version 1.3: Clean up subchan vs. slicer. + * + * Originally some number of CHANNELS (originally 2, later 6) + * which can have multiple parallel demodulators called SUB-CHANNELS. + * This was originally for staggered frequencies for HF SSB. + * It can also be used for multiple demodulators with the same + * frequency but other differing parameters. + * Each subchannel has its own demodulator and HDLC decoder. + * + * In version 1.2 we added multiple SLICERS. + * The data structure, here, has multiple slicers per + * demodulator (subchannel). Due to fuzzy thinking or + * expediency, the multiple slicers got mapped into subchannels. + * This means we can't use both multiple decoders and + * multiple slicers at the same time. + * + * Clean this up in 1.3 and keep the concepts separate. + * This means adding a third variable many places + * we are passing around the origin. + * + */ + struct { + + signed int data_clock_pll; // PLL for data clock recovery. + // It is incremented by pll_step_per_sample + // for each audio sample. + // Must be 32 bits!!! + // So far, this is the case for every compiler used. + + signed int prev_d_c_pll; // Previous value of above, before + // incrementing, to detect overflows. + + int prev_demod_data; // Previous data bit detected. + // Used to look for transitions. + float prev_demod_out_f; + + /* This is used only for "9600" baud data. */ + + int lfsr; // Descrambler shift register. + + // This is for detecting phase lock to incoming signal. + + int good_flag; // Set if transition is near where expected, + // i.e. at a good time. + int bad_flag; // Set if transition is not where expected, + // i.e. at a bad time. + unsigned char good_hist; // History of good transitions for past octet. + unsigned char bad_hist; // History of bad transitions for past octet. + unsigned int score; // History of whether good triumphs over bad + // for past 32 symbols. + int data_detect; // True when locked on to signal. + + } slicer [MAX_SLICERS]; // Actual number in use is num_slicers. + // Should be in range 1 .. MAX_SLICERS, +/* + * Version 1.6: + * + * This has become quite disorganized and messy with different combinations of + * fields used for different demodulator types. Start to reorganize it into a common + * part (with things like the DPLL for clock recovery), and separate sections + * for each of the demodulator types. + * Still a lot to do here. + */ + + union { + +////////////////////////////////////////////////////////////////////////////////// +// // +// PSK only. // +// // +////////////////////////////////////////////////////////////////////////////////// + + + struct psk_only_s { + + enum v26_e v26_alt; // Which alternative when V.26. + + float sin_table256[256]; // Precomputed sin table for speed. + + + // Optional band pass pre-filter before phase detector. + +// TODO? put back into common section? +// TODO? Why was I thinking that? + + int use_prefilter; // True to enable it. + + float prefilter_baud; // Cutoff frequencies, as fraction of baud rate, beyond tones used. + // In the case of PSK, we use only a single tone of 1800 Hz. + // If we were using 2400 bps (= 1200 baud), this would be + // the fraction of 1200 for the cutoff below and above 1800. + + + float pre_filter_width_sym; /* Length in number of symbol times. */ + + int pre_filter_taps; /* Size of pre filter, in audio samples. */ + + bp_window_t pre_window; + + float audio_in[MAX_FILTER_SIZE] __attribute__((aligned(16))); + float pre_filter[MAX_FILTER_SIZE] __attribute__((aligned(16))); + + // Use local oscillator or correlate with previous sample. + + int psk_use_lo; /* Use local oscillator rather than self correlation. */ + + unsigned int lo_step; /* How much to advance the local oscillator */ + /* phase for each audio sample. */ + + unsigned int lo_phase; /* Local oscillator phase accumulator for PSK. */ + + // After mixing with LO before low pass filter. + + float I_raw[MAX_FILTER_SIZE] __attribute__((aligned(16))); // signal * LO cos. + float Q_raw[MAX_FILTER_SIZE] __attribute__((aligned(16))); // signal * LO sin. + + // Number of delay line taps into previous symbol. + // They are one symbol period and + or - 45 degrees of the carrier frequency. + + int boffs; /* symbol length based on sample rate and baud. */ + int coffs; /* to get cos component of previous symbol. */ + int soffs; /* to get sin component of previous symbol. */ + + float delay_line_width_sym; + int delay_line_taps; // In audio samples. + + float delay_line[MAX_FILTER_SIZE] __attribute__((aligned(16))); + + // Low pass filter Second is frequency as ratio to baud rate for FIR. + +// TODO? put back into common section? +// TODO? What are the tradeoffs? + float lpf_baud; /* Cutoff frequency as fraction of baud. */ + /* Intuitively we'd expect this to be somewhere */ + /* in the range of 0.5 to 1. */ + + float lp_filter_width_sym; /* Length in number of symbol times. */ + + int lp_filter_taps; /* Size of Low Pass filter, in audio samples (i.e. filter taps). */ + + bp_window_t lp_window; + + float lp_filter[MAX_FILTER_SIZE] __attribute__((aligned(16))); + + } psk; + + } u; // end of union for different demodulator types. + +}; + + +/*------------------------------------------------------------------- + * + * Name: pll_dcd_signal_transition2 + * dcd_each_symbol2 + * + * Purpose: New DCD strategy for 1.6. + * + * Inputs: D Pointer to demodulator state. + * + * chan Radio channel: 0 to MAX_CHANS - 1 + * + * subchan Which of multiple demodulators: 0 to MAX_SUBCHANS - 1 + * + * slice Slicer number: 0 to MAX_SLICERS - 1. + * + * dpll_phase Signed 32 bit counter for DPLL phase. + * Wraparound is where data is sampled. + * Ideally transitions would occur close to 0. + * + * Output: D->slicer[slice].data_detect - true when PLL is locked to incoming signal. + * + * Description: From the beginning, DCD was based on finding several flag octets + * in a row and dropping when eight bits with no transitions. + * It was less than ideal but we limped along with it all these years. + * This fell apart when FX.25 came along and a couple of the + * correlation tags have eight "1" bits in a row. + * + * Our new strategy is to keep a running score of how well demodulator + * output transitions match to where expected. + * + *--------------------------------------------------------------------*/ + +#include "hdlc_rec.h" // for dcd_change + +// These are good for 1200 bps AFSK. +// Might want to override for other modems. + +#ifndef DCD_THRESH_ON +#define DCD_THRESH_ON 30 // Hysteresis: Can miss 2 out of 32 for detecting lock. + // 31 is best for TNC Test CD. 30 almost as good. + // 30 better for 1200 regression test. +#endif + +#ifndef DCD_THRESH_OFF +#define DCD_THRESH_OFF 6 // Might want a little more fine tuning. +#endif + +#ifndef DCD_GOOD_WIDTH +#define DCD_GOOD_WIDTH 512 // No more than 1024!!! +#endif + +__attribute__((always_inline)) +inline static void pll_dcd_signal_transition2 (struct demodulator_state_s *D, int slice, int dpll_phase) +{ + if (dpll_phase > - DCD_GOOD_WIDTH * 1024 * 1024 && dpll_phase < DCD_GOOD_WIDTH * 1024 * 1024) { + D->slicer[slice].good_flag = 1; + } + else { + D->slicer[slice].bad_flag = 1; + } +} + +__attribute__((always_inline)) +inline static void pll_dcd_each_symbol2 (struct demodulator_state_s *D, int chan, int subchan, int slice) +{ + D->slicer[slice].good_hist <<= 1; + D->slicer[slice].good_hist |= D->slicer[slice].good_flag; + D->slicer[slice].good_flag = 0; + + D->slicer[slice].bad_hist <<= 1; + D->slicer[slice].bad_hist |= D->slicer[slice].bad_flag; + D->slicer[slice].bad_flag = 0; + + D->slicer[slice].score <<= 1; + // 2 is to detect 'flag' patterns with 2 transitions per octet. + D->slicer[slice].score |= (signed)__builtin_popcount(D->slicer[slice].good_hist) + - (signed)__builtin_popcount(D->slicer[slice].bad_hist) >= 2; + + int s = __builtin_popcount(D->slicer[slice].score); + if (s >= DCD_THRESH_ON) { + if (D->slicer[slice].data_detect == 0) { + D->slicer[slice].data_detect = 1; + dcd_change (chan, subchan, slice, D->slicer[slice].data_detect); + } + } + else if (s <= DCD_THRESH_OFF) { + if (D->slicer[slice].data_detect != 0) { + D->slicer[slice].data_detect = 0; + dcd_change (chan, subchan, slice, D->slicer[slice].data_detect); + } + } +} + + +#define FSK_DEMOD_STATE_H 1 +#endif diff --git a/fsk_filters.h b/src/fsk_filters.h similarity index 100% rename from fsk_filters.h rename to src/fsk_filters.h diff --git a/fsk_gen_filter.h b/src/fsk_gen_filter.h similarity index 100% rename from fsk_gen_filter.h rename to src/fsk_gen_filter.h diff --git a/src/fx25.h b/src/fx25.h new file mode 100644 index 0000000..99e9d26 --- /dev/null +++ b/src/fx25.h @@ -0,0 +1,96 @@ +#ifndef FX25_H +#define FX25_H + +#include // for uint64_t + + +/* Reed-Solomon codec control block */ +struct rs { + unsigned int mm; /* Bits per symbol */ + unsigned int nn; /* Symbols per block (= (1<mm) +#define NN (rs->nn) +#define ALPHA_TO (rs->alpha_to) +#define INDEX_OF (rs->index_of) +#define GENPOLY (rs->genpoly) +#define NROOTS (rs->nroots) +#define FCR (rs->fcr) +#define PRIM (rs->prim) +#define IPRIM (rs->iprim) +#define A0 (NN) + + + +__attribute__((always_inline)) +static inline int modnn(struct rs *rs, int x){ + while (x >= rs->nn) { + x -= rs->nn; + x = (x >> rs->mm) + (x & rs->nn); + } + return x; +} + +#define MODNN(x) modnn(rs,x) + + +#define ENCODE_RS encode_rs_char +#define DECODE_RS decode_rs_char +#define INIT_RS init_rs_char +#define FREE_RS free_rs_char + +#define DTYPE unsigned char + +void ENCODE_RS(struct rs *rs, DTYPE *data, DTYPE *bb); + +int DECODE_RS(struct rs *rs, DTYPE *data, int *eras_pos, int no_eras); + +struct rs *INIT_RS(unsigned int symsize, unsigned int gfpoly, + unsigned int fcr, unsigned int prim, unsigned int nroots); + +void FREE_RS(struct rs *rs); + + + +// These 3 are the external interface. +// Maybe these should be in a different file, separated from the internal stuff. + +void fx25_init ( int debug_level ); +int fx25_send_frame (int chan, unsigned char *fbuf, int flen, int fx_mode); +void fx25_rec_bit (int chan, int subchan, int slice, int dbit); +int fx25_rec_busy (int chan); + + +// Other functions in fx25_init.c. + +struct rs *fx25_get_rs (int ctag_num); +uint64_t fx25_get_ctag_value (int ctag_num); +int fx25_get_k_data_radio (int ctag_num); +int fx25_get_k_data_rs (int ctag_num); +int fx25_get_nroots (int ctag_num); +int fx25_get_debug (void); +int fx25_tag_find_match (uint64_t t); +int fx25_pick_mode (int fx_mode, int dlen); + +void fx_hex_dump(unsigned char *x, int len); + + + +#define CTAG_MIN 0x01 +#define CTAG_MAX 0x0B + +// Maximum sizes of "data" and "check" parts. + +#define FX25_MAX_DATA 239 // i.e. RS(255,239) +#define FX25_MAX_CHECK 64 // e.g. RS(255, 191) +#define FX25_BLOCK_SIZE 255 // Block size always 255 for 8 bit symbols. + +#endif // FX25_H \ No newline at end of file diff --git a/src/fx25_auto.c b/src/fx25_auto.c new file mode 100644 index 0000000..c84ecef --- /dev/null +++ b/src/fx25_auto.c @@ -0,0 +1 @@ +/* */ \ No newline at end of file diff --git a/src/fx25_encode.c b/src/fx25_encode.c new file mode 100644 index 0000000..02c20a8 --- /dev/null +++ b/src/fx25_encode.c @@ -0,0 +1,84 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2019 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 . +// +// ----------------------------------------------------------------------- +// +// +// Most of this is based on: +// +// FX.25 Encoder +// Author: Jim McGuire KB3MPL +// Date: 23 October 2007 +// +// This program is a single-file implementation of the FX.25 encapsulation +// structure for use with AX.25 data packets. Details of the FX.25 +// specification are available at: +// http://www.stensat.org/Docs/Docs.htm +// +// This program implements a single RS(255,239) FEC structure. Future +// releases will incorporate more capabilities as accommodated in the FX.25 +// spec. +// +// The Reed Solomon encoding routines are based on work performed by +// Phil Karn. Phil was kind enough to release his code under the GPL, as +// noted below. Consequently, this FX.25 implementation is also released +// under the terms of the GPL. +// +// Phil Karn's original copyright notice: + /* Test the Reed-Solomon codecs + * for various block sizes and with random data and random error patterns + * + * Copyright 2002 Phil Karn, KA9Q + * May be used under the terms of the GNU General Public License (GPL) + * + */ + + +#include +#include +#include +#include + +#include "fx25.h" + + + +void ENCODE_RS(struct rs * restrict rs, DTYPE * restrict data, DTYPE * restrict bb) +{ + + int i, j; + DTYPE feedback; + + memset(bb,0,NROOTS*sizeof(DTYPE)); // clear out the FEC data area + + for(i=0;i. +// +// ----------------------------------------------------------------------- +// +// This is based on: +// +// +// FX25_extract.c +// Author: Jim McGuire KB3MPL +// Date: 23 October 2007 +// +// +// Accepts an FX.25 byte stream on STDIN, finds the correlation tag, stores 256 bytes, +// corrects errors with FEC, removes the bit-stuffing, and outputs the resultant AX.25 +// byte stream out STDOUT. +// +// stdout prints a bunch of status information about the packet being processed. +// +// +// Usage : FX25_extract < infile > outfile [2> logfile] +// +// +// +// This program is a single-file implementation of the FX.25 extraction/decode +// structure for use with FX.25 data frames. Details of the FX.25 +// specification are available at: +// http://www.stensat.org/Docs/Docs.htm +// +// This program implements a single RS(255,239) FEC structure. Future +// releases will incorporate more capabilities as accommodated in the FX.25 +// spec. +// +// The Reed Solomon encoding routines are based on work performed by +// Phil Karn. Phil was kind enough to release his code under the GPL, as +// noted below. Consequently, this FX.25 implementation is also released +// under the terms of the GPL. +// +// Phil Karn's original copyright notice: + /* Test the Reed-Solomon codecs + * for various block sizes and with random data and random error patterns + * + * Copyright 2002 Phil Karn, KA9Q + * May be used under the terms of the GNU General Public License (GPL) + * + */ + +#include +#include +#include + +#include "fx25.h" + + +//#define DEBUG 5 + +//----------------------------------------------------------------------- +// Revision History +//----------------------------------------------------------------------- +// 0.0.1 - initial release +// Modifications from Phil Karn's GPL source code. +// Initially added code to run with PC file +// I/O and use the (255,239) decoder exclusively. Confirmed that the +// code produces the correct results. +// +//----------------------------------------------------------------------- +// 0.0.2 - + + +#define min(a,b) ((a) < (b) ? (a) : (b)) + + + +int DECODE_RS(struct rs * restrict rs, DTYPE * restrict data, int *eras_pos, int no_eras) { + + int deg_lambda, el, deg_omega; + int i, j, r,k; + DTYPE u,q,tmp,num1,num2,den,discr_r; +// DTYPE lambda[NROOTS+1], s[NROOTS]; /* Err+Eras Locator poly and syndrome poly */ +// DTYPE b[NROOTS+1], t[NROOTS+1], omega[NROOTS+1]; +// DTYPE root[NROOTS], reg[NROOTS+1], loc[NROOTS]; + DTYPE lambda[FX25_MAX_CHECK+1], s[FX25_MAX_CHECK]; /* Err+Eras Locator poly and syndrome poly */ + DTYPE b[FX25_MAX_CHECK+1], t[FX25_MAX_CHECK+1], omega[FX25_MAX_CHECK+1]; + DTYPE root[FX25_MAX_CHECK], reg[FX25_MAX_CHECK+1], loc[FX25_MAX_CHECK]; + int syn_error, count; + + /* form the syndromes; i.e., evaluate data(x) at roots of g(x) */ + for(i=0;i 0) { + /* Init lambda to be the erasure locator polynomial */ + lambda[1] = ALPHA_TO[MODNN(PRIM*(NN-1-eras_pos[0]))]; + for (i = 1; i < no_eras; i++) { + u = MODNN(PRIM*(NN-1-eras_pos[i])); + for (j = i+1; j > 0; j--) { + tmp = INDEX_OF[lambda[j - 1]]; + if(tmp != A0) + lambda[j] ^= ALPHA_TO[MODNN(u + tmp)]; + } + } + +#if DEBUG >= 1 + /* Test code that verifies the erasure locator polynomial just constructed + Needed only for decoder debugging. */ + + /* find roots of the erasure location polynomial */ + for(i=1;i<=no_eras;i++) + reg[i] = INDEX_OF[lambda[i]]; + + count = 0; + for (i = 1,k=IPRIM-1; i <= NN; i++,k = MODNN(k+IPRIM)) { + q = 1; + for (j = 1; j <= no_eras; j++) + if (reg[j] != A0) { + reg[j] = MODNN(reg[j] + j); + q ^= ALPHA_TO[reg[j]]; + } + if (q != 0) + continue; + /* store root and error location number indices */ + root[count] = i; + loc[count] = k; + count++; + } + if (count != no_eras) { + fprintf(stderr,"count = %d no_eras = %d\n lambda(x) is WRONG\n",count,no_eras); + count = -1; + goto finish; + } +#if DEBUG >= 2 + fprintf(stderr,"\n Erasure positions as determined by roots of Eras Loc Poly:\n"); + for (i = 0; i < count; i++) + fprintf(stderr,"%d ", loc[i]); + fprintf(stderr,"\n"); +#endif +#endif + } + for(i=0;i 0; j--){ + if (reg[j] != A0) { + reg[j] = MODNN(reg[j] + j); + q ^= ALPHA_TO[reg[j]]; + } + } + if (q != 0) + continue; /* Not a root */ + /* store root (index-form) and error location number */ +#if DEBUG>=2 + fprintf(stderr,"count %d root %d loc %d\n",count,i,k); +#endif + root[count] = i; + loc[count] = k; + /* If we've already found max possible roots, + * abort the search to save time + */ + if(++count == deg_lambda) + break; + } + if (deg_lambda != count) { + /* + * deg(lambda) unequal to number of roots => uncorrectable + * error detected + */ + count = -1; + goto finish; + } + /* + * Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo + * x**NROOTS). in index form. Also find deg(omega). + */ + deg_omega = 0; + for (i = 0; i < NROOTS;i++){ + tmp = 0; + j = (deg_lambda < i) ? deg_lambda : i; + for(;j >= 0; j--){ + if ((s[i - j] != A0) && (lambda[j] != A0)) + tmp ^= ALPHA_TO[MODNN(s[i - j] + lambda[j])]; + } + if(tmp != 0) + deg_omega = i; + omega[i] = INDEX_OF[tmp]; + } + omega[NROOTS] = A0; + + /* + * Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = + * inv(X(l))**(FCR-1) and den = lambda_pr(inv(X(l))) all in poly-form + */ + for (j = count-1; j >=0; j--) { + num1 = 0; + for (i = deg_omega; i >= 0; i--) { + if (omega[i] != A0) + num1 ^= ALPHA_TO[MODNN(omega[i] + i * root[j])]; + } + num2 = ALPHA_TO[MODNN(root[j] * (FCR - 1) + NN)]; + den = 0; + + /* lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] */ + for (i = min(deg_lambda,NROOTS-1) & ~1; i >= 0; i -=2) { + if(lambda[i+1] != A0) + den ^= ALPHA_TO[MODNN(lambda[i+1] + i * root[j])]; + } + if (den == 0) { +#if DEBUG >= 1 + fprintf(stderr,"\n ERROR: denominator = 0\n"); +#endif + count = -1; + goto finish; + } + /* Apply error to data */ + if (num1 != 0) { + data[loc[j]] ^= ALPHA_TO[MODNN(INDEX_OF[num1] + INDEX_OF[num2] + NN - INDEX_OF[den])]; + } + } + finish: + if(eras_pos != NULL){ + for(i=0;i. +// +// ----------------------------------------------------------------------- +// +// +// Some of this is based on: +// +// FX.25 Encoder +// Author: Jim McGuire KB3MPL +// Date: 23 October 2007 +// +// This program is a single-file implementation of the FX.25 encapsulation +// structure for use with AX.25 data packets. Details of the FX.25 +// specification are available at: +// http://www.stensat.org/Docs/Docs.htm +// +// This program implements a single RS(255,239) FEC structure. Future +// releases will incorporate more capabilities as accommodated in the FX.25 +// spec. +// +// The Reed Solomon encoding routines are based on work performed by +// Phil Karn. Phil was kind enough to release his code under the GPL, as +// noted below. Consequently, this FX.25 implementation is also released +// under the terms of the GPL. +// +// Phil Karn's original copyright notice: + /* Test the Reed-Solomon codecs + * for various block sizes and with random data and random error patterns + * + * Copyright 2002 Phil Karn, KA9Q + * May be used under the terms of the GNU General Public License (GPL) + * + */ + +#include "direwolf.h" + +#include +#include +#include +#include +#include // uint64_t +#include // PRIx64 +#include + +#include "fx25.h" +#include "textcolor.h" + + +#define NTAB 3 + +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. + 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, 1, 1, 16, NULL }, // RS(255,239) + {8, 0x11d, 1, 1, 32, NULL }, // RS(255,223) + {8, 0x11d, 1, 1, 64, NULL }, // RS(255,191) +}; + +/* + * Reference: http://www.stensat.org/docs/FX-25_01_06.pdf + * FX.25 + * Forward Error Correction Extension to + * AX.25 Link Protocol For Amateur Packet Radio + * Version: 0.01 DRAFT + * Date: 01 September 2006 + */ + +struct correlation_tag_s { + uint64_t value; // 64 bit value, send LSB first. + int n_block_radio; // Size of transmitted block, all in bytes. + int k_data_radio; // Size of transmitted data part. + int n_block_rs; // Size of RS algorithm block. + int k_data_rs; // Size of RS algorithm data part. + int itab; // Index into Tab array. +}; + +static const struct correlation_tag_s tags[16] = { + /* Tag_00 */ { 0x566ED2717946107ELL, 0, 0, 0, 0, -1 }, // Reserved + + /* Tag_01 */ { 0xB74DB7DF8A532F3ELL, 255, 239, 255, 239, 0 }, // RS(255, 239) 16-byte check value, 239 information bytes + /* Tag_02 */ { 0x26FF60A600CC8FDELL, 144, 128, 255, 239, 0 }, // RS(144,128) - shortened RS(255, 239), 128 info bytes + /* Tag_03 */ { 0xC7DC0508F3D9B09ELL, 80, 64, 255, 239, 0 }, // RS(80,64) - shortened RS(255, 239), 64 info bytes + /* Tag_04 */ { 0x8F056EB4369660EELL, 48, 32, 255, 239, 0 }, // RS(48,32) - shortened RS(255, 239), 32 info bytes + + /* Tag_05 */ { 0x6E260B1AC5835FAELL, 255, 223, 255, 223, 1 }, // RS(255, 223) 32-byte check value, 223 information bytes + /* Tag_06 */ { 0xFF94DC634F1CFF4ELL, 160, 128, 255, 223, 1 }, // RS(160,128) - shortened RS(255, 223), 128 info bytes + /* Tag_07 */ { 0x1EB7B9CDBC09C00ELL, 96, 64, 255, 223, 1 }, // RS(96,64) - shortened RS(255, 223), 64 info bytes + /* Tag_08 */ { 0xDBF869BD2DBB1776LL, 64, 32, 255, 223, 1 }, // RS(64,32) - shortened RS(255, 223), 32 info bytes + + /* Tag_09 */ { 0x3ADB0C13DEAE2836LL, 255, 191, 255, 191, 2 }, // RS(255, 191) 64-byte check value, 191 information bytes + /* Tag_0A */ { 0xAB69DB6A543188D6LL, 192, 128, 255, 191, 2 }, // RS(192, 128) - shortened RS(255, 191), 128 info bytes + /* Tag_0B */ { 0x4A4ABEC4A724B796LL, 128, 64, 255, 191, 2 }, // RS(128, 64) - shortened RS(255, 191), 64 info bytes + + /* Tag_0C */ { 0x0293D578626B67E6LL, 0, 0, 0, 0, -1 }, // Undefined + /* Tag_0D */ { 0xE3B0B0D6917E58A6LL, 0, 0, 0, 0, -1 }, // Undefined + /* Tag_0E */ { 0x720267AF1BE1F846LL, 0, 0, 0, 0, -1 }, // Undefined + /* Tag_0F */ { 0x93210201E8F4C706LL, 0, 0, 0, 0, -1 } // Undefined +}; + + + +#define CLOSE_ENOUGH 8 // How many bits can be wrong in tag yet consider it a match? + // Needs to be large enough to match with significant errors + // but not so large to get frequent false matches. + // Probably don't want >= 16 because the hamming distance between + // any two pairs is 32. + // What is a good number? 8?? 12?? 15?? + // 12 got many false matches with random noise. + // Even 8 might be too high. We see 2 or 4 bit errors here + // at the point where decoding the block is very improbable. + // After 2 months of continuous operation as a digipeater/iGate, + // no false triggers were observed. So 8 doesn't seem to be too + // high for 1200 bps. No study has been done for 9600 bps. + +// Given a 64 bit correlation tag value, find acceptable match in table. +// Return index into table or -1 for no match. + +// Both gcc and clang have a built in function to count the number of '1' bits +// in an integer. This can result in a single machine instruction. You might need +// to supply your own popcount function if using a different compiler. + +int fx25_tag_find_match (uint64_t t) +{ + for (int c = CTAG_MIN; c <= CTAG_MAX; c++) { + if (__builtin_popcountll(t ^ tags[c].value) <= CLOSE_ENOUGH) { + //printf ("%016" PRIx64 " received\n", t); + //printf ("%016" PRIx64 " tag %d\n", tags[c].value, c); + //printf ("%016" PRIx64 " xor, popcount = %d\n", t ^ tags[c].value, __builtin_popcountll(t ^ tags[c].value)); + return (c); + } + } + return (-1); +} + + + +void free_rs_char(struct rs *rs){ + free(rs->alpha_to); + free(rs->index_of); + free(rs->genpoly); + free(rs); +} + + +/*------------------------------------------------------------- + * + * Name: fx25_init + * + * Purpose: This must be called once before any of the other fx25 functions. + * + * Inputs: debug_level - Controls level of informational / debug messages. + * + * 0 Only errors. + * 1 (default) Transmitting ctag. Currently no other way to know this. + * 2 Receive correlation tag detected. FEC decode complete. + * 3 Dump data going in and out. + * + * Use command line -dx to increase level or -qx for quiet. + * + * Description: Initialize 3 Reed-Solomon codecs, for 16, 32, and 64 check bytes. + * + *--------------------------------------------------------------*/ + +static int g_debug_level; + +void fx25_init ( int debug_level ) +{ + g_debug_level = debug_level; + + for (int i = 0 ; i < NTAB ; i++) { + 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("FX.25 internal error: init_rs_char failed!\n"); + exit(EXIT_FAILURE); + } + } + + // Verify integrity of tables and assumptions. + // This also does a quick check for the popcount function. + + for (int j = 0; j < 16 ; j++) { + for (int k = 0; k < 16; k++) { + if (j == k) { + assert (__builtin_popcountll(tags[j].value ^ tags[k].value) == 0); + } + else { + assert (__builtin_popcountll(tags[j].value ^ tags[k].value) == 32); + } + } + } + + for (int j = CTAG_MIN; j <= CTAG_MAX; j++) { + assert (tags[j].n_block_radio - tags[j].k_data_radio == Tab[tags[j].itab].nroots); + assert (tags[j].n_block_rs - tags[j].k_data_rs == Tab[tags[j].itab].nroots); + assert (tags[j].n_block_rs == FX25_BLOCK_SIZE); + } + + assert (fx25_pick_mode (100+1, 239) == 1); + assert (fx25_pick_mode (100+1, 240) == -1); + + assert (fx25_pick_mode (100+5, 223) == 5); + assert (fx25_pick_mode (100+5, 224) == -1); + + assert (fx25_pick_mode (100+9, 191) == 9); + assert (fx25_pick_mode (100+9, 192) == -1); + + assert (fx25_pick_mode (16, 32) == 4); + assert (fx25_pick_mode (16, 64) == 3); + assert (fx25_pick_mode (16, 128) == 2); + assert (fx25_pick_mode (16, 239) == 1); + assert (fx25_pick_mode (16, 240) == -1); + + assert (fx25_pick_mode (32, 32) == 8); + assert (fx25_pick_mode (32, 64) == 7); + assert (fx25_pick_mode (32, 128) == 6); + assert (fx25_pick_mode (32, 223) == 5); + assert (fx25_pick_mode (32, 234) == -1); + + assert (fx25_pick_mode (64, 64) == 11); + assert (fx25_pick_mode (64, 128) == 10); + assert (fx25_pick_mode (64, 191) == 9); + assert (fx25_pick_mode (64, 192) == -1); + + assert (fx25_pick_mode (1, 32) == 4); + assert (fx25_pick_mode (1, 33) == 3); + assert (fx25_pick_mode (1, 64) == 3); + assert (fx25_pick_mode (1, 65) == 6); + assert (fx25_pick_mode (1, 128) == 6); + assert (fx25_pick_mode (1, 191) == 9); + assert (fx25_pick_mode (1, 223) == 5); + assert (fx25_pick_mode (1, 239) == 1); + assert (fx25_pick_mode (1, 240) == -1); + +} // fx25_init + + +// Get properties of specified CTAG number. + +struct rs *fx25_get_rs (int ctag_num) +{ + assert (ctag_num >= CTAG_MIN && ctag_num <= CTAG_MAX); + assert (tags[ctag_num].itab >= 0 && tags[ctag_num].itab < NTAB); + assert (Tab[tags[ctag_num].itab].rs != NULL); + return (Tab[tags[ctag_num].itab].rs); +} + +uint64_t fx25_get_ctag_value (int ctag_num) +{ + assert (ctag_num >= CTAG_MIN && ctag_num <= CTAG_MAX); + return (tags[ctag_num].value); +} + +int fx25_get_k_data_radio (int ctag_num) +{ + assert (ctag_num >= CTAG_MIN && ctag_num <= CTAG_MAX); + return (tags[ctag_num].k_data_radio); +} + +int fx25_get_k_data_rs (int ctag_num) +{ + assert (ctag_num >= CTAG_MIN && ctag_num <= CTAG_MAX); + return (tags[ctag_num].k_data_rs); +} + +int fx25_get_nroots (int ctag_num) +{ + assert (ctag_num >= CTAG_MIN && ctag_num <= CTAG_MAX); + return (Tab[tags[ctag_num].itab].nroots); +} + +int fx25_get_debug (void) +{ + return (g_debug_level); +} + +/*------------------------------------------------------------- + * + * Name: fx25_pick_mode + * + * Purpose: Pick suitable transmission format based on user preference + * and size of data part required. + * + * Inputs: fx_mode - 0 = none. + * 1 = pick a tag automatically. + * 16, 32, 64 = use this many check bytes. + * 100 + n = use tag n. + * + * 0 and 1 would be the most common. + * Others are mostly for testing. + * + * dlen - Required size for transmitted "data" part, in bytes. + * This includes the AX.25 frame with bit stuffing and a flag + * pattern on each end. + * + * Returns: Correlation tag number in range of CTAG_MIN thru CTAG_MAX. + * -1 is returned for failure. + * The caller should fall back to using plain old AX.25. + * + *--------------------------------------------------------------*/ + +int fx25_pick_mode (int fx_mode, int dlen) +{ + if (fx_mode <= 0) return (-1); + +// Specify a specific tag by adding 100 to the number. +// Fails if data won't fit. + + if (fx_mode - 100 >= CTAG_MIN && fx_mode - 100 <= CTAG_MAX) { + if (dlen <= fx25_get_k_data_radio(fx_mode - 100)) { + return (fx_mode - 100); + } + else { + return (-1); // Assuming caller prints failure message. + } + } + +// Specify number of check bytes. +// Pick the shortest one that can handle the required data length. + + else if (fx_mode == 16 || fx_mode == 32 || fx_mode == 64) { + for (int k = CTAG_MAX; k >= CTAG_MIN; k--) { + if (fx_mode == fx25_get_nroots(k) && dlen <= fx25_get_k_data_radio(k)) { + return (k); + } + } + return (-1); + } + +// For any other number, [[ or if the preference was not possible, ?? ]] +// try to come up with something reasonable. For shorter frames, +// use smaller overhead. For longer frames, where an error is +// more probable, use more check bytes. When the data gets even +// larger, check bytes must be reduced to fit in block size. +// When all else fails, fall back to normal AX.25. +// Some of this is from observing UZ7HO Soundmodem behavior. +// +// Tag Data Check Max Num +// Number Bytes Bytes Repaired +// ------ ----- ----- ----- +// 0x04 32 16 8 +// 0x03 64 16 8 +// 0x06 128 32 16 +// 0x09 191 64 32 +// 0x05 223 32 16 +// 0x01 239 16 8 +// none larger +// +// The PRUG FX.25 TNC has additional modes that will handle larger frames +// by using multiple RS blocks. This is a future possibility but needs +// to be coordinated with other FX.25 developers so we maintain compatibility. + + static const int prefer[6] = { 0x04, 0x03, 0x06, 0x09, 0x05, 0x01 }; + for (int k = 0; k < 6; k++) { + int m = prefer[k]; + if (dlen <= fx25_get_k_data_radio(m)) { + return (m); + } + } + return (-1); + +// TODO: revisit error messages, produced by caller, when this returns -1. + +} + + +/* Initialize a Reed-Solomon codec + * symsize = symbol size, bits (1-8) - always 8 for this application. + * gfpoly = Field generator polynomial coefficients + * fcr = first root of RS code generator polynomial, index form + * prim = primitive element to generate polynomial roots + * nroots = RS code generator polynomial degree (number of roots) + */ + +struct rs *INIT_RS(unsigned int symsize,unsigned int gfpoly,unsigned fcr,unsigned prim, + unsigned int nroots){ + struct rs *rs; + int i, j, sr,root,iprim; + + if(symsize > 8*sizeof(DTYPE)) + return NULL; /* Need version with ints rather than chars */ + + if(fcr >= (1<= (1<= (1<mm = symsize; + rs->nn = (1<alpha_to = (DTYPE *)malloc(sizeof(DTYPE)*(rs->nn+1)); + if(rs->alpha_to == NULL){ + free(rs); + return NULL; + } + rs->index_of = (DTYPE *)malloc(sizeof(DTYPE)*(rs->nn+1)); + if(rs->index_of == NULL){ + free(rs->alpha_to); + free(rs); + return NULL; + } + + /* Generate Galois field lookup tables */ + rs->index_of[0] = A0; /* log(zero) = -inf */ + rs->alpha_to[A0] = 0; /* alpha**-inf = 0 */ + sr = 1; + for(i=0;inn;i++){ + rs->index_of[sr] = i; + rs->alpha_to[i] = sr; + sr <<= 1; + if(sr & (1<nn; + } + if(sr != 1){ + /* field generator polynomial is not primitive! */ + free(rs->alpha_to); + free(rs->index_of); + free(rs); + return NULL; + } + + /* Form RS code generator polynomial from its roots */ + rs->genpoly = (DTYPE *)malloc(sizeof(DTYPE)*(nroots+1)); + if(rs->genpoly == NULL){ + free(rs->alpha_to); + free(rs->index_of); + free(rs); + return NULL; + } + rs->fcr = fcr; + rs->prim = prim; + rs->nroots = nroots; + + /* Find prim-th root of 1, used in decoding */ + for(iprim=1;(iprim % prim) != 0;iprim += rs->nn) + ; + rs->iprim = iprim / prim; + + rs->genpoly[0] = 1; + for (i = 0,root=fcr*prim; i < nroots; i++,root += prim) { + rs->genpoly[i+1] = 1; + + /* Multiply rs->genpoly[] by @**(root + x) */ + for (j = i; j > 0; j--){ + if (rs->genpoly[j] != 0) + rs->genpoly[j] = rs->genpoly[j-1] ^ rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[j]] + root)]; + else + rs->genpoly[j] = rs->genpoly[j-1]; + } + /* rs->genpoly[0] can never be zero */ + rs->genpoly[0] = rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[0]] + root)]; + } + /* convert rs->genpoly[] to index form for quicker encoding */ + for (i = 0; i <= nroots; i++) { + rs->genpoly[i] = rs->index_of[rs->genpoly[i]]; + } + +// diagnostic prints +/* + printf("Alpha To:\n\r"); + for (i=0; i < sizeof(DTYPE)*(rs->nn+1); i++) + printf("0x%2x,", rs->alpha_to[i]); + printf("\n\r"); + + printf("Index Of:\n\r"); + for (i=0; i < sizeof(DTYPE)*(rs->nn+1); i++) + printf("0x%2x,", rs->index_of[i]); + printf("\n\r"); + + printf("GenPoly:\n\r"); + for (i = 0; i <= nroots; i++) + printf("0x%2x,", rs->genpoly[i]); + printf("\n\r"); +*/ + return rs; +} + + +// TEMPORARY!!! +// FIXME: We already have multiple copies of this. +// Consolidate them into one somewhere. + +void fx_hex_dump (unsigned char *p, int len) +{ + int n, i, offset; + + offset = 0; + while (len > 0) { + n = len < 16 ? len : 16; + dw_printf (" %03x: ", offset); + for (i=0; i. +// + + + +/******************************************************************************** + * + * File: fx25_rec.c + * + * Purpose: Extract FX.25 codeblocks from a stream of bits and process them. + * + *******************************************************************************/ + +#include "direwolf.h" + +#include +#include +#include +#include +#include +//#if __WIN32__ +//#include +//#endif + +#include "fx25.h" + +#include "fcs_calc.h" +#include "textcolor.h" +#include "multi_modem.h" +#include "demod.h" + +struct fx_context_s { + + enum { FX_TAG=0, FX_DATA, FX_CHECK } state; + uint64_t accum; // Accumulate bits for matching to correlation tag. + int ctag_num; // Correlation tag number, CTAG_MIN to CTAG_MAX if approx. match found. + int k_data_radio; // Expected size of "data" sent over radio. + int coffs; // Starting offset of the check part. + int nroots; // Expected number of check bytes. + int dlen; // Accumulated length in "data" below. + int clen; // Accumulated length in "check" below. + unsigned char imask; // Mask for storing a bit. + unsigned char block[FX25_BLOCK_SIZE+1]; +}; + +static struct fx_context_s *fx_context[MAX_CHANS][MAX_SUBCHANS][MAX_SLICERS]; + +static void process_rs_block (int chan, int subchan, int slice, struct fx_context_s *F); + +static int my_unstuff (int chan, int subchan, int slice, unsigned char * restrict pin, int ilen, unsigned char * restrict frame_buf); + +//#define FXTEST 1 // Define for standalone test application. + // It expects to find files fx01.dat, fx02.dat, ..., fx0b.dat/ + +#if FXTEST +static int fx25_test_count = 0; + +int main () +{ + fx25_init(3); + + for (int i = CTAG_MIN; i <= CTAG_MAX; i++) { + + char fname[32]; + snprintf (fname, sizeof(fname), "fx%02x.dat", i); + FILE *fp = fopen(fname, "rb"); + if (fp == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\n"); + dw_printf ("****** Could not open %s ******\n", fname); + dw_printf ("****** Did you generate the test files first? ******\n"); + exit (EXIT_FAILURE); + } + +//#if 0 // reminder for future if reading from stdin. +//#if __WIN32__ +// // So 0x1a byte does not signal EOF. +// _setmode(_fileno(stdin), _O_BINARY); +//#endif +// fp = stdin; +//#endif + unsigned char ch; + while (fread(&ch, 1, 1, fp) == 1) { + for (unsigned char imask = 0x01; imask != 0; imask <<=1) { + fx25_rec_bit (0, 0, 0, ch & imask); + } + } + fclose (fp); + } + + if (fx25_test_count == 11) { + text_color_set(DW_COLOR_REC); + dw_printf ("\n"); + dw_printf ("\n"); + dw_printf ("\n"); + dw_printf ("***** FX25 unit test Success - all tests passed. *****\n"); + exit (EXIT_SUCCESS); + } + text_color_set(DW_COLOR_ERROR); + dw_printf ("\n"); + dw_printf ("\n"); + dw_printf ("***** FX25 unit test FAILED. Only %d/11 tests passed. *****\n", fx25_test_count); + exit (EXIT_SUCCESS); + +} // end main + +#endif // FXTEST + + + +/*********************************************************************************** + * + * Name: fx25_rec_bit + * + * Purpose: Extract FX.25 codeblocks from a stream of bits. + * In a completely integrated AX.25 / FX.25 receive system, + * this would see the same bit stream as hdlc_rec_bit. + * + * Inputs: chan - Channel number. + * + * subchan - This allows multiple demodulators per channel. + * + * slice - Allows multiple slicers per demodulator (subchannel). + * + * dbit - Data bit after NRZI and any descrambling. + * Any non-zero value is logic '1'. + * + * Description: This is called once for each received bit. + * For each valid frame, 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. + * + ***********************************************************************************/ + +#define FENCE 0x55 // to detect buffer overflow. + +void fx25_rec_bit (int chan, int subchan, int slice, int dbit) +{ + +// Allocate context blocks only as needed. + + struct fx_context_s *F = fx_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 = fx_context[chan][subchan][slice] = (struct fx_context_s *)malloc(sizeof (struct fx_context_s)); + assert (F != NULL); + memset (F, 0, sizeof(struct fx_context_s)); + } + +// State machine to identify correlation tag then gather appropriate number of data and check bytes. + + switch (F->state) { + case FX_TAG: + F->accum >>= 1; + if (dbit) F->accum |= 1LL << 63; + int c = fx25_tag_find_match (F->accum); + if (c >= CTAG_MIN && c <= CTAG_MAX) { + + F->ctag_num = c; + F->k_data_radio = fx25_get_k_data_radio (F->ctag_num); + F->nroots = fx25_get_nroots (F->ctag_num); + F->coffs = fx25_get_k_data_rs (F->ctag_num); + assert (F->coffs == FX25_BLOCK_SIZE - F->nroots); + + if (fx25_get_debug() >= 2) { + text_color_set(DW_COLOR_INFO); + dw_printf ("FX.25[%d.%d]: Matched correlation tag 0x%02x with %d bit errors. Expecting %d data & %d check bytes.\n", + chan, slice, // ideally subchan too only if applicable + c, + __builtin_popcountll(F->accum ^ fx25_get_ctag_value(c)), + F->k_data_radio, F->nroots); + } + + F->imask = 0x01; + F->dlen = 0; + F->clen = 0; + memset (F->block, 0, sizeof(F->block)); + F->block[FX25_BLOCK_SIZE] = FENCE; + F->state = FX_DATA; + } + break; + + case FX_DATA: + if (dbit) F->block[F->dlen] |= F->imask; + F->imask <<= 1; + if (F->imask == 0) { + F->imask = 0x01; + F->dlen++; + if (F->dlen >= F->k_data_radio) { + F->state = FX_CHECK; + } + } + break; + + case FX_CHECK: + if (dbit) F->block[F->coffs + F->clen] |= F->imask; + F->imask <<= 1; + if (F->imask == 0) { + F->imask = 0x01; + F->clen++; + if (F->clen >= F->nroots) { + + process_rs_block (chan, subchan, slice, F); // see below + + F->ctag_num = -1; + F->accum = 0; + F->state = FX_TAG; + } + } + break; + } +} + + + +/*********************************************************************************** + * + * Name: fx25_rec_busy + * + * Purpose: Is FX.25 reception currently in progress? + * + * Inputs: chan - Channel number. + * + * Returns: True if currently in progress for the specified channel. + * + * Description: This is required for duplicate removal. One channel and can have + * multiple demodulators (called subchannels) running in parallel. + * Each of them can have multiple slicers. Duplicates need to be + * removed. Normally a delay of a couple bits (or more accurately + * symbols) was fine because they all took about the same amount of time. + * Now, we can have an additional delay of up to 64 check bytes and + * some filler in the data portion. We can't simply wait that long. + * With normal AX.25 a couple frames can come and go during that time. + * We want to delay the duplicate removal while FX.25 block reception + * is going on. + * + ***********************************************************************************/ + +int fx25_rec_busy (int chan) +{ + assert (chan >= 0 && chan < MAX_CHANS); + + // This could be a litle faster if we knew number of + // subchannels and slicers but it is probably insignificant. + + for (int i = 0; i < MAX_SUBCHANS; i++) { + for (int j = 0; j < MAX_SLICERS; j++) { + if (fx_context[chan][i][j] != NULL) { + if (fx_context[chan][i][j]->state != FX_TAG) { + return (1); + } + } + } + } + return (0); + +} // end fx25_rec_busy + + + +/*********************************************************************************** + * + * Name: process_rs_block + * + * Purpose: After the correlation tag was detected and the appropriate number + * of data and check bytes are accumulated, this performs the processing + * + * Inputs: chan, subchan, slice + * + * F->ctag_num - Correlation tag number (index into table) + * + * F->dlen - Number of "data" bytes. + * + * F->clen - Number of "check" bytes" + * + * F->block - Codeblock. Always 255 total bytes. + * Anything left over after data and check + * bytes is filled with zeros. + * + * <- - - - - - - - - - - 255 bytes total - - - - - - - - -> + * +-----------------------+---------------+---------------+ + * | dlen bytes "data" | zero fill | check bytes | + * +-----------------------+---------------+---------------+ + * + * Description: Use Reed-Solomon decoder to fix up any errors. + * Extract the AX.25 frame from the corrected data. + * + ***********************************************************************************/ + +static void process_rs_block (int chan, int subchan, int slice, struct fx_context_s *F) +{ + if (fx25_get_debug() >= 3) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("FX.25[%d.%d]: Received RS codeblock.\n", chan, slice); + fx_hex_dump (F->block, FX25_BLOCK_SIZE); + } + assert (F->block[FX25_BLOCK_SIZE] == FENCE); + + int derrlocs[FX25_MAX_CHECK]; // Half would probably be OK. + struct rs *rs = fx25_get_rs(F->ctag_num); + + int derrors = DECODE_RS(rs, F->block, derrlocs, 0); + + if (derrors >= 0) { // -1 for failure. >= 0 for success, number of bytes corrected. + + if (fx25_get_debug() >= 2) { + text_color_set(DW_COLOR_INFO); + if (derrors == 0) { + dw_printf ("FX.25[%d.%d]: FEC complete with no errors.\n", chan, slice); + } + else { + dw_printf ("FX.25[%d.%d]: FEC complete, fixed %2d errors in byte positions:", chan, slice, derrors); + for (int k = 0; k < derrors; k++) { + dw_printf (" %d", derrlocs[k]); + } + dw_printf ("\n"); + } + } + + unsigned char frame_buf[FX25_MAX_DATA+1]; // Out must be shorter than input. + int frame_len = my_unstuff (chan, subchan, slice, F->block, F->dlen, frame_buf); + + if (frame_len >= 14 + 1 + 2) { // Minimum length: Two addresses & control & FCS. + + unsigned short actual_fcs = frame_buf[frame_len-2] | (frame_buf[frame_len-1] << 8); + unsigned short expected_fcs = fcs_calc (frame_buf, frame_len - 2); + if (actual_fcs == expected_fcs) { + + if (fx25_get_debug() >= 3) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("FX.25[%d.%d]: Extracted AX.25 frame:\n", chan, slice); + fx_hex_dump (frame_buf, frame_len); + } + +#if FXTEST + fx25_test_count++; +#else + alevel_t alevel = demod_get_audio_level (chan, subchan); + + multi_modem_process_rec_frame (chan, subchan, slice, frame_buf, frame_len - 2, alevel, derrors, 1); /* len-2 to remove FCS. */ + +#endif + } else { + // Most likely cause is defective sender software. + text_color_set(DW_COLOR_ERROR); + dw_printf ("FX.25[%d.%d]: Bad FCS for AX.25 frame.\n", chan, slice); + fx_hex_dump (F->block, F->dlen); + fx_hex_dump (frame_buf, frame_len); + } + } + else { + // Most likely cause is defective sender software. + text_color_set(DW_COLOR_ERROR); + dw_printf ("FX.25[%d.%d]: AX.25 frame is shorter than minimum length.\n", chan, slice); + fx_hex_dump (F->block, F->dlen); + fx_hex_dump (frame_buf, frame_len); + } + } + else if (fx25_get_debug() >= 2) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("FX.25[%d.%d]: FEC failed. Too many errors.\n", chan, slice); + } + +} // process_rs_block + + +/*********************************************************************************** + * + * Name: my_unstuff + * + * Purpose: Remove HDLC it stuffing and surrounding flag delimiters. + * + * Inputs: chan, subchan, slice - For error messages. + * + * pin - "data" part of RS codeblock. + * First byte must be HDLC "flag". + * May be followed by additional flags. + * There must be terminating flag but it might not be byte aligned. + * + * ilen - Number of bytes in pin. + * + * Outputs: frame_buf - Frame contents including FCS. + * Bit stuffing is gone so it should be a whole number of bytes. + * + * Returns: Number of bytes in frame_buf, including 2 for FCS. + * This can never be larger than the max "data" size. + * 0 if any error. + * + * Errors: First byte is not not flag. + * Found seven '1' bits in a row. + * Result is not whole number of bytes after removing bit stuffing. + * Trailing flag not found. + * Most likely cause, for all of these, is defective sender software. + * + ***********************************************************************************/ + +static int my_unstuff (int chan, int subchan, int slice, unsigned char * restrict pin, int ilen, unsigned char * restrict frame_buf) +{ + unsigned char pat_det = 0; // Pattern detector. + unsigned char oacc = 0; // Accumulator for a byte out. + int olen = 0; // Number of good bits in oacc. + int frame_len = 0; // Number of bytes accumulated, including CRC. + + if (*pin != 0x7e) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("FX.25[%d.%d] error: Data section did not start with 0x7e.\n", chan, slice); + fx_hex_dump (pin, ilen); + return (0); + } + while (ilen > 0 && *pin == 0x7e) { + ilen--; + pin++; // Skip over leading flag byte(s). + } + + for (int i=0; i>= 1; // Shift the most recent eight bits thru the pattern detector. + pat_det |= dbit << 7; + + if (pat_det == 0xfe) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("FX.25[%d.%d]: Invalid AX.25 frame - Seven '1' bits in a row.\n", chan, slice); + fx_hex_dump (pin, ilen); + return 0; + } + + if (dbit) { + oacc >>= 1; + oacc |= 0x80; + } else { + if (pat_det == 0x7e) { // "flag" pattern - End of frame. + if (olen == 7) { + return (frame_len); // Whole number of bytes in result including CRC + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("FX.25[%d.%d]: Invalid AX.25 frame - Not a whole number of bytes.\n", chan, slice); + fx_hex_dump (pin, ilen); + return (0); + } + } else if ( (pat_det >> 2) == 0x1f ) { + continue; // Five '1' bits in a row, followed by '0'. Discard the '0'. + } + oacc >>= 1; + } + + olen++; + if (olen & 8) { + olen = 0; + frame_buf[frame_len++] = oacc; + } + } + } /* end of loop on all bits in block */ + + text_color_set(DW_COLOR_ERROR); + dw_printf ("FX.25[%d.%d]: Invalid AX.25 frame - Terminating flag not found.\n", chan, slice); + fx_hex_dump (pin, ilen); + + return (0); // Should never fall off the end. + +} // my_unstuff + +// end fx25_rec.c \ No newline at end of file diff --git a/src/fx25_send.c b/src/fx25_send.c new file mode 100644 index 0000000..7435be9 --- /dev/null +++ b/src/fx25_send.c @@ -0,0 +1,336 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2019 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 "fx25.h" +#include "fcs_calc.h" +#include "textcolor.h" +#include "audio.h" +#include "gen_tone.h" + + +//#define FXTEST 1 // To build unit test application. + + +#ifndef FXTEST +static void send_bytes (int chan, unsigned char *b, int count); +static void send_bit (int chan, int b); +#endif +static int stuff_it (unsigned char *in, int ilen, unsigned char *out, int osize); + + +static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "fx25_send_frame" or "???" + + +#if FXTEST +static unsigned char preload[] = { + 'T'<<1, 'E'<<1, 'S'<<1, 'T'<<1, ' '<<1, ' '<<1, 0x60, + 'W'<<1, 'B'<<1, '2'<<1, 'O'<<1, 'S'<<1, 'Z'<<1, 0x63, + 0x03, 0xf0, + 'F', 'o', 'o', '?' , 'B', 'a', 'r', '?' , // '?' causes bit stuffing + 0, 0, 0 // Room for FCS + extra +}; + +int main () +{ + text_color_set(DW_COLOR_ERROR); + dw_printf("fxsend - FX.25 unit test.\n"); + dw_printf("This generates 11 files named fx01.dat, fx02.dat, ..., fx0b.dat\n"); + dw_printf("Run fxrec as second part of test.\n"); + + fx25_init (3); + for (int i = 100 + CTAG_MIN; i <= 100 + CTAG_MAX; i++) { + fx25_send_frame (0, preload, (int)sizeof(preload)-3, i); + } + exit(EXIT_SUCCESS); +} // end main +#endif + + +/*------------------------------------------------------------- + * + * Name: fx25_send_frame + * + * Purpose: Convert HDLC frames to a stream of bits. + * + * Inputs: chan - Audio channel number, 0 = first. + * + * fbuf - Frame buffer address. + * + * flen - Frame length, before bit-stuffing, not including the FCS. + * + * fx_mode - Normally, this would be 16, 32, or 64 for the desired number + * of check bytes. The shortest format, adequate for the + * required data length will be picked automatically. + * 0x01 thru 0x0b may also be specified for a specific format + * but this is expected to be mostly for testing, not normal + * operation. + * + * Outputs: Bits are shipped out by calling tone_gen_put_bit(). + * + * Returns: Number of bits sent including "flags" and the + * stuffing bits. + * The required time can be calculated by dividing this + * number by the transmit rate of bits/sec. + * -1 is returned for failure. + * + * Description: Generate an AX.25 frame in the usual way then wrap + * it inside of the FX.25 correlation tag and check bytes. + * + * 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: If something goes wrong, return -1 and the caller should + * fallback to sending normal AX.25. + * + * This could happen if the frame is too large. + * + *--------------------------------------------------------------*/ + +int fx25_send_frame (int chan, unsigned char *fbuf, int flen, int fx_mode) +{ + if (fx25_get_debug() >= 3) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("------\n"); + dw_printf ("FX.25[%d] send frame: FX.25 mode = %d\n", chan, fx_mode); + fx_hex_dump (fbuf, flen); + } + + number_of_bits_sent[chan] = 0; + + // Append the FCS. + + int fcs = fcs_calc (fbuf, flen); + fbuf[flen++] = fcs & 0xff; + fbuf[flen++] = (fcs >> 8) & 0xff; + + // Add bit-stuffing. + + unsigned char data[FX25_MAX_DATA+1]; + const unsigned char fence = 0xaa; + data[FX25_MAX_DATA] = fence; + + int dlen = stuff_it(fbuf, flen, data, FX25_MAX_DATA); + + assert (data[FX25_MAX_DATA] == fence); + if (dlen < 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("FX.25[%d]: Frame length of %d + overhead is too large to encode.\n", chan, flen); + return (-1); + } + + // Pick suitable correlation tag depending on + // user's preference, for number of check bytes, + // and the data size. + + int ctag_num = fx25_pick_mode (fx_mode, dlen); + + if (ctag_num < CTAG_MIN || ctag_num > CTAG_MAX) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("FX.25[%d]: Could not find suitable format for requested %d and data length %d.\n", chan, fx_mode, dlen); + return (-1); + } + + uint64_t ctag_value = fx25_get_ctag_value (ctag_num); + + // Zero out part of data which won't be transmitted. + // It should all be filled by extra HDLC "flag" patterns. + + int k_data_radio = fx25_get_k_data_radio (ctag_num); + int k_data_rs = fx25_get_k_data_rs (ctag_num); + int shorten_by = FX25_MAX_DATA - k_data_radio; + if (shorten_by > 0) { + memset (data + k_data_radio, 0, shorten_by); + } + + // Compute the check bytes. + + unsigned char check[FX25_MAX_CHECK+1]; + check[FX25_MAX_CHECK] = fence; + struct rs *rs = fx25_get_rs (ctag_num); + + assert (k_data_rs + NROOTS == NN); + + ENCODE_RS(rs, data, check); + assert (check[FX25_MAX_CHECK] == fence); + + if (fx25_get_debug() >= 3) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("FX.25[%d]: transmit %d data bytes, ctag number 0x%02x\n", chan, k_data_radio, ctag_num); + fx_hex_dump (data, k_data_radio); + dw_printf ("FX.25[%d]: transmit %d check bytes:\n", chan, NROOTS); + fx_hex_dump (check, NROOTS); + dw_printf ("------\n"); + } + +#if FXTEST + // Standalone text application. + + unsigned char flags[16] = { 0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e }; + char fname[32]; + snprintf (fname, sizeof(fname), "fx%02x.dat", ctag_num); + FILE *fp = fopen(fname, "wb"); + fwrite (flags, sizeof(flags), 1, fp); + //fwrite ((unsigned char *)(&ctag_value), sizeof(ctag_value), 1, fp); // No - assumes little endian. + for (int k = 0; k < 8; k++) { + unsigned char b = (ctag_value >> (k * 8)) & 0xff; // Should be portable to big endian too. + fwrite (&b, 1, 1, fp); + } +#if 1 + for (int j = 8; j < 16; j++) { // Introduce errors. + data[j] = ~ data[j]; + } +#endif + fwrite (data, k_data_radio, 1, fp); + fwrite (check, NROOTS, 1, fp); + fwrite (flags, sizeof(flags), 1, fp); + fflush(fp); + fclose (fp); +#else + // Normal usage. Send bits to modulator. + +// Temp hack for testing. Corrupt first 8 bytes. +// for (int j = 0; j < 16; j++) { +// data[j] = ~ data[j]; +// } + + for (int k = 0; k < 8; k++) { + unsigned char b = (ctag_value >> (k * 8)) & 0xff; + send_bytes (chan, &b, 1); + } + send_bytes (chan, data, k_data_radio); + send_bytes (chan, check, NROOTS); +#endif + + return (number_of_bits_sent[chan]); +} + + +#ifndef FXTEST + +static void send_bytes (int chan, unsigned char *b, int count) +{ + for (int j = 0; j < count; j++) { + unsigned char x = b[j]; + for (int k = 0; k < 8; k++) { + send_bit (chan, x & 0x01); + x >>= 1; + } + } +} + +/* + * NRZI encoding. + * data 1 bit -> no change. + * data 0 bit -> invert signal. + */ +static void send_bit (int chan, int b) +{ + static int output[MAX_CHANS]; + + if (b == 0) { + output[chan] = ! output[chan]; + } + tone_gen_put_bit (chan, output[chan]); + number_of_bits_sent[chan]++; +} +#endif // FXTEST + + +/*------------------------------------------------------------- + * + * Name: stuff_it + * + * Purpose: Perform HDLC bit-stuffing and add "flag" octets in + * preparation for the RS encoding. + * + * Inputs: in - Frame, including FCS, in. + * + * ilen - Number of bytes in. + * + * osize - Size of out area. + * + * Outputs: out - Location to receive result. + * + * Returns: Number of bytes needed in output area including one trailing flag. + * -1 if it won't fit. + * + * Description: Convert to stream of bits including: + * start flag + * bit stuffed data, including FCS + * end flag + * Fill remainder with flag octets which might not be on byte boundaries. + * + *--------------------------------------------------------------*/ + +#define put_bit(value) { \ + if (olen >= osize) return(-1); \ + if (value) out[olen>>3] |= 1 << (olen & 0x7); \ + olen++; \ + } + +static int stuff_it (unsigned char *in, int ilen, unsigned char *out, int osize) +{ + const unsigned char flag = 0x7e; + int ret = -1; + memset (out, 0, osize); + out[0] = flag; + int olen = 8; // Number of bits in output. + osize *= 8; // Now in bits rather than bytes. + int ones = 0; + + for (int i = 0; i < ilen; i++) { + for (unsigned char imask = 1; imask != 0; imask <<= 1) { + int v = in[i] & imask; + put_bit(v); + if (v) { + ones++; + if (ones == 5) { + put_bit(0); + ones = 0; + } + } + else { + ones = 0; + } + } + } + for (unsigned char imask = 1; imask != 0; imask <<= 1) { + put_bit(flag & imask); + } + ret = (olen + 7) / 8; // Includes any partial byte. + + unsigned char imask = 1; + while (olen < osize) { + put_bit( flag & imask); + imask = (imask << 1) | (imask >> 7); // Rotate. + } + + return (ret); + +} // end stuff_it + +// end fx25_send.c \ No newline at end of file diff --git a/gen_packets.c b/src/gen_packets.c similarity index 90% rename from gen_packets.c rename to src/gen_packets.c index 62133a9..b097790 100644 --- a/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 John Langner, WB2OSZ +// Copyright (C) 2011, 2013, 2014, 2015, 2016, 2019 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 @@ -75,6 +75,7 @@ #include "textcolor.h" #include "morse.h" #include "dtmf.h" +#include "fx25.h" /* Own random number generator so we can get */ @@ -116,6 +117,11 @@ static void send_packet (char *str) } else { pp = ax25_from_text (str, 1); + if (pp == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\"%s\" is not valid TNC2 monitoring format.\n", str); + return; + } flen = ax25_pack (pp, fbuf); for (c=0; c= MIN_SAMPLES_PER_SEC && modem.adev[0].samples_per_sec <= MAX_SAMPLES_PER_SEC); - if (experiment) { - int chan = 0; - int n; - - // 6 cycles of 1800 Hz. - for (n=0; n<8; n++) { - tone_gen_put_bit (chan, 0); - } - - // Shift 90 - tone_gen_put_bit (chan, 0); - tone_gen_put_bit (chan, 1); - - // Shift 90 - tone_gen_put_bit (chan, 0); - tone_gen_put_bit (chan, 1); - - // Shift 90 - tone_gen_put_bit (chan, 0); - tone_gen_put_bit (chan, 1); - - // Shift 90 - tone_gen_put_bit (chan, 0); - tone_gen_put_bit (chan, 1); - - // Shift 180 - tone_gen_put_bit (chan, 1); - tone_gen_put_bit (chan, 1); - - // Shift 270 - tone_gen_put_bit (chan, 1); - tone_gen_put_bit (chan, 0); - - // Shift 0 - tone_gen_put_bit (chan, 0); - tone_gen_put_bit (chan, 0); - - // Shift 0 - tone_gen_put_bit (chan, 0); - tone_gen_put_bit (chan, 0); - - - // HDLC flag - six 1 in a row. - tone_gen_put_bit (chan, 0); - tone_gen_put_bit (chan, 1); - tone_gen_put_bit (chan, 1); - tone_gen_put_bit (chan, 1); - tone_gen_put_bit (chan, 1); - tone_gen_put_bit (chan, 1); - tone_gen_put_bit (chan, 1); - tone_gen_put_bit (chan, 0); - - tone_gen_put_bit (chan, 0); // reverse even/odd position - - tone_gen_put_bit (chan, 0); - tone_gen_put_bit (chan, 1); - tone_gen_put_bit (chan, 1); - tone_gen_put_bit (chan, 1); - tone_gen_put_bit (chan, 1); - tone_gen_put_bit (chan, 1); - tone_gen_put_bit (chan, 1); - tone_gen_put_bit (chan, 0); - - tone_gen_put_bit (chan, 0); - - // Shift 0 - tone_gen_put_bit (chan, 0); - tone_gen_put_bit (chan, 0); - - // Shift 0 - tone_gen_put_bit (chan, 0); - tone_gen_put_bit (chan, 0); - - audio_file_close (); - return (EXIT_SUCCESS); - } - /* * Get user packets(s) from file or stdin if specified. * "-n" option is ignored in this case. @@ -685,6 +667,9 @@ static void usage (char **argv) dw_printf (" -b Bits / second for data. Default is %d.\n", DEFAULT_BAUD); dw_printf (" -B Bits / second for data. Proper modem selected for 300, 1200, 2400, 4800, 9600.\n"); dw_printf (" -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 (" -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/gen_tone.c b/src/gen_tone.c similarity index 74% rename from gen_tone.c rename to src/gen_tone.c index 0d7c255..68f72bc 100644 --- a/gen_tone.c +++ b/src/gen_tone.c @@ -1,10 +1,7 @@ -//#define DEBUG 1 -//#define DEBUG2 1 - // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2014, 2015, 2016 John Langner, WB2OSZ +// Copyright (C) 2011, 2014, 2015, 2016, 2019 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 @@ -106,59 +103,9 @@ static int bit_count[MAX_CHANS]; // Counter incremented for each bit transmitted static int save_bit[MAX_CHANS]; -/* - * The K9NG/G3RUH output originally took a very simple and lazy approach. - * We simply generated a square wave with + or - the desired amplitude. - * This has a couple undesirable properties. - * - * - Transmitting a square wave would splatter into adjacent - * channels of the transmitter doesn't limit the bandwidth. - * - * - The usual sample rate of 44100 is not a multiple of the - * baud rate so jitter would be added to the zero crossings. - * - * Starting in version 1.2, we try to overcome these issues by using - * a higher sample rate, low pass filtering, and down sampling. - * - * What sort of low pass filter would be appropriate? Intuitively, - * we would expect a cutoff frequency somewhere between baud/2 and baud. - * The current values were found with a small amount of trial and - * error for best results. Future improvement is certainly possible. - */ - -/* - * For low pass filtering of 9600 baud data. - */ - -/* Add sample to buffer and shift the rest down. */ -// TODO: Can we have one copy of these in dsp.h? - -static inline void push_sample (float val, float *buff, int size) -{ - memmove(buff+1,buff,(size-1)*sizeof(float)); - buff[0] = val; -} +static int prev_dat[MAX_CHANS]; // Previous data bit. Used for G3RUH style. -/* FIR filter kernel. */ - -static inline float convolve (const float *data, const float *filter, int filter_size) -{ - float sum = 0; - int j; - - for (j=0; jachan[chan].valid) { + if (audio_config_p->achan[chan].medium == MEDIUM_RADIO) { int a = ACHAN2ADEV(chan); @@ -266,7 +213,16 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets) f2_change_per_sample[chan] = f1_change_per_sample[chan]; // Not used. break; - default: + case MODEM_BASEBAND: + case MODEM_SCRAMBLE: + case MODEM_AIS: + + // Tone is half baud. + ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->achan[chan].baud ) + 0.5); + f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].baud * 0.5 * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); + break; + + default: // AFSK ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->achan[chan].baud ) + 0.5); f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); @@ -298,75 +254,6 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets) sine_table[j] = s; } - -/* - * Low pass filter for 9600 baud. - */ - - for (chan = 0; chan < MAX_CHANS; chan++) { - - if (audio_config_p->achan[chan].valid && - (audio_config_p->achan[chan].modem_type == MODEM_SCRAMBLE - || audio_config_p->achan[chan].modem_type == MODEM_BASEBAND)) { - - int a = ACHAN2ADEV(chan); - int samples_per_sec; /* Might be scaled up! */ - int baud; - - /* These numbers were by trial and error. Need more investigation here. */ - - float filter_len_bits = 88 * 9600.0 / (44100.0 * 2.0); - /* Filter length in number of data bits. */ - /* Currently 9.58 */ - - float lpf_baud = 0.8; /* Lowpass cutoff freq as fraction of baud rate */ - - float fc; /* Cutoff frequency as fraction of sampling frequency. */ - -/* - * Normally, we want to generate the same thing whether sending over the air - * or putting it into a file for other testing. - * (There is an important exception. gen_packets can introduce random noise.) - * In this case, we want more aggressive low pass filtering so it looks more like - * what we see coming out of a receiver. - * Specifically, single bits of the same state have considerably reduced amplitude - * below several same values in a row. - */ - - if (gen_packets) { - filter_len_bits = 4; - lpf_baud = 0.55; /* Lowpass cutoff freq as fraction of baud rate */ - } - - samples_per_sec = audio_config_p->adev[a].samples_per_sec * UPSAMPLE; - baud = audio_config_p->achan[chan].baud; - - ticks_per_sample[chan] = (int) ((TICKS_PER_CYCLE / (double)samples_per_sec ) + 0.5); - ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)baud ) + 0.5); - - lp_filter_size[chan] = (int) (( filter_len_bits * (float)samples_per_sec / baud) + 0.5); - - if (lp_filter_size[chan] < 10) { - text_color_set(DW_COLOR_DEBUG); - dw_printf ("gen_tone_init: unexpected, chan %d, lp_filter_size %d < 10\n", chan, lp_filter_size[chan]); - lp_filter_size[chan] = 10; - } - else if (lp_filter_size[chan] > MAX_FILTER_SIZE) { - text_color_set(DW_COLOR_DEBUG); - dw_printf ("gen_tone_init: unexpected, chan %d, lp_filter_size %d > %d\n", chan, lp_filter_size[chan], MAX_FILTER_SIZE); - lp_filter_size[chan] = MAX_FILTER_SIZE; - } - - fc = (float)baud * lpf_baud / (float)samples_per_sec; - - //text_color_set(DW_COLOR_DEBUG); - //dw_printf ("gen_tone_init: chan %d, call gen_lowpass(fc=%.2f, , size=%d, )\n", chan, fc, lp_filter_size[chan]); - - gen_lowpass (fc, lp_filter[chan], lp_filter_size[chan], BP_WINDOW_HAMMING); - - } - } - return (0); } /* end gen_tone_init */ @@ -389,6 +276,13 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets) * * Version 1.4: Attempt to implement 2400 and 4800 bps PSK modes. * + * Version 1.6: For G3RUH, rather than generating square wave and low + * pass filtering, generate the waveform directly. + * This avoids overshoot, ringing, and adding more jitter. + * Alternating bits come out has sine wave of baud/2 Hz. + * + * Version 1.6: MFJ-2400 compatibility for V.26. + * *--------------------------------------------------------------------*/ static const int gray2phase_v26[4] = {0, 1, 3, 2}; @@ -400,8 +294,12 @@ void tone_gen_put_bit (int chan, int dat) int a = ACHAN2ADEV(chan); /* device for channel. */ assert (save_audio_config_p != NULL); - assert (save_audio_config_p->achan[chan].valid); + if (save_audio_config_p->achan[chan].medium != MEDIUM_RADIO) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Invalid channel %d for tone generation.\n", chan); + return; + } if (dat < 0) { /* Hack to test receive PLL recovery. */ @@ -409,6 +307,8 @@ void tone_gen_put_bit (int chan, int dat) dat = 0; } +// TODO: change to switch instead of if if if + if (save_audio_config_p->achan[chan].modem_type == MODEM_QPSK) { int dibit; @@ -429,7 +329,9 @@ void tone_gen_put_bit (int chan, int dat) symbol = gray2phase_v26[dibit]; tone_phase[chan] += symbol * PHASE_SHIFT_90; - + if (save_audio_config_p->achan[chan].v26_alternative == V26_B) { + tone_phase[chan] += PHASE_SHIFT_45; + } bit_count[chan]++; } @@ -469,7 +371,6 @@ void tone_gen_put_bit (int chan, int dat) do { /* until enough audio samples for this symbol. */ int sam; - float fsam; switch (save_audio_config_p->achan[chan].modem_type) { @@ -498,24 +399,19 @@ void tone_gen_put_bit (int chan, int dat) case MODEM_BASEBAND: case MODEM_SCRAMBLE: + case MODEM_AIS: -#if DEBUG2 - text_color_set(DW_COLOR_DEBUG); - dw_printf ("tone_gen_put_bit %d SCR\n", __LINE__); -#endif - fsam = dat ? amp16bit : (-amp16bit); - - /* version 1.2 - added a low pass filter instead of square wave out. */ - - push_sample (fsam, raw[chan], lp_filter_size[chan]); - - resample[chan]++; - if (resample[chan] >= UPSAMPLE) { - - sam = (int) convolve (raw[chan], lp_filter[chan], lp_filter_size[chan]); - resample[chan] = 0; - gen_tone_put_sample (chan, a, sam); + if (dat != prev_dat[chan]) { + tone_phase[chan] += f1_change_per_sample[chan]; } + else { + if (tone_phase[chan] & 0x80000000) + tone_phase[chan] = 0xc0000000; // 270 degrees. + else + tone_phase[chan] = 0x40000000; // 90 degrees. + } + sam = sine_table[(tone_phase[chan] >> 24) & 0xff]; + gen_tone_put_sample (chan, a, sam); break; default: @@ -532,7 +428,10 @@ void tone_gen_put_bit (int chan, int dat) } while (bit_len_acc[chan] < ticks_per_bit[chan]); bit_len_acc[chan] -= ticks_per_bit[chan]; -} + + prev_dat[chan] = dat; // Only needed for G3RUH baseband/scrambled. + +} /* end tone_gen_put_bit */ void gen_tone_put_sample (int chan, int a, int sam) { @@ -547,10 +446,21 @@ void gen_tone_put_sample (int chan, int a, int sam) { assert (save_audio_config_p->adev[a].bits_per_sample == 16 || save_audio_config_p->adev[a].bits_per_sample == 8); - // TODO: Should print message telling user to reduce output level. + // Bad news if we are clipping and distorting the signal. + // We are using the full range. + // Too late to change now because everyone would need to recalibrate their + // transmit audio level. - if (sam < -32767) sam = -32767; - else if (sam > 32767) sam = 32767; + if (sam < -32767) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Warning: Audio sample %d clipped to -32767.\n", sam); + sam = -32767; + } + else if (sam > 32767) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Warning: Audio sample %d clipped to +32767.\n", sam); + sam = 32767; + } if (save_audio_config_p->adev[a].num_channels == 1) { diff --git a/gen_tone.h b/src/gen_tone.h similarity index 100% rename from gen_tone.h rename to src/gen_tone.h diff --git a/grm_sym.h b/src/grm_sym.h similarity index 100% rename from grm_sym.h rename to src/grm_sym.h diff --git a/hdlc_rec.c b/src/hdlc_rec.c similarity index 58% rename from hdlc_rec.c rename to src/hdlc_rec.c index 15b72c2..6b395be 100644 --- a/hdlc_rec.c +++ b/src/hdlc_rec.c @@ -32,7 +32,9 @@ #include #include #include +#include // uint64_t +//#include "tune.h" #include "demod.h" #include "hdlc_rec.h" #include "hdlc_rec2.h" @@ -43,11 +45,11 @@ #include "multi_modem.h" #include "demod_9600.h" /* for descramble() */ #include "ptt.h" +#include "fx25.h" //#define TEST 1 /* Define for unit testing. */ - //#define DEBUG3 1 /* monitor the data detect signal. */ @@ -100,12 +102,15 @@ struct hdlc_state_s { int frame_len; /* Number of octets in frame_buf. */ /* Should be in range of 0 .. MAX_FRAME_LEN. */ - int data_detect; /* True when HDLC data is detected. */ - /* This will not be triggered by voice or other */ - /* noise or even tones. */ - rrbb_t rrbb; /* Handle for bit array for raw received bits. */ - + + uint64_t eas_acc; /* Accumulate most recent 64 bits received for EAS. */ + + int eas_gathering; /* Decoding in progress. */ + + int eas_plus_found; /* "+" seen, indicating end of geographical area list. */ + + int eas_fields_after_plus; /* Number of "-" characters after the "+". */ }; static struct hdlc_state_s hdlc_state[MAX_CHANS][MAX_SUBCHANS][MAX_SLICERS]; @@ -127,6 +132,9 @@ static int composite_dcd[MAX_CHANS][MAX_SUBCHANS+1]; static int was_init = 0; +static struct audio_s *g_audio_p; + + void hdlc_rec_init (struct audio_s *pa) { int ch, sub, slice; @@ -136,13 +144,14 @@ void hdlc_rec_init (struct audio_s *pa) //dw_printf ("hdlc_rec_init (%p) \n", pa); assert (pa != NULL); - + g_audio_p = pa; + memset (composite_dcd, 0, sizeof(composite_dcd)); for (ch = 0; ch < MAX_CHANS; ch++) { - if (pa->achan[ch].valid) { + if (pa->achan[ch].medium == MEDIUM_RADIO) { num_subchan[ch] = pa->achan[ch].num_subchan; @@ -168,6 +177,227 @@ void hdlc_rec_init (struct audio_s *pa) was_init = 1; } +/* Own copy of random number generator so we can get */ +/* same predictable results on different operating systems. */ +/* TODO: Consolidate multiple copies somewhere. */ + +#define MY_RAND_MAX 0x7fffffff +static int seed = 1; + +static int my_rand (void) { + // Perform the calculation as unsigned to avoid signed overflow error. + seed = (int)(((unsigned)seed * 1103515245) + 12345) & MY_RAND_MAX; + return (seed); +} + + +/*********************************************************************************** + * + * Name: eas_rec_bit + * + * Purpose: Extract EAS trasmissions from a stream of bits. + * + * Inputs: chan - Channel number. + * + * subchan - This allows multiple demodulators per channel. + * + * slice - Allows multiple slicers per demodulator (subchannel). + * + * raw - One bit from the demodulator. + * should be 0 or 1. + * + * future_use - Not implemented yet. PSK already provides it. + * + * + * Description: This is called once for each received bit. + * For each valid transmission, process_rec_frame() + * is called for further processing. + * + ***********************************************************************************/ + +#define PREAMBLE 0xababababababababULL +#define PREAMBLE_ZCZC 0x435a435aababababULL +#define PREAMBLE_NNNN 0x4e4e4e4eababababULL +#define EAS_MAX_LEN 268 // Not including preamble. Up to 31 geographic areas. + + +static void eas_rec_bit (int chan, int subchan, int slice, int raw, int future_use) +{ + struct hdlc_state_s *H; + +/* + * Different state information for each channel / subchannel / slice. + */ + H = &hdlc_state[chan][subchan][slice]; + + //dw_printf ("slice %d = %d\n", slice, raw); + +// Accumulate most recent 64 bits. + + H->eas_acc >>= 1; + if (raw) { + H->eas_acc |= 0x8000000000000000ULL; + } + + int done = 0; + + if (H->eas_acc == PREAMBLE_ZCZC) { + //dw_printf ("ZCZC\n"); + H->olen = 0; + H->eas_gathering = 1; + H->eas_plus_found = 0; + H->eas_fields_after_plus = 0; + strlcpy ((char*)(H->frame_buf), "ZCZC", sizeof(H->frame_buf)); + H->frame_len = 4; + } + else if (H->eas_acc == PREAMBLE_NNNN) { + //dw_printf ("NNNN\n"); + H->olen = 0; + H->eas_gathering = 1; + strlcpy ((char*)(H->frame_buf), "NNNN", sizeof(H->frame_buf)); + H->frame_len = 4; + done = 1; + } + else if (H->eas_gathering) { + H->olen++; + if (H->olen == 8) { + H->olen = 0; + char ch = H->eas_acc >> 56; + H->frame_buf[H->frame_len++] = ch; + H->frame_buf[H->frame_len] = '\0'; + //dw_printf ("frame_buf = %s\n", H->frame_buf); + + // What characters are acceptable? + // Only ASCII is allowed. i.e. the MSB must be 0. + // The examples show only digits but the geographical area can + // contain anything in range of '!' to DEL or CR or LF. + // There are no restrictions listed for the originator and + // examples contain a slash. + // It's not clear if a space can occur in other places. + + if ( ! (( ch >= ' ' && ch <= 0x7f) || ch == '\r' || ch == '\n')) { +//#define DEBUG_E 1 +#ifdef DEBUG_E + dw_printf ("reject %d invalid character = %s\n", slice, H->frame_buf); +#endif + H->eas_gathering = 0; + return; + } + if (H->frame_len > EAS_MAX_LEN) { // FIXME: look for other places with max length +#ifdef DEBUG_E + dw_printf ("reject %d too long = %s\n", slice, H->frame_buf); +#endif + H->eas_gathering = 0; + return; + } + if (ch == '+') { + H->eas_plus_found = 1; + H->eas_fields_after_plus = 0; + } + if (H->eas_plus_found && ch == '-') { + H->eas_fields_after_plus++; + if (H->eas_fields_after_plus == 3) { + done = 1; // normal case + } + } + } + } + + if (done) { +#ifdef DEBUG_E + dw_printf ("frame_buf %d = %s\n", slice, H->frame_buf); +#endif + alevel_t alevel = demod_get_audio_level (chan, subchan); + multi_modem_process_rec_frame (chan, subchan, slice, H->frame_buf, H->frame_len, alevel, 0, 0); + H->eas_gathering = 0; + } + +} // end eas_rec_bit + + +/* + +EAS has no error detection. +Maybe that doesn't matter because we would normally be dealing with a reasonable +VHF FM or TV signal. +Let's see what happens when we intentionally introduce errors. +When some match and others don't, the multislice voting should give preference +to those matching others. + + $ src/atest -P+ -B EAS -e 3e-3 ../../ref-doc/EAS/same.wav + Demodulator profile set to "+" + 96000 samples per second. 16 bits per sample. 1 audio channels. + 2079360 audio bytes in file. Duration = 10.8 seconds. + Fix Bits level = 0 + Channel 0: 521 baud, AFSK 2083 & 1563 Hz, D+, 96000 sample rate / 3. + +case 1: Slice 6 is different than others (EQS vs. EAS) so we want one of the others that match. + Slice 3 has an unexpected character (in 0120u7) so it is a mismatch. + At this point we are not doing validity checking other than all printable characters. + + We are left with 0 & 4 which don't match (012057 vs. 012077). + So I guess we don't have any two that match so it is a toss up. + + reject 7 invalid character = ZCZC-EAS-RWT-0120▒ + reject 5 invalid character = ZCZC-ECW-RWT-012057-012081-012101-012103-012115+003 + frame_buf 6 = ZCZC-EQS-RWT-012057-012081-012101-012103-012115+0030-2780415-WTSP/TV- + frame_buf 4 = ZCZC-EAS-RWT-012077-012081-012101-012103-012115+0030-2780415-WTSP/TV- + frame_buf 3 = ZCZC-EAS-RWT-0120u7-012281-012101-012103-092115+0038-2780415-VTSP/TV- + frame_buf 0 = ZCZC-EAS-RWT-012057-412081-012101-012103-012115+0030-2780415-WTSP/TV- + + DECODED[1] 0:01.313 EAS audio level = 194(106/108) |__||_|__ + [0.0] EAS>APDW16:{DEZCZC-EAS-RWT-012057-412081-012101-012103-012115+0030-2780415-WTSP/TV- + +Case 2: We have two that match so pick either one. + + reject 5 invalid character = ZCZC-EAS-RW▒ + reject 7 invalid character = ZCZC-EAS-RWT-0 + reject 3 invalid character = ZCZC-EAS-RWT-012057-012080-012101-012103-01211 + reject 0 invalid character = ZCZC-EAS-RWT-012057-012081-012101-012103-012115+0030-2780415-W▒ + frame_buf 6 = ZCZC-EAS-RWT-012057-012081-012!01-012103-012115+0030-2780415-WTSP/TV- + frame_buf 1 = ZCZC-EAS-RWT-012057-012081-012101-012103-012115+0030-2780415-WTSP/TV- + + DECODED[2] 0:03.617 EAS audio level = 194(106/108) _|____|__ + [0.1] EAS>APDW16:{DEZCZC-EAS-RWT-012057-012081-012101-012103-012115+0030-2780415-WTSP/TV- + +Case 3: Slice 6 is a mismatch (EAs vs. EAS). + Slice 7 has RST rather than RWT. + 2 & 4 don't match either (012141 vs. 012101). + We have another case where no two match so there is no clear winner. + + + reject 5 invalid character = ZCZC-EAS-RWT-012057-012081-012101-012103-012115+▒ + frame_buf 7 = ZCZC-EAS-RST-012057-012081-012101-012103-012115+0030-2780415-WTSP/TV- + frame_buf 6 = ZCZC-EAs-RWT-012057-012081-012101-012103-012115+0030-2780415-WTSP/TV- + frame_buf 4 = ZCZC-EAS-RWT-112057-012081-012101-012103-012115+0030-2780415-WTSP/TV- + frame_buf 2 = ZCZC-EAS-RWT-012057-012081-012141-012103-012115+0030-2780415-WTSP/TV- + + DECODED[3] 0:05.920 EAS audio level = 194(106/108) __|_|_||_ + [0.2] EAS>APDW16:{DEZCZC-EAS-RWT-012057-012081-012141-012103-012115+0030-2780415-WTSP/TV- + +Conclusions: + + (1) The existing algorithm gives a higher preference to those frames matching others. + We didn't see any cases here where that would be to our advantage. + + (2) A partial solution would be more validity checking. (i.e. non-digit where + digit is expected.) But wait... We might want to keep it for consideration: + + (3) If I got REALLY ambitious, some day, we could compare all of them one column + at a time and take the most popular (and valid for that column) character and + use all of the most popular characters. Better yet, at the bit level. + +Of course this is probably all overkill because we would normally expect to have pretty +decent signals. The designers didn't even bother to add any sort of checksum for error checking. + +The random errors injected are also not realistic. Actual noise would probably wipe out the +same bit(s) for all of the slices. + +The protocol specification suggests comparing all 3 transmissions and taking the best 2 out of 3. +I think that would best be left to an external application and we just concentrate on being +a good modem here and providing a result when it is received. + +*/ /*********************************************************************************** @@ -188,6 +418,7 @@ void hdlc_rec_init (struct audio_s *pa) * is_scrambled - Is the data scrambled? * * descram_state - Current descrambler state. (not used - remove) + * Not so fast - plans to add new parameter. PSK already provides it. * * * Description: This is called once for each received bit. @@ -196,8 +427,6 @@ void hdlc_rec_init (struct audio_s *pa) * ***********************************************************************************/ -// TODO: int not_used_remove - void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled, int not_used_remove) { @@ -212,11 +441,34 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled, assert (slice >= 0 && slice < MAX_SLICERS); +// -e option can be used to artificially introduce the desired +// Bit Error Rate (BER) for testing. + + if (g_audio_p->recv_ber != 0) { + double r = (double)my_rand() / (double)MY_RAND_MAX; // calculate as double to preserve all 31 bits. + if (g_audio_p->recv_ber > r) { + +// FIXME +//text_color_set(DW_COLOR_DEBUG); +//dw_printf ("hdlc_rec_bit randomly clobber bit, ber = %.6f\n", g_audio_p->recv_ber); + + raw = ! raw; + } + } + +// EAS does not use HDLC. + + if (g_audio_p->achan[chan].modem_type == MODEM_EAS) { + eas_rec_bit (chan, subchan, slice, raw, not_used_remove); + return; + } + /* * Different state information for each channel / subchannel / slice. */ H = &hdlc_state[chan][subchan][slice]; + /* * Using NRZI encoding, * A '0' bit is represented by an inversion since previous bit. @@ -230,13 +482,22 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled, dbit = (descram == H->prev_descram); H->prev_descram = descram; - H->prev_raw = raw; } + H->prev_raw = raw; + } else { dbit = (raw == H->prev_raw); + H->prev_raw = raw; } +// After BER insertion, NRZI, and any descrambling, feed into FX.25 decoder as well. +// Don't waste time on this if AIS. EAS does not get this far. + + if (g_audio_p->achan[chan].modem_type != MODEM_AIS) { + fx25_rec_bit (chan, subchan, slice, dbit); + } + /* * Octets are sent LSB first. * Shift the most recent 8 bits thru the pattern detector. @@ -251,94 +512,6 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled, H->flag4_det |= 0x80000000; } - -/* - * "Data Carrier detect" function based on data patterns rather than - * audio signal strength. - * - * Idle time, at beginning of transmission should be filled - * with the special "flag" characters. - * - * Idle time of all zero bits (alternating tones at maximum rate) - * has also been observed rarely. It is easy to understand the reasoning. - * The tones alternate at the maximum rate, making it symmetrical and providing - * the most opportunity for the PLL to lock on to the edges. - * It also violates the published protocol spec. - * - * Recognize zero(s) followed by a single flag even though it violates the spec. - * - * It has been reported that the TinyTrak4 does this. - * https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/1207 - */ - -/* - * Originally, this looked for 4 flags in a row or 3 zeros and a flag. - * Is that too fussy? - * Here are the numbers of start of DCD for our favorite Track 2 test. - * - * 7e7e7e7e 504 7e000000 32 - * 7e7e7e-- 513 7e0000-- 33 - * 7e7e---- 555 7e00---- 42 - * 7e------ 2088 - * - * I don't think we want to look for a single flag because that would - * make DCD too sensitive to noise and it would interfere with waiting for a - * clear channel to transmit. Even a two byte match causes a lot of flickering - * when listening to live signals. Let's try 3 and see how that works out. - */ - - - //if (H->flag4_det == 0x7e7e7e7e) { - if ((H->flag4_det & 0xffffff00) == 0x7e7e7e00) { - //if ((H->flag4_det & 0xffff0000) == 0x7e7e0000) { - - if ( ! H->data_detect) { - H->data_detect = 1; - dcd_change (chan, subchan, slice, 1); - } - } - //else if (H->flag4_det == 0x7e000000) { - else if ((H->flag4_det & 0xffffff00) == 0x7e000000) { - //else if ((H->flag4_det & 0xffff0000) == 0x7e000000) { - - if ( ! H->data_detect) { - H->data_detect = 1; - dcd_change (chan, subchan, slice, 1); - } - } - - -/* - * Loss of signal should result in lack of transitions. - * (all '1' bits) for at least a little while. - * - * When this was written, I was only concerned about 1200 baud. - * For 9600, added later, there is a (de)scrambling function. - * So if there is no change in the signal, we would get pseudo random bits here. - * Maybe we need to put in another check earlier so DCD is not held on too long - * after loss of signal for 9600. - * No, that would not be a good idea. part of a valid frame, when scrambled, - * could have seven or more "1" bits in a row. - * Needs more study. - */ - - - if (H->pat_det == 0xff) { - - if ( H->data_detect ) { - H->data_detect = 0; - dcd_change (chan, subchan, slice, 0); - } - } - - -/* - * End of data carrier detect. - * - * The rest is concerned with framing. - */ - - rrbb_append_bit (H->rrbb, raw); if (H->pat_det == 0x7e) { @@ -391,7 +564,7 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled, if (actual_fcs == expected_fcs) { alevel_t alevel = demod_get_audio_level (chan, subchan); - multi_modem_process_rec_frame (chan, subchan, slice, H->frame_buf, H->frame_len - 2, alevel, RETRY_NONE); /* len-2 to remove FCS. */ + multi_modem_process_rec_frame (chan, subchan, slice, H->frame_buf, H->frame_len - 2, alevel, RETRY_NONE, 0); /* len-2 to remove FCS. */ } else { @@ -520,47 +693,10 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled, } } - - -/*------------------------------------------------------------------- - * - * Name: hdlc_rec_gathering - * - * Purpose: Report whether bits are currently being gathered into a frame. - * This is used to influence the PLL inertia. - * The idea is that the PLL should be a little more agreeable to - * synchronize with the incoming data stream when not in a frame - * and resist changing a little more when capturing a frame. - * - * Inputs: chan - * subchan - * slice - * - * Returns: True if we are currently gathering bits. - * In this case we want the PLL to have more inertia. - * - * Discussion: This simply returns the data carrier detect state. - * A couple other variations were tried but turned out to - * be slightly worse. - * - *--------------------------------------------------------------------*/ - -int hdlc_rec_gathering (int chan, int subchan, int slice) -{ - assert (chan >= 0 && chan < MAX_CHANS); - assert (subchan >= 0 && subchan < MAX_SUBCHANS); - assert (slice >= 0 && slice < MAX_SLICERS); - - // Counts from Track 1 & Track 2 - // data_detect 992 988 - // olen>=0 992 985 - // OR-ed 992 985 - - return ( hdlc_state[chan][subchan][slice].data_detect ); - -} /* end hdlc_rec_gathering */ - - +// TODO: Data Carrier Detect (DCD) is now based on DPLL lock +// rather than data patterns found here. +// It would make sense to move the next 2 functions to demod.c +// because this is done at the modem level, rather than HDLC decoder. /*------------------------------------------------------------------- * diff --git a/hdlc_rec.h b/src/hdlc_rec.h similarity index 100% rename from hdlc_rec.h rename to src/hdlc_rec.h diff --git a/hdlc_rec2.c b/src/hdlc_rec2.c similarity index 89% rename from hdlc_rec2.c rename to src/hdlc_rec2.c index a4764f5..e23aaee 100644 --- a/hdlc_rec2.c +++ b/src/hdlc_rec2.c @@ -17,6 +17,7 @@ // + /******************************************************************************** * * File: hdlc_rec2.c @@ -98,13 +99,12 @@ #include "textcolor.h" #include "ax25_pad.h" #include "rrbb.h" -#include "rdq.h" #include "multi_modem.h" #include "dtime_now.h" #include "demod_9600.h" /* for descramble() */ #include "audio.h" /* for struct audio_s */ //#include "ax25_pad.h" /* for AX25_MAX_ADDR_LEN */ - +#include "ais.h" //#define DEBUG 1 //#define DEBUGx 1 @@ -123,6 +123,7 @@ static struct audio_s *save_audio_config_p; #define MAX_FRAME_LEN ((AX25_MAX_PACKET_LEN) + 2) + /* * This is the current state of the HDLC decoder. * @@ -132,7 +133,11 @@ static struct audio_s *save_audio_config_p; * Should have a reset function instead of initializations here. */ -struct hdlc_state_s { +// TODO: Clean up. This is a remnant of splitting hdlc_rec.c into 2 parts. +// This is not the same as hdlc_state_s in hdlc_rec.c +// "2" was added to reduce confusion. Can be trimmed down. + +struct hdlc_state2_s { int prev_raw; /* Keep track of previous bit so */ /* we can look for transitions. */ @@ -582,7 +587,7 @@ inline static char is_sep_bit_modified(int bit_idx, retry_conf_t retry_conf) { static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t alevel, retry_conf_t retry_conf, int passall) { - struct hdlc_state_s H; + struct hdlc_state2_s H2; int blen; /* Block length in bits. */ int i; int raw; /* From demodulator. Should be 0 or 1. */ @@ -594,10 +599,10 @@ static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t int retry_conf_retry = retry_conf.retry; - H.is_scrambled = rrbb_get_is_scrambled (block); - H.prev_descram = rrbb_get_prev_descram (block); - H.lfsr = rrbb_get_descram_state (block); - H.prev_raw = rrbb_get_bit (block, 0); /* Actually last bit of the */ + H2.is_scrambled = rrbb_get_is_scrambled (block); + H2.prev_descram = rrbb_get_prev_descram (block); + H2.lfsr = rrbb_get_descram_state (block); + H2.prev_raw = rrbb_get_bit (block, 0); /* Actually last bit of the */ /* opening flag so we can derive the */ /* first data bit. */ @@ -608,13 +613,13 @@ static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t if ((retry_conf.mode == RETRY_MODE_CONTIGUOUS && is_contig_bit_modified(0, retry_conf)) || (retry_conf.mode == RETRY_MODE_SEPARATED && is_sep_bit_modified(0, retry_conf))) { - H.prev_raw = ! H.prev_raw; + H2.prev_raw = ! H2.prev_raw; } - H.pat_det = 0; - H.oacc = 0; - H.olen = 0; - H.frame_len = 0; + H2.pat_det = 0; + H2.oacc = 0; + H2.olen = 0; + H2.frame_len = 0; blen = rrbb_get_len(block); @@ -646,49 +651,49 @@ static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t * Octets are sent LSB first. * Shift the most recent 8 bits thru the pattern detector. */ - H.pat_det >>= 1; + H2.pat_det >>= 1; /* * Using NRZI encoding, * A '0' bit is represented by an inversion since previous bit. * A '1' bit is represented by no change. - * Note: this code can be factorized with the raw != H.prev_raw code at the cost of processing time + * Note: this code can be factorized with the raw != H2.prev_raw code at the cost of processing time */ int dbit ; - if (H.is_scrambled) { + if (H2.is_scrambled) { int descram; - descram = descramble(raw, &(H.lfsr)); + descram = descramble(raw, &(H2.lfsr)); - dbit = (descram == H.prev_descram); - H.prev_descram = descram; - H.prev_raw = raw; + dbit = (descram == H2.prev_descram); + H2.prev_descram = descram; + H2.prev_raw = raw; } else { - dbit = (raw == H.prev_raw); - H.prev_raw = raw; + dbit = (raw == H2.prev_raw); + H2.prev_raw = raw; } if (dbit) { - H.pat_det |= 0x80; + H2.pat_det |= 0x80; /* Valid data will never have 7 one bits in a row: exit. */ - if (H.pat_det == 0xfe) { + if (H2.pat_det == 0xfe) { #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("try_decode: found abort, i=%d\n", i); #endif return 0; } - H.oacc >>= 1; - H.oacc |= 0x80; + H2.oacc >>= 1; + H2.oacc |= 0x80; } else { /* The special pattern 01111110 indicates beginning and ending of a frame: exit. */ - if (H.pat_det == 0x7e) { + if (H2.pat_det == 0x7e) { #if DEBUGx text_color_set(DW_COLOR_DEBUG); dw_printf ("try_decode: found flag, i=%d\n", i); @@ -703,10 +708,10 @@ static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t * "bit stuffing." */ - } else if ( (H.pat_det >> 2) == 0x1f ) { + } else if ( (H2.pat_det >> 2) == 0x1f ) { continue; } - H.oacc >>= 1; + H2.oacc >>= 1; } /* @@ -714,14 +719,14 @@ static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t * into the frame buffer. */ - H.olen++; + H2.olen++; - if (H.olen & 8) { - H.olen = 0; + if (H2.olen & 8) { + H2.olen = 0; - if (H.frame_len < MAX_FRAME_LEN) { - H.frame_buf[H.frame_len] = H.oacc; - H.frame_len++; + if (H2.frame_len < MAX_FRAME_LEN) { + H2.frame_buf[H2.frame_len] = H2.oacc; + H2.frame_len++; } } @@ -732,10 +737,10 @@ static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t #if DEBUGx text_color_set(DW_COLOR_DEBUG); - dw_printf ("try_decode: olen=%d, frame_len=%d\n", H.olen, H.frame_len); + dw_printf ("try_decode: olen=%d, frame_len=%d\n", H2.olen, H2.frame_len); #endif - if (H.olen == 0 && H.frame_len >= MIN_FRAME_LEN) { + if (H2.olen == 0 && H2.frame_len >= MIN_FRAME_LEN) { unsigned short actual_fcs, expected_fcs; @@ -743,9 +748,9 @@ static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t if (retry_conf.type == RETRY_TYPE_NONE) { int j; text_color_set(DW_COLOR_DEBUG); - dw_printf ("NEW WAY: frame len = %d\n", H.frame_len); - for (j=0; jachan[chan].sanity_test)) { + if (actual_fcs == expected_fcs && save_audio_config_p->achan[chan].modem_type == MODEM_AIS) { + + // Sanity check for AIS. + if (ais_check_length((H2.frame_buf[0] >> 2) & 0x3f, H2.frame_len - 2) == 0) { + multi_modem_process_rec_frame (chan, subchan, slice, H2.frame_buf, H2.frame_len - 2, alevel, retry_conf.retry, 0); /* len-2 to remove FCS. */ + return 1; /* success */ + } + else { + return 0; /* did not pass sanity check */ + } + } + else if (actual_fcs == expected_fcs && + sanity_check (H2.frame_buf, H2.frame_len - 2, retry_conf.retry, save_audio_config_p->achan[chan].sanity_test)) { // TODO: Shouldn't be necessary to pass chan, subchan, alevel into // try_decode because we can obtain them from block. @@ -773,7 +789,7 @@ static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t assert (rrbb_get_chan(block) == chan); assert (rrbb_get_subchan(block) == subchan); - multi_modem_process_rec_frame (chan, subchan, slice, H.frame_buf, H.frame_len - 2, alevel, retry_conf.retry); /* len-2 to remove FCS. */ + multi_modem_process_rec_frame (chan, subchan, slice, H2.frame_buf, H2.frame_len - 2, alevel, retry_conf.retry, 0); /* len-2 to remove FCS. */ return 1; /* success */ } else if (passall) { @@ -782,7 +798,7 @@ static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t //text_color_set(DW_COLOR_ERROR); //dw_printf ("ATTEMPTING PASSALL PROCESSING\n"); - multi_modem_process_rec_frame (chan, subchan, slice, H.frame_buf, H.frame_len - 2, alevel, RETRY_MAX); /* len-2 to remove FCS. */ + multi_modem_process_rec_frame (chan, subchan, slice, H2.frame_buf, H2.frame_len - 2, alevel, RETRY_MAX, 0); /* len-2 to remove FCS. */ return 1; /* success */ } else { @@ -807,25 +823,25 @@ failure: text_color_set(DW_COLOR_ERROR); if (crc_failed) dw_printf ("CRC failed\n"); - if (H.olen != 0) - dw_printf ("Bad olen: %d \n", H.olen); - else if (H.frame_len < MIN_FRAME_LEN) { + if (H2.olen != 0) + dw_printf ("Bad olen: %d \n", H2.olen); + else if (H2.frame_len < MIN_FRAME_LEN) { dw_printf ("Frame too small\n"); goto end; } - dw_printf ("FAILURE with frame: frame len = %d\n", H.frame_len); + dw_printf ("FAILURE with frame: frame len = %d\n", H2.frame_len); dw_printf ("\n"); - for (j=0; j>1); + for (j=0; j>1); } dw_printf ("\nORIG\n"); - for (j=0; j 0) { + return (n); + } + text_color_set(DW_COLOR_ERROR); + dw_printf ("Unable to send FX.25. Falling back to regular AX.25.\n"); + } + + 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; diff --git a/hdlc_send.h b/src/hdlc_send.h similarity index 82% rename from hdlc_send.h rename to src/hdlc_send.h index 10d200c..4ebbcc2 100644 --- a/hdlc_send.h +++ b/src/hdlc_send.h @@ -1,7 +1,7 @@ /* hdlc_send.h */ -int hdlc_send_frame (int chan, unsigned char *fbuf, int flen, int bad_fcs); +int hdlc_send_frame (int chan, unsigned char *fbuf, int flen, int bad_fcs, int fx25_xmit_enable); int hdlc_send_flags (int chan, int flags, int finish); diff --git a/igate.c b/src/igate.c similarity index 99% rename from igate.c rename to src/igate.c index 1d73c19..37cd34f 100644 --- a/igate.c +++ b/src/igate.c @@ -32,6 +32,8 @@ * * APRS iGate properties * http://wiki.ham.fi/APRS_iGate_properties + * (now gone but you can find a copy here:) + * https://web.archive.org/web/20120503201832/http://wiki.ham.fi/APRS_iGate_properties * * Notes to iGate developers * https://github.com/hessu/aprsc/blob/master/doc/IGATE-HINTS.md#igates-dropping-duplicate-packets-unnecessarily @@ -209,7 +211,7 @@ int main (int argc, char *argv[]) packet_t pp; memset (&audio_config, 0, sizeof(audio_config)); - audio_config.adev[0].num_chans = 2; + audio_config.adev[0].num_channels = 2; strlcpy (audio_config.achan[0].mycall, "WB2OSZ-1", sizeof(audio_config.achan[0].mycall)); strlcpy (audio_config.achan[1].mycall, "WB2OSZ-2", sizeof(audio_config.achan[0].mycall)); @@ -228,7 +230,7 @@ int main (int argc, char *argv[]) memset (&digi_config, 0, sizeof(digi_config)); - igate_init(&igate_config, &digi_config); + igate_init(&audio_config, &igate_config, &digi_config, 0); while (igate_sock == -1) { SLEEP_SEC(1); @@ -269,7 +271,7 @@ int main (int argc, char *argv[]) SLEEP_SEC (20); text_color_set(DW_COLOR_INFO); dw_printf ("Send received packet\n"); - send_msg_to_server ("W1ABC>APRS:?", strlen("W1ABC>APRS:?"); + send_msg_to_server ("W1ABC>APRS:?", strlen("W1ABC>APRS:?")); } #endif return 0; @@ -466,7 +468,7 @@ void igate_init (struct audio_s *p_audio_config, struct igate_config_s *p_igate_ return; } #else - e = pthread_create (&connect_listen_tid, NULL, connnect_thread, (void *)NULL); + e = pthread_create (&connect_listen_tid, NULL, connnect_thread, NULL); if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("Internal error: Could not create IGate connection thread"); @@ -1536,6 +1538,10 @@ static void * igate_recv_thread (void *arg) * Duplicate removal will drop the original if there is no * corresponding digipeated version. * + * 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. + * *--------------------------------------------------------------------*/ static void satgate_delay_packet (packet_t pp, int chan) @@ -1863,7 +1869,7 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan) * -> Raise the rate limiting value. */ if (ig_to_tx_allow (pp3, to_chan)) { - char radio [500]; + char radio [2400]; packet_t pradio; snprintf (radio, sizeof(radio), "%s>%s%d%d%s:}%s", diff --git a/igate.h b/src/igate.h similarity index 100% rename from igate.h rename to src/igate.h diff --git a/kiss.c b/src/kiss.c similarity index 99% rename from kiss.c rename to src/kiss.c index 8186df6..76cb322 100644 --- a/kiss.c +++ b/src/kiss.c @@ -115,12 +115,8 @@ void kisspt_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int f #include #include #include - -#ifdef __OpenBSD__ #include -#else -#include -#endif + #include "tq.h" #include "ax25_pad.h" diff --git a/kiss.h b/src/kiss.h similarity index 100% rename from kiss.h rename to src/kiss.h diff --git a/kiss_frame.c b/src/kiss_frame.c similarity index 90% rename from kiss_frame.c rename to src/kiss_frame.c index 44d01e8..c087635 100644 --- a/kiss_frame.c +++ b/src/kiss_frame.c @@ -93,6 +93,7 @@ #include "tq.h" #include "xmit.h" #include "version.h" +#include "kissnet.h" /* In server.c. Should probably move to some misc. function file. */ @@ -506,7 +507,7 @@ void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, v * debug - Debug option is selected. * * client - Client app number for TCP KISS. - * Ignored for pseudo termal and serial port. + * Should be -1 for pseudo termal and serial port. * * sendfun - Function to send something to the client application. * "Set Hardware" can send a response. @@ -522,7 +523,7 @@ void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, v void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int)) { - int port; + int port; // Should rename to chan because that's what we use everywhere else. int cmd; packet_t pp; alevel_t alevel; @@ -534,7 +535,9 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int cli { case KISS_CMD_DATA_FRAME: /* 0 = Data Frame */ - /* Special hack - Discard apparently bad data from Linux AX25. */ + if (client >= 0) { + kissnet_copy (kiss_msg, kiss_len, port, cmd, 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 */ @@ -545,27 +548,58 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int cli /* Our current default is a maximum of 6 channels but it is easily */ /* increased by changing one number and recompiling. */ - if (kiss_len > 16 && - (port == 2 || port == 8) && - kiss_msg[1] == 'Q' << 1 && - kiss_msg[2] == 'S' << 1 && - kiss_msg[3] == 'T' << 1 && - kiss_msg[4] == ' ' << 1 && - kiss_msg[15] == 3 && - kiss_msg[16] == 0xcd) { - - if (debug) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Special case - Drop packets which appear to be in error.\n"); - } - return; - } - - /* Verify that the port (channel) number is valid. */ +// Additional information, from Mike Playle, December 2018, for Issue #42 +// +// I came across this the other day with Xastir, and took a quick look. +// The problem is fixable without the kiss_frame.c hack, which doesn't help with Xastir anyway. +// +// Workaround +// +// After the kissattach command, put the interface into CRC mode "none" with a command like this: +// +// # kissparms -c 1 -p radio +// +// Analysis +// +// The source of this behaviour is the kernel's KISS implementation: +// +// https://elixir.bootlin.com/linux/v4.9/source/drivers/net/hamradio/mkiss.c#L489 +// +// It defaults to starting in state CRC_MODE_SMACK_TEST and ending up in mode CRC_NONE +// after the first two packets, which have their framing byte modified by this code in the process. +// It looks to me like deliberate behaviour on the kernel's part. +// +// Setting the CRC mode explicitly before sending any packets stops this state machine from running. +// +// Is this a bug? I don't know - that's up to you! Maybe it would make sense for Direwolf to set +// the CRC mode itself, or to expect this behaviour and ignore these flags on the first packets +// received from the Linux pty. +// +// This workaround seems sound to me, though, so perhaps this is just a documentation issue. - if (port < 0 || port >= MAX_CHANS || ! save_audio_config_p->achan[port].valid) { + +// 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. +// 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. */ + /* Any sort of medium should be OK here. */ + + if (port < 0 || port >= MAX_CHANS || save_audio_config_p->achan[port].medium == MEDIUM_NONE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid transmit channel %d from KISS client app.\n", port); + 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 ("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"); + dw_printf ("sudo /sbin/modprobe -q mkiss crc_force=1\n"); + + dw_printf ("\n"); text_color_set(DW_COLOR_DEBUG); kiss_debug_print (FROM_CLIENT, NULL, kiss_msg, kiss_len); return; diff --git a/kiss_frame.h b/src/kiss_frame.h similarity index 100% rename from kiss_frame.h rename to src/kiss_frame.h diff --git a/kissnet.c b/src/kissnet.c similarity index 87% rename from kissnet.c rename to src/kissnet.c index ee58c43..7b4f1c4 100644 --- a/kissnet.c +++ b/src/kissnet.c @@ -106,17 +106,14 @@ #include #include #include -#ifdef __OpenBSD__ #include -#else -#include -#endif #endif #include #include #include #include +#include #include "tq.h" @@ -163,6 +160,7 @@ static THREAD_F connect_listen_thread (void *arg); static THREAD_F kissnet_listen_thread (void *arg); +static struct misc_config_s *s_misc_config_p; static int kiss_debug = 0; /* Print information flowing from and to client. */ @@ -208,6 +206,8 @@ 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. */ @@ -232,14 +232,14 @@ 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 *)kiss_port, 0, NULL); + connect_listen_th = (HANDLE)_beginthreadex (NULL, 0, connect_listen_thread, (void *)(ptrdiff_t)kiss_port, 0, NULL); if (connect_listen_th == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not create KISS socket connect listening thread\n"); return; } #else - e = pthread_create (&connect_listen_tid, NULL, connect_listen_thread, (void *)(long)kiss_port); + e = pthread_create (&connect_listen_tid, NULL, connect_listen_thread, (void *)(ptrdiff_t)kiss_port); if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("Could not create KISS socket connect listening thread"); @@ -255,14 +255,14 @@ void kissnet_init (struct misc_config_s *mc) for (client = 0; client < MAX_NET_CLIENTS; client++) { #if __WIN32__ - cmd_listen_th[client] = (HANDLE)_beginthreadex (NULL, 0, kissnet_listen_thread, (void*)client, 0, NULL); + cmd_listen_th[client] = (HANDLE)_beginthreadex (NULL, 0, kissnet_listen_thread, (void*)(ptrdiff_t)client, 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 *)(long)client); + e = pthread_create (&(cmd_listen_tid[client]), NULL, kissnet_listen_thread, (void *)(ptrdiff_t)client); if (e != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not create KISS command listening thread for client %d\n", client); @@ -306,10 +306,10 @@ static THREAD_F connect_listen_thread (void *arg) SOCKET listen_sock; WSADATA wsadata; - snprintf (kiss_port_str, sizeof(kiss_port_str), "%d", (int)(long)arg); + snprintf (kiss_port_str, sizeof(kiss_port_str), "%d", (int)(ptrdiff_t)arg); #if DEBUG text_color_set(DW_COLOR_DEBUG); - dw_printf ("DEBUG: kissnet port = %d = '%s'\n", (int)(long)arg, kiss_port_str); + dw_printf ("DEBUG: kissnet port = %d = '%s'\n", (int)(ptrdiff_t)arg, kiss_port_str); #endif err = WSAStartup (MAKEWORD(2,2), &wsadata); if (err != 0) { @@ -426,7 +426,7 @@ static THREAD_F connect_listen_thread (void *arg) struct sockaddr_in sockaddr; /* Internet socket address stuct */ socklen_t sockaddr_size = sizeof(struct sockaddr_in); - int kiss_port = (int)(long)arg; + int kiss_port = (int)(ptrdiff_t)arg; int listen_sock; int bcopt = 1; @@ -654,6 +654,90 @@ void kissnet_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int f } /* end kissnet_send_rec_packet */ +/*------------------------------------------------------------------- + * + * Name: kissnet_copy + * + * 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). + * + * in_len - Number of bytes in above. + * + * chan - Channel. Redundant because it is also in first byte of kiss_msg. + * Not currently used. + * + * cmd - KISS command nybble. Redundant because it is in first byte. + * Should be 0 because I'm expecting this only for data. + * + * from_client - Number of network (TCP) client instance. + * Should be 0, 1, 2, ... + * + * + * Global In: kiss_copy - From misc. configuration. + * This enables the feature. + * + * + * Description: Send message to any attached network KISS clients, other than the one where it came from. + * Enable this by putting KISSCOPY in the configuration file. + * Note that this applies only to network (TCP) KISS clients, not serial port, or pseudo terminal. + * + * + *--------------------------------------------------------------------*/ + + +void kissnet_copy (unsigned char *in_msg, int in_len, int chan, int cmd, 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++) { + + if (send_to != from_client && client_sock[send_to] != -1) { + + 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(); + } +#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; + } +#endif + } // if origin and destination different. + } // loop over all KISS network clients. + } // Feature enabled. + +} /* end kissnet_copy */ + + /*------------------------------------------------------------------- * @@ -725,7 +809,7 @@ static THREAD_F kissnet_listen_thread (void *arg) unsigned char ch; - int client = (int)(long)arg; + int client = (int)(ptrdiff_t)arg; #if DEBUG text_color_set(DW_COLOR_DEBUG); diff --git a/kissnet.h b/src/kissnet.h similarity index 76% rename from kissnet.h rename to src/kissnet.h index a0681b9..ac00752 100644 --- a/kissnet.h +++ b/src/kissnet.h @@ -17,5 +17,7 @@ void kissnet_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int void kiss_net_set_debug (int n); +void kissnet_copy (unsigned char *kiss_msg, int kiss_len, int chan, int cmd, int from_client); + /* end kissnet.h */ diff --git a/kissserial.c b/src/kissserial.c similarity index 100% rename from kissserial.c rename to src/kissserial.c diff --git a/kissserial.h b/src/kissserial.h similarity index 100% rename from kissserial.h rename to src/kissserial.h diff --git a/kissutil.c b/src/kissutil.c similarity index 98% rename from kissutil.c rename to src/kissutil.c index 66a7c15..026a6ea 100644 --- a/kissutil.c +++ b/src/kissutil.c @@ -46,7 +46,7 @@ #else #include -#include +#include #include #include @@ -56,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -65,7 +66,7 @@ #include "textcolor.h" #include "serial_port.h" #include "kiss_frame.h" -#include "sock.h" +#include "dwsock.h" #include "dtime_now.h" #include "audio.h" // for DEFAULT_TXDELAY, etc. #include "dtime_now.h" @@ -285,7 +286,7 @@ int main (int argc, char *argv[]) #if __WIN32__ if (using_tcp) { - tnc_th = (HANDLE)_beginthreadex (NULL, 0, tnc_listen_net, (void *)99, 0, NULL); + tnc_th = (HANDLE)_beginthreadex (NULL, 0, tnc_listen_net, (void *)(ptrdiff_t)99, 0, NULL); } else { tnc_th = (HANDLE)_beginthreadex (NULL, 0, tnc_listen_serial, (void *)99, 0, NULL); @@ -296,10 +297,10 @@ int main (int argc, char *argv[]) } #else if (using_tcp) { - e = pthread_create (&tnc_tid, NULL, tnc_listen_net, (void *)(long)99); + e = pthread_create (&tnc_tid, NULL, tnc_listen_net, (void *)(ptrdiff_t)99); } else { - e = pthread_create (&tnc_tid, NULL, tnc_listen_serial, (void *)(long)99); + e = pthread_create (&tnc_tid, NULL, tnc_listen_serial, (void *)(ptrdiff_t)99); } if (e != 0) { perror("Internal error: Could not create TNC listen thread."); @@ -611,7 +612,7 @@ static void send_to_kiss_tnc (int chan, int cmd, char *data, int dlen) static THREAD_F tnc_listen_net (void *arg) { int err; - char ipaddr_str[SOCK_IPADDR_LEN]; // Text form of IP address. + char ipaddr_str[DWSOCK_IPADDR_LEN]; // Text form of IP address. char data[4096]; int allow_ipv6 = 0; // Maybe someday. int debug = 0; @@ -620,7 +621,7 @@ static THREAD_F tnc_listen_net (void *arg) memset (&kstate, 0, sizeof(kstate)); - err = sock_init (); + err = dwsock_init (); if (err < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Network interface failure. Can't go on.\n"); @@ -633,7 +634,7 @@ static THREAD_F tnc_listen_net (void *arg) // For the IGate we would loop around and try to reconnect if the TNC // goes away. We should probably do the same here. - server_sock = sock_connect (hostname, port, "TCP KISS TNC", allow_ipv6, debug, ipaddr_str); + server_sock = dwsock_connect (hostname, port, "TCP KISS TNC", allow_ipv6, debug, ipaddr_str); if (server_sock == -1) { text_color_set(DW_COLOR_ERROR); @@ -774,7 +775,7 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int cli printf ("ERROR - Invalid KISS data frame from TNC.\n"); } else { - char prefix[100]; // Channel and optional timestamp. + char prefix[120]; // Channel and optional timestamp. // Like [0] or [2 12:34:56] char addrs[AX25_MAX_ADDRS*AX25_MAX_ADDR_LEN]; // Like source>dest,digi,...,digi: diff --git a/latlong.c b/src/latlong.c similarity index 100% rename from latlong.c rename to src/latlong.c diff --git a/latlong.h b/src/latlong.h similarity index 100% rename from latlong.h rename to src/latlong.h diff --git a/ll2utm.c b/src/ll2utm.c similarity index 100% rename from ll2utm.c rename to src/ll2utm.c diff --git a/log.c b/src/log.c similarity index 99% rename from log.c rename to src/log.c index 2894bc3..02aab2a 100644 --- a/log.c +++ b/src/log.c @@ -51,6 +51,10 @@ #include #include +#if __WIN32__ +#include // for _mkdir() +#endif + #include "ax25_pad.h" #include "textcolor.h" #include "decode_aprs.h" diff --git a/log.h b/src/log.h similarity index 100% rename from log.h rename to src/log.h diff --git a/log2gpx.c b/src/log2gpx.c similarity index 98% rename from log2gpx.c rename to src/log2gpx.c index 15b9835..b13d80e 100644 --- a/log2gpx.c +++ b/src/log2gpx.c @@ -325,10 +325,10 @@ static void read_csv(FILE *fp) things[num_things].speed = speed; things[num_things].course = course; things[num_things].alt = alt; - strncpy (things[num_things].time, pisotime, sizeof(things[num_things].time)); - strncpy (things[num_things].name, pname, sizeof(things[num_things].name)); - strncpy (things[num_things].desc, desc, sizeof(things[num_things].desc)); - strncpy (things[num_things].comment, comment, sizeof(things[num_things].comment)); + strlcpy (things[num_things].time, pisotime, sizeof(things[num_things].time)); + strlcpy (things[num_things].name, pname, sizeof(things[num_things].name)); + strlcpy (things[num_things].desc, desc, sizeof(things[num_things].desc)); + strlcpy (things[num_things].comment, comment, sizeof(things[num_things].comment)); num_things++; } @@ -543,4 +543,4 @@ static void process_things (int first, int last) } printf (" %s\n", safe_name); printf (" \n"); -} \ No newline at end of file +} diff --git a/mgn_icon.h b/src/mgn_icon.h similarity index 100% rename from mgn_icon.h rename to src/mgn_icon.h diff --git a/mheard.c b/src/mheard.c similarity index 99% rename from mheard.c rename to src/mheard.c index 37163c5..eff4709 100644 --- a/mheard.c +++ b/src/mheard.c @@ -243,9 +243,9 @@ static void mheard_dump (void) int i; mheard_t *mptr; time_t now = time(NULL); - char stuff[80]; - char rf[16]; // hours:minutes - char is[16]; + char stuff[120]; + char rf[20]; // hours:minutes + char is[20]; char position[40]; mheard_t *station[MAXDUMP]; int num_stations = 0; diff --git a/mheard.h b/src/mheard.h similarity index 100% rename from mheard.h rename to src/mheard.h diff --git a/morse.c b/src/morse.c similarity index 95% rename from morse.c rename to src/morse.c index df0038d..8e709fd 100644 --- a/morse.c +++ b/src/morse.c @@ -312,7 +312,12 @@ static void morse_tone (int chan, int tu, int wpm) { int f1_change_per_sample; // How much to advance phase for each audio sample. - assert (save_audio_config_p->achan[chan].valid); + + if (save_audio_config_p->achan[chan].medium != MEDIUM_RADIO) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Invalid channel %d for sending Morse Code.\n", chan); + return; + } tone_phase = 0; @@ -360,7 +365,11 @@ static void morse_quiet (int chan, int tu, int wpm) { int nsamples; int j; - assert (save_audio_config_p->achan[chan].valid); + if (save_audio_config_p->achan[chan].medium != MEDIUM_RADIO) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Invalid channel %d for sending Morse Code.\n", chan); + return; + } nsamples = (int) ((TIME_UNITS_TO_MS(tu,wpm) * (float)save_audio_config_p->adev[a].samples_per_sec / 1000.) + 0.5); @@ -395,7 +404,11 @@ static void morse_quiet_ms (int chan, int ms) { int nsamples; int j; - assert (save_audio_config_p->achan[chan].valid); + if (save_audio_config_p->achan[chan].medium != MEDIUM_RADIO) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Invalid channel %d for sending Morse Code.\n", chan); + return; + } nsamples = (int) ((ms * (float)save_audio_config_p->adev[a].samples_per_sec / 1000.) + 0.5); diff --git a/morse.h b/src/morse.h similarity index 100% rename from morse.h rename to src/morse.h diff --git a/multi_modem.c b/src/multi_modem.c similarity index 73% rename from multi_modem.c rename to src/multi_modem.c index 5d96c79..c59af07 100644 --- a/multi_modem.c +++ b/src/multi_modem.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2013, 2014, 2015, 2016 John Langner, WB2OSZ +// Copyright (C) 2013, 2014, 2015, 2016, 2019 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 @@ -69,9 +69,20 @@ * different fixup attempts. * Set limit on number of packets in fix up later queue. * + * New in version 1.6: + * + * FX.25. Previously a delay of a couple bits (or more accurately + * symbols) was fine because the decoders took about the same amount of time. + * Now, we can have an additional delay of up to 64 check bytes and + * some filler in the data portion. We can't simply wait that long. + * With normal AX.25 a couple frames can come and go during that time. + * We want to delay the duplicate removal while FX.25 block reception + * is going on. + * *------------------------------------------------------------------*/ //#define DEBUG 1 + #define DIGIPEATER_C #include "direwolf.h" @@ -80,7 +91,7 @@ #include #include #include -#include +#include #include "ax25_pad.h" #include "textcolor.h" @@ -89,6 +100,10 @@ #include "hdlc_rec.h" #include "hdlc_rec2.h" #include "dlq.h" +#include "fx25.h" +#include "version.h" +#include "ais.h" + // Properties of the radio channels. @@ -101,7 +116,12 @@ static struct audio_s *save_audio_config_p; static struct { packet_t packet_p; alevel_t alevel; - retry_t retries; + int is_fx25; // 1 for FX.25, 0 for regular AX.25. + retry_t retries; // For the old "fix bits" strategy, this is the + // number of bits that were modified to get a good CRC. + // It would be 0 to something around 4. + // For FX.25, it is the number of corrected. + // This could be from 0 thru 32. int age; unsigned int crc; int score; @@ -152,7 +172,7 @@ void multi_modem_init (struct audio_s *pa) hdlc_rec_init (save_audio_config_p); for (chan=0; chanachan[chan].valid) { + if (save_audio_config_p->achan[chan].medium == 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__); @@ -169,97 +189,6 @@ void multi_modem_init (struct audio_s *pa) } -#if 0 - -//Add a crc to the end of the queue and returns the numbers of CRC stored in the queue -int crc_queue_append (unsigned int crc, unsigned int chan) { - crc_t plast; -// crc_t plast1; - crc_t pnext; - crc_t new_crc; - - unsigned int nb_crc = 1; - if (chan>=MAX_CHANS) { - return -1; - } - new_crc = (crc_t) malloc (10*sizeof(struct crc_s)); - if (!new_crc) - return -1; - new_crc->crc = crc; - new_crc->nextp = NULL; - if (crc_queue_of_last_to_app[chan] == NULL) { - crc_queue_of_last_to_app[chan] = new_crc; - nb_crc = 1; - } - else { - nb_crc = 2; - plast = crc_queue_of_last_to_app[chan]; - pnext = plast->nextp; - while (pnext != NULL) { - nb_crc++; - plast = pnext; - pnext = pnext->nextp; - } - plast->nextp = new_crc; - } -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf("Out crc_queue_append nb_crc = %d\n", nb_crc); -#endif - return nb_crc; - - -} - -//Remove the crc from the top of the queue -unsigned int crc_queue_remove (unsigned int chan) { - - unsigned int res; -// crc_t plast; -// crc_t pnext; -#if DEBUG - text_color_set(DW_COLOR_DEBUG); - dw_printf("In crc_queue_remove\n"); -#endif - crc_t removed_crc; - if (chan>=MAX_CHANS) { - return 0; - } - removed_crc = crc_queue_of_last_to_app[chan]; - if (removed_crc == NULL) { - return 0; - } - else { - - crc_queue_of_last_to_app[chan] = removed_crc->nextp; - res = removed_crc->crc; - free(removed_crc); - - } - return res; - -} - -unsigned char is_crc_in_queue(unsigned int chan, unsigned int crc) { - crc_t plast; - crc_t pnext; - - if (crc_queue_of_last_to_app[chan] == NULL) { - return 0; - } - else { - plast = crc_queue_of_last_to_app[chan]; - do { - pnext = plast->nextp; - if (plast->crc == crc) { - return 1; - } - plast = pnext; - } while (pnext != NULL); - } - return 0; -} -#endif /* if 0 */ /*------------------------------------------------------------------------------ * @@ -306,7 +235,6 @@ void multi_modem_process_sample (int chan, int audio_sample) { int d; int subchan; - static int i = 0; /* for interleaving among multiple demodulators. */ // Accumulate an average DC bias level. // Shouldn't happen with a soundcard but could with mistuned SDR. @@ -334,21 +262,9 @@ void multi_modem_process_sample (int chan, int audio_sample) /* Formerly one loop. */ /* 1.2: We can feed one demodulator but end up with multiple outputs. */ - - if (save_audio_config_p->achan[chan].interleave > 1) { - -// TODO: temp debug, remove this. - - assert (save_audio_config_p->achan[chan].interleave == save_audio_config_p->achan[chan].num_subchan); - demod_process_sample(chan, i, audio_sample); - i++; - if (i >= save_audio_config_p->achan[chan].interleave) i = 0; - } - else { - /* Send same thing to all. */ - for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) { - demod_process_sample(chan, d, audio_sample); - } + /* Send same thing to all. */ + for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) { + demod_process_sample(chan, d, audio_sample); } for (subchan = 0; subchan < save_audio_config_p->achan[chan].num_subchan; subchan++) { @@ -359,7 +275,12 @@ void multi_modem_process_sample (int chan, int audio_sample) if (candidate[chan][subchan][slice].packet_p != NULL) { candidate[chan][subchan][slice].age++; if (candidate[chan][subchan][slice].age > process_age[chan]) { - pick_best_candidate (chan); + if (fx25_rec_busy(chan)) { + candidate[chan][subchan][slice].age = 0; + } + else { + pick_best_candidate (chan); + } } } } @@ -384,94 +305,15 @@ void multi_modem_process_sample (int chan, int audio_sample) * (Special case, use negative to skip * display of audio level line. * Use -2 to indicate DTMF message.) - * retries - Level of bit correction used. - * + * retries - Level of correction used. + * is_fx25 - 1 for FX.25, 0 for normal AX.25. * * Description: Add to list of candidates. Best one will be picked later. * *--------------------------------------------------------------------*/ -/* - - It gets a little more complicated when we try fixing frames - with imperfect CRCs. - Changing of adjacent bits is quick and done immediately. These - all come in at nearly the same time. The processing of two - separated bits can take a long time and is handled in the - background by another thread. These could come in seconds later. - - We need a way to remove duplicates. I think these are the - two cases we need to consider. - - (1) Same result as earlier no error or adjacent bit errors. - - ____||||_ - 0.0: ptr=00000000 - 0.1: ptr=00000000 - 0.2: ptr=00000000 - 0.3: ptr=00000000 - 0.4: ptr=009E5540, retry=0, age=295, crc=9458, score=5024 - 0.5: ptr=0082F008, retry=0, age=294, crc=9458, score=5026 *** - 0.6: ptr=009CE560, retry=0, age=293, crc=9458, score=5026 - 0.7: ptr=009CEE08, retry=0, age=293, crc=9458, score=5024 - 0.8: ptr=00000000 - - ___._____ - 0.0: ptr=00000000 - 0.1: ptr=00000000 - 0.2: ptr=00000000 - 0.3: ptr=009E5540, retry=4, age=295, crc=9458, score=1000 *** - 0.4: ptr=00000000 - 0.5: ptr=00000000 - 0.6: ptr=00000000 - 0.7: ptr=00000000 - 0.8: ptr=00000000 - - (2) Only results from adjusting two non-adjacent bits. - - - ||||||||_ - 0.0: ptr=022EBA08, retry=0, age=289, crc=5acd, score=5042 - 0.1: ptr=022EA8B8, retry=0, age=290, crc=5acd, score=5048 - 0.2: ptr=022EB160, retry=0, age=290, crc=5acd, score=5052 - 0.3: ptr=05BD0048, retry=0, age=291, crc=5acd, score=5054 *** - 0.4: ptr=04FE0048, retry=0, age=292, crc=5acd, score=5054 - 0.5: ptr=05E10048, retry=0, age=294, crc=5acd, score=5052 - 0.6: ptr=053D0048, retry=0, age=294, crc=5acd, score=5048 - 0.7: ptr=02375558, retry=0, age=295, crc=5acd, score=5042 - 0.8: ptr=00000000 - - _______._ - 0.0: ptr=00000000 - 0.1: ptr=00000000 - 0.2: ptr=00000000 - 0.3: ptr=00000000 - 0.4: ptr=00000000 - 0.5: ptr=00000000 - 0.6: ptr=00000000 - 0.7: ptr=02375558, retry=4, age=295, crc=5fc5, score=1000 *** - 0.8: ptr=00000000 - - ________. - 0.0: ptr=00000000 - 0.1: ptr=00000000 - 0.2: ptr=00000000 - 0.3: ptr=00000000 - 0.4: ptr=00000000 - 0.5: ptr=00000000 - 0.6: ptr=00000000 - 0.7: ptr=00000000 - 0.8: ptr=02375558, retry=4, age=295, crc=5fc5, score=1000 *** - - - These can both be covered by keepin the last CRC and dropping - duplicates. In theory we could get another frame in between with - a slow computer so the complete solution would be to remember more - than one. -*/ - -void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries) +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) { packet_t pp; @@ -480,8 +322,29 @@ void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned c assert (subchan >= 0 && subchan < MAX_SUBCHANS); assert (slice >= 0 && slice < MAX_SUBCHANS); - pp = ax25_from_frame (fbuf, flen, alevel); +// Special encapsulation for AIS & EAS so they can be treated normally pretty much everywhere else. + if (save_audio_config_p->achan[chan].modem_type == MODEM_AIS) { + char nmea[256]; + ais_to_nmea (fbuf, flen, nmea, sizeof(nmea)); + + char monfmt[276]; + snprintf (monfmt, sizeof(monfmt), "AIS>%s%1d%1d:{%c%c%s", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, USER_DEF_USER_ID, USER_DEF_TYPE_AIS, nmea); + pp = ax25_from_text (monfmt, 1); + + // alevel gets in there somehow making me question why it is passed thru here. + } + else if (save_audio_config_p->achan[chan].modem_type == MODEM_EAS) { + char monfmt[300]; // EAS SAME message max length is 268 + + snprintf (monfmt, sizeof(monfmt), "EAS>%s%1d%1d:{%c%c%s", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, USER_DEF_USER_ID, USER_DEF_TYPE_EAS, fbuf); + pp = ax25_from_text (monfmt, 1); + + // alevel gets in there somehow making me question why it is passed thru here. + } + else { + pp = ax25_from_frame (fbuf, flen, alevel); + } if (pp == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Unexpected internal problem, %s %d\n", __FILE__, __LINE__); @@ -490,10 +353,12 @@ void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned c /* - * If only one demodulator/slicer, push it thru and forget about all this foolishness. + * If only one demodulator/slicer, and no FX.25 in progress, + * push it thru and forget about all this foolishness. */ if (save_audio_config_p->achan[chan].num_subchan == 1 && - save_audio_config_p->achan[chan].num_slicers == 1) { + save_audio_config_p->achan[chan].num_slicers == 1 && + ! fx25_rec_busy(chan)) { int drop_it = 0; @@ -514,7 +379,7 @@ void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned c ax25_delete (pp); } else { - dlq_rec_frame (chan, subchan, slice, pp, alevel, retries, ""); + dlq_rec_frame (chan, subchan, slice, pp, alevel, is_fx25, retries, ""); } return; } @@ -524,13 +389,17 @@ void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned c * Otherwise, save them up for a few bit times so we can pick the best. */ if (candidate[chan][subchan][slice].packet_p != NULL) { - /* Oops! Didn't expect it to be there. */ + /* Plain old AX.25: Oops! Didn't expect it to be there. */ + /* FX.25: Quietly replace anything already there. It will have priority. */ ax25_delete (candidate[chan][subchan][slice].packet_p); candidate[chan][subchan][slice].packet_p = NULL; } + assert (pp != NULL); + candidate[chan][subchan][slice].packet_p = pp; candidate[chan][subchan][slice].alevel = alevel; + candidate[chan][subchan][slice].is_fx25 = is_fx25; candidate[chan][subchan][slice].retries = retries; candidate[chan][subchan][slice].age = 0; candidate[chan][subchan][slice].crc = ax25_m_m_crc(pp); @@ -580,6 +449,15 @@ static void pick_best_candidate (int chan) if (candidate[chan][j][k].packet_p == NULL) { spectrum[n] = '_'; } + else if (candidate[chan][j][k].is_fx25) { + // FIXME: using retries both as an enum and later int too. + if ((int)(candidate[chan][j][k].retries) <= 9) { + spectrum[n] = '0' + candidate[chan][j][k].retries; + } + else { + spectrum[n] = '+'; + } + } else if (candidate[chan][j][k].retries == RETRY_NONE) { spectrum[n] = '|'; } @@ -596,12 +474,17 @@ static void pick_best_candidate (int chan) candidate[chan][j][k].score = 0; } else { - /* Originally, this produced 0 for the PASSALL case. */ - /* This didn't work so well when looking for the best score. */ - /* Around 1.3 dev H, we add an extra 1 in here so the minimum */ - /* score should now be 1 for anything received. */ + if (candidate[chan][j][k].is_fx25) { + candidate[chan][j][k].score = 9000 - 100 * candidate[chan][j][k].retries; + } + else { + /* Originally, this produced 0 for the PASSALL case. */ + /* This didn't work so well when looking for the best score. */ + /* Around 1.3 dev H, we add an extra 1 in here so the minimum */ + /* score should now be 1 for anything received. */ - candidate[chan][j][k].score = RETRY_MAX * 1000 - ((int)candidate[chan][j][k].retries * 1000) + 1; + candidate[chan][j][k].score = RETRY_MAX * 1000 - ((int)candidate[chan][j][k].retries * 1000) + 1; + } } } @@ -657,8 +540,9 @@ static void pick_best_candidate (int chan) candidate[chan][j][k].packet_p); } else { - dw_printf ("%d.%d.%d: ptr=%p, retry=%d, age=%3d, crc=%04x, score=%d %s\n", chan, j, k, + dw_printf ("%d.%d.%d: ptr=%p, is_fx25=%d, retry=%d, age=%3d, crc=%04x, score=%d %s\n", chan, j, k, candidate[chan][j][k].packet_p, + candidate[chan][j][k].is_fx25, (int)(candidate[chan][j][k].retries), candidate[chan][j][k].age, candidate[chan][j][k].crc, @@ -713,9 +597,11 @@ static void pick_best_candidate (int chan) candidate[chan][j][k].packet_p = NULL; } else { + assert (candidate[chan][j][k].packet_p != NULL); dlq_rec_frame (chan, j, k, candidate[chan][j][k].packet_p, candidate[chan][j][k].alevel, + candidate[chan][j][k].is_fx25, (int)(candidate[chan][j][k].retries), spectrum); diff --git a/multi_modem.h b/src/multi_modem.h similarity index 95% rename from multi_modem.h rename to src/multi_modem.h index 0c096ef..0734492 100644 --- a/multi_modem.h +++ b/src/multi_modem.h @@ -16,6 +16,6 @@ void multi_modem_process_sample (int c, int audio_sample); int multi_modem_get_dc_average (int chan); -void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned char *fbuf, int flen, alevel_t alevel, retry_t retries); +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); #endif diff --git a/pfilter.c b/src/pfilter.c similarity index 98% rename from pfilter.c rename to src/pfilter.c index 26b8532..626f071 100644 --- a/pfilter.c +++ b/src/pfilter.c @@ -1298,7 +1298,7 @@ static int filt_s (pfstate_t *pf) * * * "time" is maximum number of minutes since message addressee was last heard. - * This is required. + * This is required. APRS-IS uses 3 hours so that would be a good value here. * * "hops" is maximum number of digpeater hops. (i.e. 0 for heard directly). * If hops is not specified, the maximum transmit digipeater hop count, @@ -1309,8 +1309,8 @@ static int filt_s (pfstate_t *pf) * Examples: * i/60/0 Heard in past 60 minutes directly. * i/45 Past 45 minutes, default max digi hops. - * i/30/3 Default time, max 3 digi hops. - * i/30/8/42.6/-71.3/50. + * i/180/3 Default time (3 hours), max 3 digi hops. + * i/180/8/42.6/-71.3/50. * * * It only makes sense to use this for the IS>RF direction. @@ -1321,6 +1321,10 @@ static int filt_s (pfstate_t *pf) * position report from the sender of the "message." * That is done somewhere else. We are not concerned with it here. * + * IMHO, the rules here are too restrictive. + * + * FIXME -explain + * *------------------------------------------------------------------------------*/ static int filt_i (pfstate_t *pf) @@ -1329,7 +1333,15 @@ static int filt_i (pfstate_t *pf) char *cp; char sep[2]; char *v; - int heardtime = 30; + +// http://lists.tapr.org/pipermail/aprssig_lists.tapr.org/2020-July/048656.html +// Default of 3 hours should be good. +// One might question why to have a time limit at all. Messages are very rare +// the the APRS-IS wouldn't be sending it to me unless the addressee was in the +// vicinity recently. +// TODO: Should produce a warning if a user specified filter does not include "i". + + int heardtime = 180; // 3 hours * 60 min/hr = 180 minutes #if PFTEST int maxhops = 2; #else @@ -1466,7 +1478,7 @@ static int filt_i (pfstate_t *pf) * * Maybe we could compromise here and say the sender must have been heard directly. * It sent the message currently being processed so we must have heard it very recently, i.e. in - * the past minute, rather than the usual 30 or 60 minutes for the addressee. + * the past minute, rather than the usual 180 minutes for the addressee. */ was_heard = mheard_was_recently_nearby ("source", src, 1, 0, G_UNKNOWN, G_UNKNOWN, G_UNKNOWN); diff --git a/pfilter.h b/src/pfilter.h similarity index 100% rename from pfilter.h rename to src/pfilter.h diff --git a/ptt.c b/src/ptt.c similarity index 92% rename from ptt.c rename to src/ptt.c index c070e5a..2a94300 100644 --- a/ptt.c +++ b/src/ptt.c @@ -62,8 +62,8 @@ *---------------------------------------------------------------*/ /* - A growing number of people have been asking about support for the DMK URI - or the similar RB-USB RIM. + A growing number of people have been asking about support for the DMK URI, + RB-USB RIM, etc. These use a C-Media CM108/CM119 with an interesting addition, a GPIO pin is used to drive PTT. Here is some related information. @@ -78,6 +78,15 @@ http://www.repeater-builder.com/products/usb-rim-lite.html http://www.repeater-builder.com/voip/pdf/cm119-datasheet.pdf + RA-35: + + http://www.masterscommunications.com/products/radio-adapter/ra35.html + + DINAH: + + https://hamprojects.info/dinah/ + + Homebrew versions of the same idea: http://images.ohnosec.org/usbfob.pdf @@ -458,6 +467,9 @@ void export_gpio(int ch, int ot, int invert, int direction) exit (1); } } + /* Wait for udev to adjust permissions after enabling GPIO. */ + /* https://github.com/wb2osz/direwolf/issues/176 */ + SLEEP_MS(250); close (fd); /* @@ -725,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].valid) { + if (audio_config_p->achan[ch].medium == MEDIUM_RADIO) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { @@ -756,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].valid) { + if (audio_config_p->achan[j].medium == 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]; @@ -839,7 +851,7 @@ void ptt_init (struct audio_s *audio_config_p) using_gpio = 0; for (ch=0; chachan[ch].valid) { + if (save_audio_config_p->achan[ch].medium == MEDIUM_RADIO) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_GPIO) { @@ -864,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].valid) { + if (save_audio_config_p->achan[ch].medium == MEDIUM_RADIO) { int ot; // output control type, PTT, DCD, CON, ... int it; // input control type @@ -896,7 +908,7 @@ 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].valid) { + if (save_audio_config_p->achan[ch].medium == MEDIUM_RADIO) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_LPT) { @@ -911,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].valid) { + if (audio_config_p->achan[j].medium == 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]; @@ -963,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].valid) { + if (save_audio_config_p->achan[ch].medium == MEDIUM_RADIO) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_HAMLIB) { @@ -972,16 +984,26 @@ void ptt_init (struct audio_s *audio_config_p) /* For "AUTO" model, try to guess what is out there. */ if (audio_config_p->achan[ch].octrl[ot].ptt_model == -1) { - hamlib_port_t hport; + hamlib_port_t hport; // http://hamlib.sourceforge.net/manuals/1.2.15/structhamlib__port__t.html memset (&hport, 0, sizeof(hport)); strlcpy (hport.pathname, audio_config_p->achan[ch].octrl[ot].ptt_device, sizeof(hport.pathname)); + + if (audio_config_p->achan[ch].octrl[ot].ptt_rate > 0) { + // Override the default serial port data rate. + hport.parm.serial.rate = audio_config_p->achan[ch].octrl[ot].ptt_rate; + hport.parm.serial.data_bits = 8; + hport.parm.serial.stop_bits = 1; + hport.parm.serial.parity = RIG_PARITY_NONE; + hport.parm.serial.handshake = RIG_HANDSHAKE_NONE; + } + rig_load_all_backends(); audio_config_p->achan[ch].octrl[ot].ptt_model = rig_probe(&hport); if (audio_config_p->achan[ch].octrl[ot].ptt_model == RIG_MODEL_NONE) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Couldn't guess rig model number for AUTO option. Run \"rigctl --list\" for a list of model numbers.\n"); + dw_printf ("Hamlib Error: Couldn't guess rig model number for AUTO option. Run \"rigctl --list\" for a list of model numbers.\n"); continue; } @@ -993,12 +1015,35 @@ void ptt_init (struct audio_s *audio_config_p) rig[ch][ot] = rig_init(audio_config_p->achan[ch].octrl[ot].ptt_model); if (rig[ch][ot] == NULL) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Unknown rig model %d for hamlib. Run \"rigctl --list\" for a list of model numbers.\n", + dw_printf ("Hamlib error: Unknown rig model %d. Run \"rigctl --list\" for a list of model numbers.\n", audio_config_p->achan[ch].octrl[ot].ptt_model); continue; } strlcpy (rig[ch][ot]->state.rigport.pathname, audio_config_p->achan[ch].octrl[ot].ptt_device, sizeof(rig[ch][ot]->state.rigport.pathname)); + + // Issue 290. + // We had a case where hamlib defaulted to 9600 baud for a particular + // radio model but 38400 was needed. Add an option for the configuration + // file to override the hamlib default speed. + + text_color_set(DW_COLOR_INFO); + if (audio_config_p->achan[ch].octrl[ot].ptt_model != 2) { // 2 is network, not serial port. + dw_printf ("Hamlib determined CAT control serial port rate of %d.\n", rig[ch][ot]->state.rigport.parm.serial.rate); + } + + // Config file can optionally override the rate that hamlib came up with. + + if (audio_config_p->achan[ch].octrl[ot].ptt_rate > 0) { + dw_printf ("User configuration overriding hamlib CAT control speed to %d.\n", audio_config_p->achan[ch].octrl[ot].ptt_rate); + rig[ch][ot]->state.rigport.parm.serial.rate = audio_config_p->achan[ch].octrl[ot].ptt_rate; + + // Do we want to explicitly set all of these or let it default? + rig[ch][ot]->state.rigport.parm.serial.data_bits = 8; + rig[ch][ot]->state.rigport.parm.serial.stop_bits = 1; + rig[ch][ot]->state.rigport.parm.serial.parity = RIG_PARITY_NONE; + rig[ch][ot]->state.rigport.parm.serial.handshake = RIG_HANDSHAKE_NONE; + } int err = rig_open(rig[ch][ot]); if (err != RIG_OK) { text_color_set(DW_COLOR_ERROR); @@ -1030,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].valid) { + if (audio_config_p->achan[ch].medium == MEDIUM_RADIO) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_CM108) { @@ -1051,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].valid) { + if (audio_config_p->achan[ch].medium == 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); @@ -1103,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].valid) { + if ( save_audio_config_p->achan[chan].medium != 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; @@ -1267,13 +1312,13 @@ void ptt_set (int ot, int chan, int ptt_signal) if (retcode != RIG_OK) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Error sending rig_set_ptt command for channel %d %s\n", chan, otnames[ot]); + dw_printf ("Hamlib Error: rig_set_ptt command for channel %d %s\n", chan, otnames[ot]); dw_printf ("%s\n", rigerror(retcode)); } } else { text_color_set(DW_COLOR_ERROR); - dw_printf ("Can't use rig_set_ptt for channel %d %s because rig_open failed.\n", chan, otnames[ot]); + dw_printf ("Hamlib: Can't use rig_set_ptt for channel %d %s because rig_open failed.\n", chan, otnames[ot]); } } #endif @@ -1314,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].valid) { + if ( save_audio_config_p->achan[chan].medium != 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; @@ -1378,7 +1423,7 @@ void ptt_term (void) int n; for (n = 0; n < MAX_CHANS; n++) { - if (save_audio_config_p->achan[n].valid) { + if (save_audio_config_p->achan[n].medium == MEDIUM_RADIO) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { ptt_set (ot, n, 0); @@ -1387,7 +1432,7 @@ void ptt_term (void) } for (n = 0; n < MAX_CHANS; n++) { - if (save_audio_config_p->achan[n].valid) { + if (save_audio_config_p->achan[n].medium == MEDIUM_RADIO) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { if (ptt_fd[n][ot] != INVALID_HANDLE_VALUE) { @@ -1405,7 +1450,7 @@ void ptt_term (void) #ifdef USE_HAMLIB for (n = 0; n < MAX_CHANS; n++) { - if (save_audio_config_p->achan[n].valid) { + if (save_audio_config_p->achan[n].medium == MEDIUM_RADIO) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { if (rig[n][ot] != NULL) { @@ -1444,14 +1489,14 @@ int main () my_audio_config.adev[0].num_channels = 2; - my_audio_config.achan[0].valid = 1; + my_audio_config.achan[0].medium = 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].valid = 1; + my_audio_config.achan[1].medium = 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)); @@ -1525,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.valid[0] = 1; + my_audio_config.achan[0].medium = 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; @@ -1556,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.valid[0] = 1; + my_audio_config.achan[0].medium = 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.valid[1] = 1; + my_audio_config.achan[1].medium = 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/ptt.h b/src/ptt.h similarity index 100% rename from ptt.h rename to src/ptt.h diff --git a/recv.c b/src/recv.c similarity index 96% rename from recv.c rename to src/recv.c index 2f82d54..f5c7816 100644 --- a/recv.c +++ b/src/recv.c @@ -88,6 +88,7 @@ #include #include #include +#include #include //#include //#include @@ -160,7 +161,7 @@ void recv_init (struct audio_s *pa) #endif #if __WIN32__ - xmit_th[a] = (HANDLE)_beginthreadex (NULL, 0, recv_adev_thread, (void*)(long)a, 0, NULL); + xmit_th[a] = (HANDLE)_beginthreadex (NULL, 0, recv_adev_thread, (void*)(ptrdiff_t)a, 0, NULL); if (xmit_th[a] == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("FATAL: Could not create audio receive thread for device %d.\n", a); @@ -168,7 +169,7 @@ void recv_init (struct audio_s *pa) } #else int e; - e = pthread_create (&xmit_tid[a], NULL, recv_adev_thread, (void *)(long)a); + e = pthread_create (&xmit_tid[a], NULL, recv_adev_thread, (void *)(ptrdiff_t)a); if (e != 0) { text_color_set(DW_COLOR_ERROR); @@ -203,7 +204,7 @@ static unsigned __stdcall recv_adev_thread (void *arg) static void * recv_adev_thread (void *arg) #endif { - int a = (int)(long)arg; // audio device number. + int a = (int)(ptrdiff_t)arg; // audio device number. int eof; /* This audio device can have one (mono) or two (stereo) channels. */ @@ -337,7 +338,7 @@ void recv_process (void) * - Digipeater. */ - app_process_rec_packet (pitem->chan, pitem->subchan, pitem->slice, pitem->pp, pitem->alevel, pitem->retries, pitem->spectrum); + app_process_rec_packet (pitem->chan, pitem->subchan, pitem->slice, pitem->pp, pitem->alevel, pitem->is_fx25, pitem->retries, pitem->spectrum); /* @@ -373,6 +374,11 @@ void recv_process (void) dl_unregister_callsign (pitem); break; + case DLQ_OUTSTANDING_FRAMES_REQUEST: + + dl_outstanding_frames_request (pitem); + break; + case DLQ_CHANNEL_BUSY: lm_channel_busy (pitem); diff --git a/recv.h b/src/recv.h similarity index 100% rename from recv.h rename to src/recv.h diff --git a/redecode.h b/src/redecode.h similarity index 100% rename from redecode.h rename to src/redecode.h diff --git a/rpack.h b/src/rpack.h similarity index 100% rename from rpack.h rename to src/rpack.h diff --git a/rrbb.c b/src/rrbb.c similarity index 100% rename from rrbb.c rename to src/rrbb.c diff --git a/rrbb.h b/src/rrbb.h similarity index 100% rename from rrbb.h rename to src/rrbb.h diff --git a/serial_port.c b/src/serial_port.c similarity index 99% rename from serial_port.c rename to src/serial_port.c index 5f3c8e1..7dff33a 100644 --- a/serial_port.c +++ b/src/serial_port.c @@ -44,7 +44,7 @@ #include #include #include -#include +#include #endif diff --git a/serial_port.h b/src/serial_port.h similarity index 100% rename from serial_port.h rename to src/serial_port.h diff --git a/server.c b/src/server.c similarity index 81% rename from server.c rename to src/server.c index 0a74ff3..8c52b19 100644 --- a/server.c +++ b/src/server.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 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 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 @@ -45,9 +45,9 @@ * 'k' Ask to start receiving RAW AX25 frames. * * 'm' Ask to start receiving Monitor AX25 frames. + * Enables sending of U, I, S, and T messages to client app. * * 'V' Transmit UI data frame. - * Generate audio for transmission. * * 'H' Report recently heard stations. Not implemented yet. * @@ -89,7 +89,16 @@ * 'K' Received AX.25 frame in raw format. * (Enabled with 'k' command.) * - * 'U' Received AX.25 frame in monitor format. + * 'U' Received AX.25 "UI" frames in monitor format. + * (Enabled with 'm' command.) + * + * 'I' Received AX.25 "I" frames in monitor format. (new in 1.6) + * (Enabled with 'm' command.) + * + * 'S' Received AX.25 "S" and "U" (other than UI) frames in monitor format. (new in 1.6) + * (Enabled with 'm' command.) + * + * 'T' Own Transmitted AX.25 frames in monitor format. (new in 1.6) * (Enabled with 'm' command.) * * 'y' Outstanding frames waiting on a Port (new in 1.2) @@ -136,11 +145,7 @@ #include #include #include -#ifdef __OpenBSD__ #include -#else -#include -#endif #endif #include @@ -149,6 +154,7 @@ #include #include #include +#include #include "tq.h" #include "ax25_pad.h" @@ -465,14 +471,14 @@ void server_init (struct audio_s *audio_config_p, struct misc_config_s *mc) * This waits for a client to connect and sets an available client_sock[n]. */ #if __WIN32__ - connect_listen_th = (HANDLE)_beginthreadex (NULL, 0, connect_listen_thread, (void *)(unsigned int)server_port, 0, NULL); + connect_listen_th = (HANDLE)_beginthreadex (NULL, 0, connect_listen_thread, (void *)(ptrdiff_t)server_port, 0, NULL); if (connect_listen_th == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not create AGW connect listening thread\n"); return; } #else - e = pthread_create (&connect_listen_tid, NULL, connect_listen_thread, (void *)(long)server_port); + e = pthread_create (&connect_listen_tid, NULL, connect_listen_thread, (void *)(ptrdiff_t)server_port); if (e != 0) { text_color_set(DW_COLOR_ERROR); perror("Could not create AGW connect listening thread"); @@ -488,14 +494,14 @@ void server_init (struct audio_s *audio_config_p, struct misc_config_s *mc) for (client = 0; client < MAX_NET_CLIENTS; client++) { #if __WIN32__ - cmd_listen_th[client] = (HANDLE)_beginthreadex (NULL, 0, cmd_listen_thread, (void*)client, 0, NULL); + cmd_listen_th[client] = (HANDLE)_beginthreadex (NULL, 0, cmd_listen_thread, (void*)(ptrdiff_t)client, 0, NULL); if (cmd_listen_th[client] == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not create AGW command listening thread for client %d\n", client); return; } #else - e = pthread_create (&cmd_listen_tid[client], NULL, cmd_listen_thread, (void *)(long)client); + e = pthread_create (&cmd_listen_tid[client], NULL, cmd_listen_thread, (void *)(ptrdiff_t)client); if (e != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not create AGW command listening thread for client %d\n", client); @@ -539,10 +545,10 @@ static THREAD_F connect_listen_thread (void *arg) SOCKET listen_sock; WSADATA wsadata; - snprintf (server_port_str, sizeof(server_port_str), "%d", (int)(long)arg); + snprintf (server_port_str, sizeof(server_port_str), "%d", (int)(ptrdiff_t)arg); #if DEBUG text_color_set(DW_COLOR_DEBUG); - dw_printf ("DEBUG: serverport = %d = '%s'\n", (int)(long)arg, server_port_str); + dw_printf ("DEBUG: serverport = %d = '%s'\n", (int)(ptrdiff_t)arg, server_port_str); #endif err = WSAStartup (MAKEWORD(2,2), &wsadata); if (err != 0) { @@ -662,7 +668,7 @@ static THREAD_F connect_listen_thread (void *arg) struct sockaddr_in sockaddr; /* Internet socket address stuct */ socklen_t sockaddr_size = sizeof(struct sockaddr_in); - int server_port = (int)(long)arg; + int server_port = (int)(ptrdiff_t)arg; int listen_sock; int bcopt = 1; @@ -771,10 +777,13 @@ static THREAD_F connect_listen_thread (void *arg) * * There are two different formats: * RAW - the original received frame. - * MONITOR - just the information part. + * MONITOR - human readable monitoring format. * *--------------------------------------------------------------------*/ +static void mon_addrs (int chan, packet_t pp, char *result, int result_size); +static char mon_desc (packet_t pp, char *result, int result_size); + void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int flen) { @@ -784,15 +793,11 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl } agwpe_msg; int err; - int info_len; - unsigned char *pinfo; - int client; - /* * RAW format */ - for (client=0; client 0){ @@ -842,33 +847,38 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl } } + // Application might want more human readable format. -/* MONITOR format - only for UI frames. */ + server_send_monitored (chan, pp, 0); - for (client=0; client 0 - && ax25_get_control(pp) == AX25_UI_FRAME){ +} /* end server_send_rec_packet */ - time_t clock; - struct tm *tm; - int num_digi; - clock = time(NULL); - tm = localtime(&clock); // TODO: should use localtime_r + +void server_send_monitored (int chan, packet_t pp, int own_xmit) +{ +/* + * MONITOR format - 'I' for information frames. + * 'U' for unnumbered information. + * 'S' for supervisory and other unnumbered. + */ + struct { + struct agwpe_s hdr; + char data[1+AX25_MAX_PACKET_LEN]; + } agwpe_msg; + + int err; + + for (int client=0; client 0) { memset (&agwpe_msg.hdr, 0, sizeof(agwpe_msg.hdr)); - agwpe_msg.hdr.portx = chan; - - agwpe_msg.hdr.datakind = 'U'; - + agwpe_msg.hdr.portx = chan; // datakind is added later. ax25_get_addr_with_ssid (pp, AX25_SOURCE, agwpe_msg.hdr.call_from); - ax25_get_addr_with_ssid (pp, AX25_DESTINATION, agwpe_msg.hdr.call_to); - info_len = ax25_get_info (pp, &pinfo); - /* http://uz7ho.org.ua/includes/agwpeapi.htm#_Toc500723812 */ /* Description mentions one CR character after timestamp but example has two. */ @@ -886,34 +896,34 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl // AGWPE: // [AGWE-IN] 1:Fm ZL4FOX-8 To Q7P2U2 Via WIDE3-3 [08:32:14]`I0*l V>/"98}[:Barts Tracker 3.83V X - num_digi = ax25_get_num_repeaters(pp); + // Format the channel and addresses, with leading and trailing space. - if (num_digi > 0) { + mon_addrs (chan, pp, (char*)(agwpe_msg.data), sizeof(agwpe_msg.data)); - char via[AX25_MAX_REPEATERS*(AX25_MAX_ADDR_LEN+1)]; - char stemp[AX25_MAX_ADDR_LEN+1]; - int j; + // Add the description with <... > - ax25_get_addr_with_ssid (pp, AX25_REPEATER_1, via); - for (j = 1; j < num_digi; j++) { - ax25_get_addr_with_ssid (pp, AX25_REPEATER_1 + j, stemp); - strlcat (via, ",", sizeof(via)); - strlcat (via, stemp, sizeof(via)); - } - - snprintf (agwpe_msg.data, sizeof(agwpe_msg.data), " %d:Fm %s To %s Via %s [%02d:%02d:%02d]\r%s\r\r", - chan+1, agwpe_msg.hdr.call_from, agwpe_msg.hdr.call_to, via, - ax25_get_pid(pp), info_len, - tm->tm_hour, tm->tm_min, tm->tm_sec, - pinfo); + char desc[80]; + agwpe_msg.hdr.datakind = mon_desc (pp, desc, sizeof(desc)); + if (own_xmit) { + agwpe_msg.hdr.datakind = 'T'; } - else { + strlcat ((char*)(agwpe_msg.data), desc, sizeof(agwpe_msg.data)); - snprintf (agwpe_msg.data, sizeof(agwpe_msg.data), " %d:Fm %s To %s [%02d:%02d:%02d]\r%s\r\r", - chan+1, agwpe_msg.hdr.call_from, agwpe_msg.hdr.call_to, - ax25_get_pid(pp), info_len, - tm->tm_hour, tm->tm_min, tm->tm_sec, - pinfo); + // Timestamp with [...]\r + + time_t clock = time(NULL); + struct tm *tm = localtime(&clock); // TODO: use localtime_r ? + char ts[32]; + 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. + + unsigned char *pinfo = NULL; + int info_len = ax25_get_info (pp, &pinfo); + 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)); } agwpe_msg.hdr.data_len_NETLE = host2netle(strlen(agwpe_msg.data) + 1) /* +1 to include terminating null */ ; @@ -947,9 +957,103 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl } } -} /* server_send_rec_packet */ +} /* server_send_monitored */ +// Next two are broken out in case they can be reused elsewhere. + +// Format addresses in AGWPR monitoring format such as: +// 1:Fm ZL4FOX-8 To Q7P2U2 Via WIDE3-3 + +static void mon_addrs (int chan, packet_t pp, char *result, int result_size) +{ + char src[AX25_MAX_ADDR_LEN]; + char dst[AX25_MAX_ADDR_LEN]; + + ax25_get_addr_with_ssid (pp, AX25_SOURCE, src); + ax25_get_addr_with_ssid (pp, AX25_DESTINATION, dst); + int num_digi = ax25_get_num_repeaters(pp); + + if (num_digi > 0) { + + char via[AX25_MAX_REPEATERS*(AX25_MAX_ADDR_LEN+1)]; + char stemp[AX25_MAX_ADDR_LEN+1]; + int j; + + ax25_get_addr_with_ssid (pp, AX25_REPEATER_1, via); + for (j = 1; j < num_digi; j++) { + ax25_get_addr_with_ssid (pp, AX25_REPEATER_1 + j, stemp); + strlcat (via, ",", sizeof(via)); + strlcat (via, stemp, sizeof(via)); + } + snprintf (result, result_size, " %d:Fm %s To %s Via %s ", + chan+1, src, dst, via); + } + else { + snprintf (result, result_size, " %d:Fm %s To %s ", + chan+1, src, dst); + } +} + + +// Generate frame description in AGWPE monitoring format such as +// +// +// +// +// Returns: +// 'I' for information frame. +// 'U' for unnumbered information frame. +// 'S' for supervisory and other unnumbered frames. + +static char mon_desc (packet_t pp, char *result, int result_size) +{ + cmdres_t cr; // command/response. + char ignore[80]; // direwolf description. not used here. + int pf; // poll/final bit. + int ns; // N(S) Send sequence number. + int nr; // N(R) Received sequence number. + char pf_text[4]; // P or F depending on whether command or response. + + ax25_frame_type_t ftype = ax25_frame_type (pp, &cr, ignore, &pf, &nr, &ns); + + switch (cr) { + case cr_cmd: strcpy(pf_text, "P"); break; // P only: I, SABME, SABM, DISC + case cr_res: strcpy(pf_text, "F"); break; // F only: DM, UA, FRMR + // Either: RR, RNR, REJ, SREJ, UI, XID, TEST + + default: strcpy(pf_text, "PF"); break; // Not AX.25 version >= 2.0 + // APRS is often sloppy about this but it + // is essential for connected mode. + } + + unsigned char *pinfo = NULL; // I, UI, XID, SREJ, TEST can have information part. + int info_len = ax25_get_info (pp, &pinfo); + + 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_U_UI: snprintf (result, result_size, "", ax25_get_pid(pp), info_len, pf_text, pf); return ('U'); break; + + case frame_type_S_RR: snprintf (result, result_size, "", nr, pf_text, pf); return ('S'); break; + case frame_type_S_RNR: snprintf (result, result_size, "", nr, pf_text, pf); return ('S'); break; + case frame_type_S_REJ: snprintf (result, result_size, "", nr, pf_text, pf); return ('S'); break; + case frame_type_S_SREJ: snprintf (result, result_size, "", nr, pf_text, pf, info_len); return ('S'); break; + + case frame_type_U_SABME: snprintf (result, result_size, "", pf_text, pf); return ('S'); break; + case frame_type_U_SABM: snprintf (result, result_size, "", pf_text, pf); return ('S'); break; + case frame_type_U_DISC: snprintf (result, result_size, "", pf_text, pf); return ('S'); break; + case frame_type_U_DM: snprintf (result, result_size, "", pf_text, pf); return ('S'); break; + case frame_type_U_UA: snprintf (result, result_size, "", pf_text, pf); return ('S'); break; + case frame_type_U_FRMR: snprintf (result, result_size, "", pf_text, pf); return ('S'); break; + case frame_type_U_XID: snprintf (result, result_size, "", pf_text, pf, info_len); return ('S'); break; + case frame_type_U_TEST: snprintf (result, result_size, "", pf_text, pf, info_len); return ('S'); break; + default: + case frame_type_U: snprintf (result, result_size, ""); return ('S'); break; + } +} + /*------------------------------------------------------------------- * @@ -1121,6 +1225,50 @@ void server_rec_conn_data (int chan, int client, char *remote_call, char *own_ca } /* end server_rec_conn_data */ +/*------------------------------------------------------------------- + * + * Name: server_outstanding_frames_reply + * + * Purpose: Send 'Y' Outstanding frames for connected data to the application. + * + * Inputs: chan - Which radio channel. + * + * client - Which one of potentially several clients. + * + * own_call - Callsign[-ssid] of my end. + * + * remote_call - Callsign[-ssid] of remote station. + * + * count - Number of frames sent from the application but + * not yet received by the other station. + * + *--------------------------------------------------------------------*/ + +void server_outstanding_frames_reply (int chan, int client, char *own_call, char *remote_call, int count) +{ + + struct { + struct agwpe_s hdr; + int count_NETLE; + } reply; + + + memset (&reply.hdr, 0, sizeof(reply.hdr)); + + reply.hdr.portx = chan; + reply.hdr.datakind = 'Y'; + + strlcpy (reply.hdr.call_from, own_call, sizeof(reply.hdr.call_from)); + strlcpy (reply.hdr.call_to, remote_call, sizeof(reply.hdr.call_to)); + + reply.hdr.data_len_NETLE = host2netle(4); + reply.count_NETLE = host2netle(count); + + send_to_client (client, &reply); + +} /* end server_outstanding_frames_reply */ + + /*------------------------------------------------------------------- * * Name: read_from_socket @@ -1220,11 +1368,12 @@ static THREAD_F cmd_listen_thread (void *arg) struct { struct agwpe_s hdr; /* Command header. */ - char data[512]; /* Additional data used by some commands. */ + char data[AX25_MAX_PACKET_LEN]; /* Additional data used by some commands. */ /* Maximum for 'V': 1 + 8*10 + 256 */ + /* Maximum for 'D': Info part length + 1 */ } cmd; - int client = (int)(long)arg; + int client = (int)(ptrdiff_t)arg; assert (client >= 0 && client < MAX_NET_CLIENTS); @@ -1381,47 +1530,72 @@ static THREAD_F cmd_listen_thread (void *arg) // We can have gaps in the numbering. // I wonder what applications will think about that. -#if 1 // No other place cares about total number. count = 0; for (j=0; jachan[j].valid) { + if (save_audio_config_p->achan[j].medium == MEDIUM_RADIO || + save_audio_config_p->achan[j].medium == MEDIUM_IGATE || + save_audio_config_p->achan[j].medium == MEDIUM_NETTNC) { count++; } } snprintf (reply.info, sizeof(reply.info), "%d;", count); for (j=0; jachan[j].valid) { - char stemp[100]; - int a = ACHAN2ADEV(j); - // If I was really ambitious, some description could be provided. - static const char *names[8] = { "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth" }; - if (save_audio_config_p->adev[a].num_channels == 1) { - snprintf (stemp, sizeof(stemp), "Port%d %s soundcard mono;", j+1, names[a]); - strlcat (reply.info, stemp, sizeof(reply.info)); - } - else { - snprintf (stemp, sizeof(stemp), "Port%d %s soundcard %s;", j+1, names[a], j&1 ? "right" : "left"); - strlcat (reply.info, stemp, sizeof(reply.info)); - } - } - } + switch (save_audio_config_p->achan[j].medium) { + + case MEDIUM_RADIO: + { + char stemp[100]; + int a = ACHAN2ADEV(j); + // If I was really ambitious, some description could be provided. + static const char *names[8] = { "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth" }; + + if (save_audio_config_p->adev[a].num_channels == 1) { + snprintf (stemp, sizeof(stemp), "Port%d %s soundcard mono;", j+1, names[a]); + strlcat (reply.info, stemp, sizeof(reply.info)); + } + else { + snprintf (stemp, sizeof(stemp), "Port%d %s soundcard %s;", j+1, names[a], j&1 ? "right" : "left"); + strlcat (reply.info, stemp, sizeof(reply.info)); + } + } + break; + + case MEDIUM_IGATE: + { + char stemp[100]; + snprintf (stemp, sizeof(stemp), "Port%d Internet Gateway;", j+1); + strlcat (reply.info, stemp, sizeof(reply.info)); + } + break; + + case MEDIUM_NETTNC: + { + // could elaborate with hostname, etc. + char stemp[100]; + snprintf (stemp, sizeof(stemp), "Port%d Network TNC;", j+1); + strlcat (reply.info, stemp, sizeof(reply.info)); + } + break; + + default: + { + // could elaborate with hostname, etc. + char stemp[100]; + snprintf (stemp, sizeof(stemp), "Port%d INVALID CHANNEL;", j+1); + strlcat (reply.info, stemp, sizeof(reply.info)); + } + break; + + } // switch + } // for each channel -#else - if (num_channels == 1) { - snprintf (reply.info, sizeof(reply.info), "1;Port1 Single channel;"); - } - else { - snprintf (reply.info, sizeof(reply.info), "2;Port1 Left channel;Port2 Right Channel;"); - } -#endif reply.hdr.data_len_NETLE = host2netle(strlen(reply.info) + 1); send_to_client (client, &reply); - } break; @@ -1647,7 +1821,9 @@ static THREAD_F cmd_listen_thread (void *arg) int chan = cmd.hdr.portx; - if (chan >= 0 && chan < MAX_CHANS && save_audio_config_p->achan[chan].valid) { + // Connected mode can only be used with internal modems. + + if (chan >= 0 && chan < MAX_CHANS && save_audio_config_p->achan[chan].medium == MEDIUM_RADIO) { ok = 1; dlq_register_callsign (cmd.hdr.call_from, chan, client); } @@ -1674,7 +1850,9 @@ static THREAD_F cmd_listen_thread (void *arg) int chan = cmd.hdr.portx; - if (chan >= 0 && chan < MAX_CHANS && save_audio_config_p->achan[chan].valid) { + // Connected mode can only be used with internal modems. + + if (chan >= 0 && chan < MAX_CHANS && save_audio_config_p->achan[chan].medium == MEDIUM_RADIO) { dlq_unregister_callsign (cmd.hdr.call_from, chan, client); } else { @@ -1864,6 +2042,7 @@ static THREAD_F cmd_listen_thread (void *arg) int n = 0; if (cmd.hdr.portx >= 0 && cmd.hdr.portx < MAX_CHANS) { + // Count both normal and expedited in transmit queue for given channel. n = tq_count (cmd.hdr.portx, -1, "", "", 0); } reply.data_NETLE = host2netle(n); @@ -1874,34 +2053,53 @@ static THREAD_F cmd_listen_thread (void *arg) case 'Y': /* How Many Outstanding frames wait for tx for a particular station */ - /* Number of frames sitting in transmit queue for given channel, */ - /* source (optional) and destination addresses. */ + // This is different than the above 'y' because this refers to a specific + // link in connected mode. + + // This would be useful for a couple different purposes. + + // When sending bulk data, we want to keep a fair amount queued up to take + // advantage of large window sizes (MAXFRAME, EMAXFRAME). On the other + // 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 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. + // 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. + + // The documentation is inconsistent about the address order. + // One place says "callfrom" is my callsign and "callto" is the other guy. + // That would make sense. We are asking about frames going to the other guy. + + // But another place says it depends on who initiated the connection. + // + // "If we started the connection CallFrom=US and CallTo=THEM + // If the other end started the connection CallFrom=THEM and CallTo=US" + // + // The response description says nothing about the order; it just mentions two addresses. + // If you are writing a client or server application, the order would + // be clear but right here it could be either case. + // + // Another version of the documentation mentioned the source address being optional. + // + + // The only way to get this information is from inside the data link state machine. + // We will send a request to it and the result coming out will be used to + // send the reply back to the client application. + { - char source[AX25_MAX_ADDR_LEN]; - char dest[AX25_MAX_ADDR_LEN]; - struct { - struct agwpe_s hdr; - int data_NETLE; // Little endian order. - } reply; + char callsigns[2][AX25_MAX_ADDR_LEN]; + const int num_calls = 2; - strlcpy (source, cmd.hdr.call_from, sizeof(source)); - strlcpy (dest, cmd.hdr.call_to, sizeof(dest)); + strlcpy (callsigns[AX25_SOURCE], cmd.hdr.call_from, sizeof(callsigns[AX25_SOURCE])); + strlcpy (callsigns[AX25_DESTINATION], cmd.hdr.call_to, sizeof(callsigns[AX25_SOURCE])); - memset (&reply, 0, sizeof(reply)); - reply.hdr.portx = cmd.hdr.portx; /* Reply with same port number, addresses. */ - reply.hdr.datakind = 'Y'; - strlcpy (reply.hdr.call_from, source, sizeof(reply.hdr.call_from)); - strlcpy (reply.hdr.call_to, dest, sizeof(reply.hdr.call_to)); - reply.hdr.data_len_NETLE = host2netle(4); + // Issue 169. Proper implementation for 'Y'. + dlq_outstanding_frames_request (callsigns, num_calls, cmd.hdr.portx, client); - int n = 0; - if (cmd.hdr.portx >= 0 && cmd.hdr.portx < MAX_CHANS) { - n = tq_count (cmd.hdr.portx, -1, source, dest, 0); - } - reply.data_NETLE = host2netle(n); - - send_to_client (client, &reply); } break; diff --git a/server.h b/src/server.h similarity index 80% rename from server.h rename to src/server.h index 08db571..4cc2ea0 100644 --- a/server.h +++ b/src/server.h @@ -15,6 +15,8 @@ void server_init (struct audio_s *audio_config_p, struct misc_config_s *misc_con void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int flen); +void server_send_monitored (int chan, packet_t pp, int own_xmit); + int server_callsign_registered_by_client (char *callsign); @@ -24,5 +26,7 @@ void server_link_terminated (int chan, int client, char *remote_call, char *own_ void server_rec_conn_data (int chan, int client, char *remote_call, char *own_call, int pid, char *data_ptr, int data_len); +void server_outstanding_frames_reply (int chan, int client, char *own_call, char *remote_call, int count); + /* end server.h */ diff --git a/symbols.c b/src/symbols.c similarity index 98% rename from symbols.c rename to src/symbols.c index 208c327..bb29e4f 100644 --- a/symbols.c +++ b/src/symbols.c @@ -261,10 +261,12 @@ static const struct { // Make sure the array is null terminated. -// If search order is changed, do the same in decode_aprs.c +// If search order is changed, do the same in decode_aprs.c for consistency. static const char *search_locations[] = { - (const char *) "symbols-new.txt", + (const char *) "symbols-new.txt", // CWD + (const char *) "data/symbols-new.txt", // Windows with Cmake + (const char *) "../data/symbols-new.txt", // ? #ifndef __WIN32__ (const char *) "/usr/local/share/direwolf/symbols-new.txt", (const char *) "/usr/share/direwolf/symbols-new.txt", @@ -276,7 +278,7 @@ static const char *search_locations[] = { // path as well. (const char *) "/opt/local/share/direwolf/symbols-new.txt", #endif - (const char *) NULL + (const char *) NULL // Important - Indicates end of list. }; @@ -493,7 +495,7 @@ void symbols_list (void) } } dw_printf ("\n"); - + dw_printf ("More information here: http://www.aprs.org/symbols.html\n"); } /* end symbols_list */ @@ -1059,4 +1061,4 @@ int main (int argc, char *argv[]) #endif -/* end symbols.c */ \ No newline at end of file +/* end symbols.c */ diff --git a/symbols.h b/src/symbols.h similarity index 100% rename from symbols.h rename to src/symbols.h diff --git a/telemetry.c b/src/telemetry.c similarity index 100% rename from telemetry.c rename to src/telemetry.c diff --git a/telemetry.h b/src/telemetry.h similarity index 100% rename from telemetry.h rename to src/telemetry.h diff --git a/src/textcolor.c b/src/textcolor.c new file mode 100644 index 0000000..a515e2e --- /dev/null +++ b/src/textcolor.c @@ -0,0 +1,401 @@ + +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2011, 2012, 2013, 2014, 2019 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 . +// + + +/*------------------------------------------------------------------- + * + * Name: textcolor.c + * + * Purpose: Originally this would only set color of text + * and we used printf everywhere. + * Now we also have a printf replacement that can + * be used to redirect all output to the desired place. + * This opens the door to using ncurses, a GUI, or + * running as a daemon. + * + * Description: For Linux and Cygwin use the ANSI escape sequences. + * In earlier versions of Windows, the cmd window and ANSI.SYS + * could interpret this but it doesn't seem to be available + * anymore so we use a different interface. + * + * Reference: + * http://en.wikipedia.org/wiki/ANSI_escape_code + * + * + +>>>> READ THIS PART!!! <<<< + + * + * + * Problem: Years ago, when I started on this... + * + * The ANSI escape sequences, used for text colors, allowed 8 basic colors. + * Unfortunately, white is not one of them. We only have dark + * white, also known as light gray. To get brighter colors, + * we need to apply an attribute. On some systems, the bold + * attribute produces a brighter color rather than a bold font. + * On other systems, we need to use the blink attribute to get + * bright colors, including white. However on others, blink + * does actually produce blinking characters. + * + * Previously, the only option was to put "-t 0" on the command + * line to disable all text color. This is more readable but + * makes it harder to distinguish different types of + * information, e.g. received packets vs. error messages. + * + * A few people have suggested ncurses. + * I looked at ncurses, and it doesn't seem to be the solution. + * It always sends the same color control codes rather than + * detecting the terminal type and adjusting its behavior. + * + * Version 1.6: + * + * For a long time, there was a compile time distinction between + * ARM (e.g. Raspberry Pi) and other platforms. With the arrival + * of Raspbian Buster, we get flashing and the general Linux settings + * work better. + * + * Since there doesn't seem to be a single universal solution, + * the text color option will now be allowed to have multiple values. + * Several people have also complained that bright green is + * very hard to read against a light background so only dark green will be used. + * + *--------------------------------------------------------------------*/ + + +#include "direwolf.h" // Should be first. includes windows.h + +#include +#include +#include + + +#if __WIN32__ + +#define BACKGROUND_WHITE (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY) + +#else /* Linux, BSD, Mac OSX */ + +// Alternative 1: + +// Using RGB colors - New in version 1.6. +// Since version 1.2, we've been using RGB to set the background to white. +// From this we can deduce that pretty much everyone recognizes RGB colors by now. +// The only known exception was PuTTY 0.70 and this has been rectified in 0.71. +// Instead of picking 1 of 8 colors, and using some attribute to get bright, just specify it directly. +// This should eliminate the need to reset the background after messing with the bright/bold/blink +// attributes to get more than 8 colors. + + +// Alternative 2: + +// Was new in version 1.2, as suggested by IW2DHW. +// Tested with gnome-terminal and xterm. +// Raspbian Buster LXTerminal also likes this. +// There was probably an issue with an earlier release because I intentionally made ARM different at one time. + +// Here we are using the RGB color format to set the background. +// PuTTY 0.70 doesn't recognize the RGB format so the background is not set. +// Instead of complaining about it, just upgrade to PuTTY 0.71. + + +// Alternative 3: + +// For some terminals we needed "blink" (5) rather than the expected bright/bold (1) +// attribute to get bright white background. +// Makes no sense but I stumbled across that somewhere. + +// In some cases, you might find background (around text but not rest of line) is set to white. +// On GNOME Terminal and LXTerminal, this produces blinking text with a gray background. + + +// Alternative 4: + +// This is using the bright/bold attribute, as you would expect from the documentation. +// Whenever a dark color is used, the background is reset and needs to be set again. +// In recent tests, background is always gray, not white like it should be. + + +#define MAX_T 4 + +static const char *t_background_white[MAX_T+1] = { "", "\e[48;2;255;255;255m", "\e[48;2;255;255;255m", "\e[5;47m", "\e[1;47m" }; + +static const char *t_black[MAX_T+1] = { "", "\e[38;2;0;0;0m", "\e[0;30m" "\e[48;2;255;255;255m", "\e[0;30m" "\e[5;47m", "\e[0;30m" "\e[1;47m" }; +static const char *t_red[MAX_T+1] = { "", "\e[38;2;255;0;0m", "\e[1;31m" "\e[48;2;255;255;255m", "\e[1;31m" "\e[5;47m", "\e[1;31m" "\e[1;47m" }; +static const char *t_green[MAX_T+1] = { "", "\e[38;2;0;255;0m", "\e[1;32m" "\e[48;2;255;255;255m", "\e[1;32m" "\e[5;47m", "\e[1;32m" "\e[1;47m" }; +static const char *t_dark_green[MAX_T+1]= { "", "\e[38;2;0;192;0m", "\e[0;32m" "\e[48;2;255;255;255m", "\e[0;32m" "\e[5;47m", "\e[0;32m" "\e[1;47m" }; +static const char *t_yellow[MAX_T+1] = { "", "\e[38;2;255;255;0m", "\e[1;33m" "\e[48;2;255;255;255m", "\e[1;33m" "\e[5;47m", "\e[1;33m" "\e[1;47m" }; +static const char *t_blue[MAX_T+1] = { "", "\e[38;2;0;0;255m", "\e[1;34m" "\e[48;2;255;255;255m", "\e[1;34m" "\e[5;47m", "\e[1;34m" "\e[1;47m" }; +static const char *t_magenta[MAX_T+1] = { "", "\e[38;2;255;0;255m", "\e[1;35m" "\e[48;2;255;255;255m", "\e[1;35m" "\e[5;47m", "\e[1;35m" "\e[1;47m" }; +static const char *t_cyan[MAX_T+1] = { "", "\e[38;2;0;255;255m", "\e[0;36m" "\e[48;2;255;255;255m", "\e[0;36m" "\e[5;47m", "\e[0;36m" "\e[1;47m" }; + + +/* Clear from cursor to end of screen. */ + +static const char clear_eos[] = "\e[0J"; + +#endif /* end Linux */ + + +#include "textcolor.h" + + +/* + * g_enable_color: + * 0 = disable text colors. + * 1 = default, should be good for LXTerminal >= 0.3.2, GNOME Terminal, xterm, PuTTY >= 0.71. + * 2 = what we had for a few earlier versions. Should be good for LXTerminal, GNOME Terminal, xterm. + * 3 = use 8 basic colors, blinking attribute to get brighter color. Best for older PuTTY. + * 4 = use 8 basic colors, bold attribute to get brighter color. + * + * others... future possibility - tell me if none of these work properly for your terminal type. + * + * 9 (more accurately any invalid value) = try all of them and exit. + */ + +static int g_enable_color = 1; + + +void text_color_init (int enable_color) +{ + + +#if __WIN32__ + + + if (g_enable_color != 0) { + + HANDLE h; + CONSOLE_SCREEN_BUFFER_INFO csbi; + WORD attr = BACKGROUND_WHITE; + DWORD length; + COORD coord; + DWORD nwritten; + + h = GetStdHandle(STD_OUTPUT_HANDLE); + if (h != NULL && h != INVALID_HANDLE_VALUE) { + + GetConsoleScreenBufferInfo (h, &csbi); + + length = csbi.dwSize.X * csbi.dwSize.Y; + coord.X = 0; + coord.Y = 0; + FillConsoleOutputAttribute (h, attr, length, coord, &nwritten); + } + } + +#else + +// Run a test if outside of acceptable range. + + if (enable_color < 0 || enable_color > MAX_T) { + int t; + for (t = 0; t <= MAX_T; t++) { + text_color_init (t); + printf ("-t %d", t); + if (t) printf (" [white background] "); + printf ("\n"); + printf ("%sBlack ", t_black[t]); + printf ("%sRed ", t_red[t]); + printf ("%sGreen ", t_green[t]); + printf ("%sDark-Green ", t_dark_green[t]); + printf ("%sYellow ", t_yellow[t]); + printf ("%sBlue ", t_blue[t]); + printf ("%sMagenta ", t_magenta[t]); + printf ("%sCyan \n", t_cyan[t]); + } + exit (EXIT_SUCCESS); + } + + g_enable_color = enable_color; + + if (g_enable_color != 0) { + int t = g_enable_color; + + if (t < 0) t = 0; + if (t > MAX_T) t = MAX_T; + + printf ("%s", t_background_white[t]); + printf ("%s", clear_eos); + printf ("%s", t_black[t]); + } +#endif +} + + +#if __WIN32__ + +/* Seems that ANSI.SYS is no longer available. */ + + +void text_color_set ( enum dw_color_e c ) +{ + WORD attr; + HANDLE h; + + if (g_enable_color == 0) { + return; + } + + switch (c) { + + default: + case DW_COLOR_INFO: + attr = BACKGROUND_WHITE; + break; + + case DW_COLOR_ERROR: + attr = FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_WHITE; + break; + + case DW_COLOR_REC: + // Release 1.6. Dark green, same as for debug. + // Bright green is too hard to see with white background, + // attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_WHITE; + attr = FOREGROUND_GREEN | BACKGROUND_WHITE; + break; + + case DW_COLOR_DECODED: + attr = FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_WHITE; + break; + + case DW_COLOR_XMIT: + attr = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_WHITE; + break; + + case DW_COLOR_DEBUG: + attr = FOREGROUND_GREEN | BACKGROUND_WHITE; + break; + } + + h = GetStdHandle(STD_OUTPUT_HANDLE); + + if (h != NULL && h != INVALID_HANDLE_VALUE) { + SetConsoleTextAttribute (h, attr); + } +} + +#else + +void text_color_set ( enum dw_color_e c ) +{ + + if (g_enable_color == 0) { + return; + } + + int t = g_enable_color; + + if (t < 0) t = 0; + if (t > MAX_T) t = MAX_T; + + switch (c) { + + default: + case DW_COLOR_INFO: + printf ("%s", t_black[t]); + break; + + case DW_COLOR_ERROR: + printf ("%s", t_red[t]); + break; + + case DW_COLOR_REC: + // Bright green is very difficult to read against a while background. + // Let's use dark green instead. release 1.6. + //printf ("%s", t_green[t]); + printf ("%s", t_dark_green[t]); + break; + + case DW_COLOR_DECODED: + printf ("%s", t_blue[t]); + break; + + case DW_COLOR_XMIT: + printf ("%s", t_magenta[t]); + break; + + case DW_COLOR_DEBUG: + printf ("%s", t_dark_green[t]); + break; + } +} + +#endif + + +/*------------------------------------------------------------------- + * + * Name: dw_printf + * + * Purpose: printf replacement that allows us to send all text + * output to stdout or other desired destination. + * + * Inputs: fmt - C language format. + * ... - Addtional arguments, just like printf. + * + * + * Returns: Number of characters in result. + * + * Bug: Fixed size buffer. + * I'd rather not do a malloc for each print. + * + *--------------------------------------------------------------------*/ + + +// TODO: replace all printf, look for stderr, perror +// TODO: $ grep printf *.c | grep -v dw_printf | grep -v fprintf | gawk '{ print $1 }' | sort -u + + +int dw_printf (const char *fmt, ...) +{ +#define BSIZE 1000 + va_list args; + char buffer[BSIZE]; + int len; + + va_start (args, fmt); + len = vsnprintf (buffer, BSIZE, fmt, args); + va_end (args); + +// TODO: other possible destinations... + + fputs (buffer, stdout); + return (len); +} + + + +#if TESTC +main () +{ + printf ("Initial condition\n"); + text_color_init (1); + printf ("After text_color_init\n"); + text_color_set(DW_COLOR_INFO); printf ("Info\n"); + text_color_set(DW_COLOR_ERROR); printf ("Error\n"); + text_color_set(DW_COLOR_REC); printf ("Rec\n"); + text_color_set(DW_COLOR_DECODED); printf ("Decoded\n"); + text_color_set(DW_COLOR_XMIT); printf ("Xmit\n"); + text_color_set(DW_COLOR_DEBUG); printf ("Debug\n"); +} +#endif + +/* end textcolor.c */ diff --git a/textcolor.h b/src/textcolor.h similarity index 100% rename from textcolor.h rename to src/textcolor.h diff --git a/tq.c b/src/tq.c similarity index 91% rename from tq.c rename to src/tq.c index 79bdcf9..0cc4bec 100644 --- a/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].valid) { + if (audio_config_p->achan[c].medium == 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].valid) { + if (audio_config_p->achan[c].medium == MEDIUM_RADIO) { err = pthread_cond_init (&(wake_up_cond[c]), NULL); if (err != 0) { text_color_set(DW_COLOR_ERROR); @@ -247,11 +247,15 @@ void tq_append (int chan, int prio, packet_t pp) } #endif - if (chan < 0 || chan >= MAX_CHANS || ! save_audio_config_p->achan[chan].valid) { + if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->achan[chan].medium == 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"); - dw_printf ("AX.25 for Linux is known to transmit on channels 2 & 8 sometimes when it shouldn't.\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 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 ("\n"); ax25_delete(pp); return; } @@ -447,9 +451,13 @@ void lm_data_request (int chan, int prio, packet_t pp) } #endif - if (chan < 0 || chan >= MAX_CHANS || ! save_audio_config_p->achan[chan].valid) { + if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->achan[chan].medium != 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); + dw_printf ("Connected packet mode is allowed only with internal modems.\n"); + dw_printf ("Why aren't external KISS modems allowed? See\n"); + dw_printf ("Why-is-9600-only-twice-as-fast-as-1200.pdf for explanation.\n"); ax25_delete(pp); return; } @@ -600,9 +608,14 @@ void lm_seize_request (int chan) dw_printf ("lm_seize_request (chan=%d)\n", chan); #endif - if (chan < 0 || chan >= MAX_CHANS || ! save_audio_config_p->achan[chan].valid) { + + if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->achan[chan].medium != 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); + dw_printf ("Connected packet mode is allowed only with internal modems.\n"); + dw_printf ("Why aren't external KISS modems allowed? See\n"); + dw_printf ("Why-is-9600-only-twice-as-fast-as-1200.pdf for explanation.\n"); return; } @@ -930,11 +943,17 @@ static int tq_is_empty (int chan) * *--------------------------------------------------------------------*/ +//#define DEBUG2 1 + + int tq_count (int chan, int prio, char *source, char *dest, int bytes) { - packet_t pp; - int n; + +#if DEBUG2 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("tq_count(chan=%d, prio=%d, source=\"%s\", dest=\"%s\", bytes=%d)\n", chan, prio, source, dest, bytes); +#endif if (prio == -1) { return (tq_count(chan, TQ_PRIO_0_HI, source, dest, bytes) @@ -950,6 +969,10 @@ int tq_count (int chan, int prio, char *source, char *dest, int bytes) } if (queue_head[chan][prio] == 0) { +#if DEBUG2 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("tq_count: queue chan %d, prio %d is empty, returning 0.\n", chan, prio); +#endif return (0); } @@ -957,19 +980,33 @@ int tq_count (int chan, int prio, char *source, char *dest, int bytes) dw_mutex_lock (&tq_mutex); - n = 0; - pp = queue_head[chan][prio]; + int n = 0; // Result. Number of bytes or packets. + packet_t pp = queue_head[chan][prio];; + while (pp != NULL) { + if (ax25_get_num_addr(pp) >= AX25_MIN_ADDRS) { + // Consider only real packets. + int count_it = 1; if (source != NULL && *source != '\0') { char frame_source[AX25_MAX_ADDR_LEN]; ax25_get_addr_with_ssid (pp, AX25_SOURCE, frame_source); +#if DEBUG2 + // I'm cringing at the thought of printing while in a critical region. But it's only for temp debug. :-( + text_color_set(DW_COLOR_DEBUG); + dw_printf ("tq_count: compare to frame source %s\n", frame_source); +#endif if (strcmp(source,frame_source) != 0) count_it = 0; } if (count_it && dest != NULL && *dest != '\0') { char frame_dest[AX25_MAX_ADDR_LEN]; ax25_get_addr_with_ssid (pp, AX25_DESTINATION, frame_dest); +#if DEBUG2 + // I'm cringing at the thought of printing while in a critical region. But it's only for debug debug. :-( + text_color_set(DW_COLOR_DEBUG); + dw_printf ("tq_count: compare to frame destination %s\n", frame_dest); +#endif if (strcmp(dest,frame_dest) != 0) count_it = 0; } @@ -981,12 +1018,13 @@ int tq_count (int chan, int prio, char *source, char *dest, int bytes) n++; } } - pp = ax25_get_nextp(pp); + } + pp = ax25_get_nextp(pp); } dw_mutex_unlock (&tq_mutex); -#if DEBUG +#if DEBUG2 text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_count(%d, %d, \"%s\", \"%s\", %d) returns %d\n", chan, prio, source, dest, bytes, n); #endif diff --git a/tq.h b/src/tq.h similarity index 100% rename from tq.h rename to src/tq.h diff --git a/tt_text.c b/src/tt_text.c similarity index 100% rename from tt_text.c rename to src/tt_text.c diff --git a/tt_text.h b/src/tt_text.h similarity index 100% rename from tt_text.h rename to src/tt_text.h diff --git a/tt_user.c b/src/tt_user.c similarity index 100% rename from tt_user.c rename to src/tt_user.c diff --git a/tt_user.h b/src/tt_user.h similarity index 100% rename from tt_user.h rename to src/tt_user.h diff --git a/ttcalc.c b/src/ttcalc.c similarity index 99% rename from ttcalc.c rename to src/ttcalc.c index 9c5b4e4..51952b9 100644 --- a/ttcalc.c +++ b/src/ttcalc.c @@ -59,7 +59,7 @@ #include #include #include -#include +#include #endif #include diff --git a/tune.h b/src/tune.h similarity index 100% rename from tune.h rename to src/tune.h diff --git a/utm2ll.c b/src/utm2ll.c similarity index 100% rename from utm2ll.c rename to src/utm2ll.c diff --git a/src/version.h b/src/version.h new file mode 100644 index 0000000..a09490c --- /dev/null +++ b/src/version.h @@ -0,0 +1,21 @@ + +/* Dire Wolf version 1.6 */ + +// Put in destination field to identify the equipment used. + +#define APP_TOCALL "APDW" // Assigned by WB4APR in tocalls.txt + +// This now comes from compile command line options. + +//#define MAJOR_VERSION 1 +//#define MINOR_VERSION 6 +//#define EXTRA_VERSION "Beta Test" + + +// For user-defined data format. +// APRS protocol spec Chapter 18 and http://www.aprs.org/aprs11/expfmts.txt + +#define USER_DEF_USER_ID 'D' // user id D for direwolf + +#define USER_DEF_TYPE_AIS 'A' // data type A for AIS NMEA sentence +#define USER_DEF_TYPE_EAS 'E' // data type E for EAS broadcasts diff --git a/walk96.c b/src/walk96.c similarity index 97% rename from walk96.c rename to src/walk96.c index dfad612..9fc791f 100644 --- a/walk96.c +++ b/src/walk96.c @@ -86,7 +86,7 @@ int main (int argc, char *argv[]) // USB GPS happens to be COM22 memset (&config, 0, sizeof(config)); - strlcpy (config.gpsnmea_port, "COM22", sizeof(config.nmea_port)); + strlcpy (config.gpsnmea_port, "COM22", sizeof(config.gpsnmea_port)); dwgps_init (&config, debug_gps); @@ -152,7 +152,7 @@ static void walk96 (int fix, double lat, double lon, float knots, float course, info_len = encode_position (messaging, compressed, - lat, lon, (int)(DW_METERS_TO_FEET(alt)), + lat, lon, 0, (int)(DW_METERS_TO_FEET(alt)), '/', '=', G_UNKNOWN, G_UNKNOWN, G_UNKNOWN, "", // PHGd (int)roundf(course), (int)roundf(knots), diff --git a/waypoint.c b/src/waypoint.c similarity index 77% rename from waypoint.c rename to src/waypoint.c index 1ec7712..c06b362 100644 --- a/waypoint.c +++ b/src/waypoint.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2014, 2015, 2016 John Langner, WB2OSZ +// Copyright (C) 2014, 2015, 2016, 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 @@ -32,16 +32,21 @@ #include "direwolf.h" // should be first - #include #include +#include #if __WIN32__ -#include +#include +#include // _WIN32_WINNT must be set to 0x0501 before including this #else -#include #include -#include +#include +#include +#include +#include +//#include +#include // gethostbyname #endif #include @@ -57,13 +62,16 @@ #include "mgn_icon.h" /* Magellan icons */ #include "dwgpsnmea.h" #include "serial_port.h" +#include "dwsock.h" -static MYFDTYPE s_waypoint_port_fd = MYFDERROR; +static MYFDTYPE s_waypoint_serial_port_fd = MYFDERROR; +static int s_waypoint_udp_sock_fd = -1; // ideally INVALID_SOCKET for Windows. +static struct sockaddr_in s_udp_dest_addr; static int s_waypoint_formats = 0; /* which formats should we generate? */ -static int s_waypoint_debug = 0; /* Print information flowing to attached device. */ +static int s_waypoint_debug = 0; /* Print information flowing to attached device. */ @@ -86,19 +94,24 @@ void waypoint_set_debug (int n) * * Inputs: mc - Pointer to configuration options. * - * ->waypoint_port - Name of serial port. COM1, /dev/ttyS0, etc. + * ->waypoint_serial_port - Name of serial port. COM1, /dev/ttyS0, etc. * + * ->waypoint_udp_hostname - Destination host when using UDP. + * + * ->waypoint_udp_portnum - UDP port number. * * (currently none) - speed, baud. Default 4800 if not set * * * ->waypoint_formats - Set of formats enabled. - * If none set, default to generic & Kenwood. + * If none set, default to generic & Kenwood here. * - * Global output: s_waypoint_port_fd + * Global output: s_waypoint_serial_port_fd + * s_waypoint_udp_sock_fd * * Description: First to see if this is shared with GPS input. * If not, open serial port. + * In version 1.6 UDP is added. It is possible to use both. * * Restriction: MUST be done after GPS init because we might be sharing the * same serial port device. @@ -111,46 +124,79 @@ void waypoint_init (struct misc_config_s *mc) #if DEBUG text_color_set (DW_COLOR_DEBUG); - dw_printf ("waypoint_init() device=%s formats=%d\n", mc->waypoint_port, mc->waypoint_formats); + dw_printf ("waypoint_init() serial device=%s formats=%02x\n", mc->waypoint_serial_port, mc->waypoint_formats); + dw_printf ("waypoint_init() destination hostname=%s UDP port=%d\n", mc->waypoint_udp_hostname, mc->waypoint_udp_portnum); #endif - + + s_waypoint_udp_sock_fd = -1; + + if (mc->waypoint_udp_portnum > 0) { + + s_waypoint_udp_sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (s_waypoint_udp_sock_fd != -1) { + + // Not thread-safe. Should use getaddrinfo instead. + struct hostent *hp = gethostbyname(mc->waypoint_udp_hostname); + + if (hp != NULL) { + memset ((char *)&s_udp_dest_addr, 0, sizeof(s_udp_dest_addr)); + s_udp_dest_addr.sin_family = AF_INET; + memcpy ((char *)&s_udp_dest_addr.sin_addr, (char *)hp->h_addr, hp->h_length); + s_udp_dest_addr.sin_port = htons(mc->waypoint_udp_portnum); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Waypoint: Couldn't get address for %s\n", mc->waypoint_udp_hostname); + close (s_waypoint_udp_sock_fd); + s_waypoint_udp_sock_fd = -1; + } + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Couldn't create socket for waypoint send to %s\n", mc->waypoint_udp_hostname); + } + } + /* * TODO: * Are we sharing with GPS input? * First try to get fd if they have same device name. * If that fails, do own serial port open. */ - if (strlen(mc->waypoint_port) > 0) { + s_waypoint_serial_port_fd = MYFDERROR; - s_waypoint_port_fd = dwgpsnmea_get_fd (mc->waypoint_port, 4800); + if (strlen(mc->waypoint_serial_port) > 0) { - if (s_waypoint_port_fd == MYFDERROR) { - s_waypoint_port_fd = serial_port_open (mc->waypoint_port, 4800); + s_waypoint_serial_port_fd = dwgpsnmea_get_fd (mc->waypoint_serial_port, 4800); + + if (s_waypoint_serial_port_fd == MYFDERROR) { + s_waypoint_serial_port_fd = serial_port_open (mc->waypoint_serial_port, 4800); } else { text_color_set (DW_COLOR_INFO); dw_printf ("Note: Sharing same port for GPS input and waypoint output.\n"); } - if (s_waypoint_port_fd == MYFDERROR) { + if (s_waypoint_serial_port_fd == MYFDERROR) { text_color_set (DW_COLOR_ERROR); - dw_printf ("Unable to open %s for waypoint output.\n", mc->waypoint_port); - return; - } - - s_waypoint_formats = mc->waypoint_formats; - if (s_waypoint_formats == 0) { - s_waypoint_formats = WPT_FORMAT_NMEA_GENERIC | WPT_FORMAT_KENWOOD; - } - if (s_waypoint_formats & WPT_FORMAT_GARMIN) { - s_waypoint_formats |= WPT_FORMAT_NMEA_GENERIC; /* See explanation below. */ + dw_printf ("Unable to open serial port %s for waypoint output.\n", mc->waypoint_serial_port); } } +// Set default formats if user did not specify any. + + s_waypoint_formats = mc->waypoint_formats; + if (s_waypoint_formats == 0) { + s_waypoint_formats = WPT_FORMAT_NMEA_GENERIC | WPT_FORMAT_KENWOOD; + } + if (s_waypoint_formats & WPT_FORMAT_GARMIN) { + s_waypoint_formats |= WPT_FORMAT_NMEA_GENERIC; /* See explanation below. */ + } #if DEBUG text_color_set (DW_COLOR_DEBUG); - dw_printf ("end of waypoint_init: s_waypoint_port_fd = %d\n", s_waypoint_port_fd); + dw_printf ("end of waypoint_init: s_waypoint_serial_port_fd = %d\n", s_waypoint_serial_port_fd); + dw_printf ("end of waypoint_init: s_waypoint_udp_sock_fd = %d\n", s_waypoint_udp_sock_fd); #endif } @@ -252,6 +298,12 @@ void waypoint_send_sentence (char *name_in, double dlat, double dlong, char symt dw_printf ("waypoint_send_sentence (\"%s\", \"%c%c\")\n", name_in, symtab, symbol); #endif +// Don't waste time if no destintations specified. + + if (s_waypoint_serial_port_fd == MYFDERROR && + s_waypoint_udp_sock_fd == -1) { + return; + } /* * We need to remove any , or * from name, symbol, or comment because they are field delimiters. @@ -583,29 +635,59 @@ void waypoint_send_sentence (char *name_in, double dlat, double dlong, char symt } /* end waypoint_send_sentence */ +/*------------------------------------------------------------------- + * + * Name: nema_send_ais + * + * Purpose: Send NMEA AIS sentence to GPS display or other mapping application. + * + * Inputs: sentence - should look something like this, with checksum, and no CR LF. + * + * !AIVDM,1,1,,A,35NO=dPOiAJriVDH@94E84AJ0000,0*4B + * + *--------------------------------------------------------------------*/ + +void waypoint_send_ais (char *sentence) +{ + if (s_waypoint_serial_port_fd == MYFDERROR && + s_waypoint_udp_sock_fd == -1) { + return; + } + + if (s_waypoint_formats & WPT_FORMAT_AIS) { + send_sentence (sentence); + } +} + + /* * Append CR LF and send it. */ static void send_sentence (char *sent) { - char final[256]; - - if (s_waypoint_port_fd == MYFDERROR) { - return; - } - if (s_waypoint_debug) { text_color_set(DW_COLOR_XMIT); - dw_printf ("%s\n", sent); + dw_printf ("waypoint send sentence: \"%s\"\n", sent); } strlcpy (final, sent, sizeof(final)); strlcat (final, "\r\n", sizeof(final)); + int final_len = strlen(final); - serial_port_write (s_waypoint_port_fd, final, strlen(final)); + if (s_waypoint_serial_port_fd != MYFDERROR) { + serial_port_write (s_waypoint_serial_port_fd, final, final_len); + } + + if (s_waypoint_udp_sock_fd != -1) { + int n = sendto(s_waypoint_udp_sock_fd, final, final_len, 0, (struct sockaddr*)(&s_udp_dest_addr), sizeof(struct sockaddr_in)); + if (n != final_len) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Failed to send waypoint via UDP, errno=%d\n", errno); + } + } } /* send_sentence */ @@ -613,10 +695,13 @@ static void send_sentence (char *sent) void waypoint_term (void) { - - if (s_waypoint_port_fd != MYFDERROR) { + if (s_waypoint_serial_port_fd != MYFDERROR) { //serial_port_close (s_waypoint_port_fd); - s_waypoint_port_fd = MYFDERROR; + s_waypoint_serial_port_fd = MYFDERROR; + } + if (s_waypoint_udp_sock_fd != -1) { + close (s_waypoint_udp_sock_fd); + s_waypoint_udp_sock_fd = -1; } } diff --git a/waypoint.h b/src/waypoint.h similarity index 91% rename from waypoint.h rename to src/waypoint.h index 0f5ef3a..3ba6f1c 100644 --- a/waypoint.h +++ b/src/waypoint.h @@ -16,6 +16,8 @@ void waypoint_set_debug (int n); void waypoint_send_sentence (char *wname_in, double dlat, double dlong, char symtab, char symbol, float alt, float course, float speed, char *comment_in); +void waypoint_send_ais (char *sentence); + void waypoint_term (); diff --git a/xid.c b/src/xid.c similarity index 99% rename from xid.c rename to src/xid.c index a96b89a..617720c 100644 --- a/xid.c +++ b/src/xid.c @@ -665,6 +665,9 @@ int main (int argc, char *argv[]) { text_color_set (DW_COLOR_ERROR); +#ifdef NDEBUG +#error "This won't work properly if NDEBUG is defined. It should be undefined in direwolf.h" +#endif assert (n==1); assert (param.full_duplex == 0); assert (param.srej == srej_single); diff --git a/xid.h b/src/xid.h similarity index 100% rename from xid.h rename to src/xid.h diff --git a/xmit.c b/src/xmit.c similarity index 94% rename from xmit.c rename to src/xmit.c index 19a9ad0..6a725a7 100644 --- a/xmit.c +++ b/src/xmit.c @@ -60,6 +60,7 @@ #include #include #include +#include #include "direwolf.h" #include "ax25_pad.h" @@ -75,7 +76,7 @@ #include "dtmf.h" #include "xid.h" #include "dlq.h" - +#include "server.h" /* @@ -277,10 +278,9 @@ void xmit_init (struct audio_s *p_modem, int debug_xmit_packet) for (j=0; jachan[j].valid) { - + if (p_modem->achan[j].medium == MEDIUM_RADIO) { #if __WIN32__ - xmit_th[j] = (HANDLE)_beginthreadex (NULL, 0, xmit_thread, (void*)(long)j, 0, NULL); + xmit_th[j] = (HANDLE)_beginthreadex (NULL, 0, xmit_thread, (void*)(ptrdiff_t)j, 0, NULL); if (xmit_th[j] == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Could not create xmit thread %d\n", j); @@ -311,10 +311,10 @@ void xmit_init (struct audio_s *p_modem, int debug_xmit_packet) perror("pthread_attr_setschedparam"); } - e = pthread_create (&(xmit_tid[j]), &attr, xmit_thread, (void *)(long)j); + e = pthread_create (&(xmit_tid[j]), &attr, xmit_thread, (void *)(ptrdiff_t)j); pthread_attr_destroy (&attr); #else - e = pthread_create (&(xmit_tid[j]), NULL, xmit_thread, (void *)(long)j); + e = pthread_create (&(xmit_tid[j]), NULL, xmit_thread, (void *)(ptrdiff_t)j); #endif if (e != 0) { text_color_set(DW_COLOR_ERROR); @@ -511,7 +511,7 @@ static unsigned __stdcall xmit_thread (void *arg) static void * xmit_thread (void *arg) #endif { - int chan = (int)(long)arg; // channel number. + int chan = (int)(ptrdiff_t)arg; // channel number. packet_t pp; int prio; int ok; @@ -522,7 +522,7 @@ static void * xmit_thread (void *arg) tq_wait_while_empty (chan); #if DEBUG text_color_set(DW_COLOR_DEBUG); - dw_printf ("xmit_thread, channel %d: woke up\n", c); + dw_printf ("xmit_thread, channel %d: woke up\n", chan); #endif // Does this extra loop offer any benefit? @@ -751,11 +751,10 @@ static void xmit_ax25_frames (int chan, int prio, packet_t pp, int max_bundle) #if DEBUG text_color_set(DW_COLOR_DEBUG); - dw_printf ("xmit_thread: Turn on PTT now for channel %d. speed = %d\n", chan, xmit_bits_per_sec[chan]); + dw_printf ("xmit_thread: t=%.3f, Turn on PTT now for channel %d. speed = %d\n", dtime_now()-time_ptt, chan, xmit_bits_per_sec[chan]); #endif ptt_set (OCTYPE_PTT, chan, 1); - // Inform data link state machine that we are now transmitting. dlq_seize_confirm (chan); // C4.2. "This primitive indicates, to the Data-link State @@ -765,7 +764,8 @@ static void xmit_ax25_frames (int chan, int prio, packet_t pp, int max_bundle) num_bits = hdlc_send_flags (chan, pre_flags, 0); #if DEBUG text_color_set(DW_COLOR_DEBUG); - dw_printf ("xmit_thread: txdelay=%d [*10], pre_flags=%d, num_bits=%d\n", xmit_txdelay[chan], pre_flags, num_bits); + 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); + double presleep = dtime_now(); #endif SLEEP_MS (10); // Give data link state machine a chance to @@ -773,6 +773,16 @@ static void xmit_ax25_frames (int chan, int prio, packet_t pp, int max_bundle) // in response to dlq_seize_confirm, so // we don't run off the end too soon. +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + // How long did sleep last? + dw_printf ("xmit_thread: t=%.3f, Should be 0.010 second after the above.\n", dtime_now()-time_ptt); + double naptime = dtime_now() - presleep; + if (naptime > 0.015) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Sleep for 10 ms actually took %.3f second!\n", naptime); + } +#endif /* * Transmit the frame. @@ -784,7 +794,7 @@ static void xmit_ax25_frames (int chan, int prio, packet_t pp, int max_bundle) if (nb > 0) numframe++; #if DEBUG text_color_set(DW_COLOR_DEBUG); - dw_printf ("xmit_thread: flen=%d, nb=%d, num_bits=%d, numframe=%d\n", flen, nb, num_bits, numframe); + dw_printf ("xmit_thread: t=%.3f, nb=%d, num_bits=%d, numframe=%d\n", dtime_now()-time_ptt, nb, num_bits, numframe); #endif ax25_delete (pp); @@ -826,7 +836,7 @@ static void xmit_ax25_frames (int chan, int prio, packet_t pp, int max_bundle) pp = tq_remove (chan, prio); #if DEBUG text_color_set(DW_COLOR_DEBUG); - dw_printf ("xmit_thread: tq_remove(chan=%d, prio=%d) returned %p\n", chan, prio, pp); + dw_printf ("xmit_thread: t=%.3f, tq_remove(chan=%d, prio=%d) returned %p\n", dtime_now()-time_ptt, chan, prio, pp); #endif nb = send_one_frame (chan, prio, pp); @@ -835,7 +845,7 @@ static void xmit_ax25_frames (int chan, int prio, packet_t pp, int max_bundle) if (nb > 0) numframe++; #if DEBUG text_color_set(DW_COLOR_DEBUG); - dw_printf ("xmit_thread: flen=%d, nb=%d, num_bits=%d, numframe=%d\n", flen, nb, num_bits, numframe); + dw_printf ("xmit_thread: t=%.3f, nb=%d, num_bits=%d, numframe=%d\n", dtime_now()-time_ptt, nb, num_bits, numframe); #endif ax25_delete (pp); @@ -856,7 +866,7 @@ static void xmit_ax25_frames (int chan, int prio, packet_t pp, int max_bundle) num_bits += nb; #if DEBUG text_color_set(DW_COLOR_DEBUG); - dw_printf ("xmit_thread: txtail=%d [*10], post_flags=%d, nb=%d, num_bits=%d\n", xmit_txtail[chan], post_flags, nb, num_bits); + dw_printf ("xmit_thread: t=%.3f, txtail=%d [*10], post_flags=%d, nb=%d, num_bits=%d\n", dtime_now()-time_ptt, xmit_txtail[chan], post_flags, nb, num_bits); #endif @@ -889,7 +899,7 @@ static void xmit_ax25_frames (int chan, int prio, packet_t pp, int max_bundle) #if DEBUG text_color_set(DW_COLOR_DEBUG); - dw_printf ("xmit_thread: xmit duration=%d, %d already elapsed since PTT, wait %d more\n", duration, already, wait_more ); + dw_printf ("xmit_thread: t=%.3f, xmit duration=%d, %d already elapsed since PTT, wait %d more\n", dtime_now()-time_ptt, duration, already, wait_more ); #endif if (wait_more > 0) { @@ -915,7 +925,7 @@ static void xmit_ax25_frames (int chan, int prio, packet_t pp, int max_bundle) #if DEBUG text_color_set(DW_COLOR_DEBUG); time_now = dtime_now(); - dw_printf ("xmit_thread: Turn off PTT now. Actual time on was %d mS, vs. %d desired\n", (int) ((time_now - time_ptt) * 1000.), duration); + dw_printf ("xmit_thread: t=%.3f, Turn off PTT now. Actual time on was %d mS, vs. %d desired\n", dtime_now()-time_ptt, (int) ((time_now - time_ptt) * 1000.), duration); #endif ptt_set (OCTYPE_PTT, chan, 0); @@ -992,7 +1002,14 @@ 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 + dw_printf ("[%d%c%s%s] ", c, + p==TQ_PRIO_0_HI ? 'H' : 'L', + save_audio_config_p->fx25_xmit_enable ? "F" : "", + ts); +#else dw_printf ("[%d%c%s] ", c, p==TQ_PRIO_0_HI ? 'H' : 'L', ts); +#endif dw_printf ("%s", stemp); /* stations followed by : */ /* Demystify non-APRS. Use same format for received frames in direwolf.c. */ @@ -1057,7 +1074,12 @@ static int send_one_frame (int c, int p, packet_t pp) } } - nb = hdlc_send_frame (c, fbuf, flen, send_invalid_fcs2); + nb = hdlc_send_frame (c, fbuf, flen, send_invalid_fcs2, save_audio_config_p->fx25_xmit_enable); + +// Optionally send confirmation to AGW client app if monitoring enabled. + + server_send_monitored (c, pp, 1); + return (nb); } /* end send_one_frame */ @@ -1147,9 +1169,9 @@ static void xmit_speech (int c, packet_t pp) int xmit_speak_it (char *script, int c, char *orig_msg) { int err; - char cmd[2000]; - char *p; char msg[2000]; + char cmd[sizeof(msg) + 16]; + char *p; /* Remove any quotes because it will mess up command line argument parsing. */ diff --git a/xmit.h b/src/xmit.h similarity index 100% rename from xmit.h rename to src/xmit.h diff --git a/systemd/direwolf.logrotate b/systemd/direwolf.logrotate new file mode 100644 index 0000000..143b608 --- /dev/null +++ b/systemd/direwolf.logrotate @@ -0,0 +1,20 @@ +/var/log/direwolf/stdout /var/log/direwolf/stderr { + missingok + rotate 30 + daily + copytruncate + notifempty + compress + delaycompress + dateext + dateyesterday + } + +/var/log/direwolf/*.log { + missingok + daily + rotate 30 + minage 7 + maxage 30 + compress +} diff --git a/systemd/direwolf.service b/systemd/direwolf.service new file mode 100644 index 0000000..d4109a0 --- /dev/null +++ b/systemd/direwolf.service @@ -0,0 +1,24 @@ +[Unit] +Description=Direwolf Sound Card-based AX.25 TNC +After=sound.target + +[Service] +EnvironmentFile=/etc/sysconfig/direwolf +User=direwolf +# You may want to set the audio levels of your radio-connected soundcard +# prior to starting direwolf. To do so, copy this file to /etc/systemd/system/ +# and edit the ExecStartPre line to point to your preferred method of +# doing so. Then run systemctl daemon-reload so systemd uses your updated +# copy of this service file. +#ExecStartPre=/some/script.sh +ExecStart=/bin/bash -ce "exec /usr/bin/direwolf $DIREWOLF_ARGS >>/var/log/direwolf/stdout 2>>/var/log/direwolf/stderr" +Restart=always +StandardOutput=null +StandardError=null +ProtectSystem=strict +ProtectHome=true +ReadWritePaths=/var/log/direwolf + +[Install] +WantedBy=multi-user.target +DefaultInstance=1 diff --git a/systemd/direwolf.sysconfig b/systemd/direwolf.sysconfig new file mode 100644 index 0000000..748dfe7 --- /dev/null +++ b/systemd/direwolf.sysconfig @@ -0,0 +1,2 @@ +# Set direwolf command line arguments here +DIREWOLF_ARGS="-l /var/log/direwolf -c /etc/direwolf.conf" diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..6d4336e --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,701 @@ +# This is a trick to avoid more complication +# because configure_file() is done at configuration time + +set(CUSTOM_TEST_BINARY_DIR "${CMAKE_BINARY_DIR}/test") +set(GEN_PACKETS_BIN "${CMAKE_BINARY_DIR}/src/gen_packets${CMAKE_EXECUTABLE_SUFFIX}") +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}") + +if(WIN32) + set(CUSTOM_SCRIPT_SUFFIX ".bat") +else() + set(CUSTOM_SCRIPT_SUFFIX "") +endif() + +set(TEST_CHECK-FX25_FILE "check-fx25") +set(TEST_CHECK-MODEM1200_FILE "check-modem1200") +set(TEST_CHECK-MODEM300_FILE "check-modem300") +set(TEST_CHECK-MODEM9600_FILE "check-modem9600") +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") +set(TEST_CHECK-MODEM2400-g_FILE "check-modem2400-g") +set(TEST_CHECK-MODEM4800_FILE "check-modem4800") + +# generate the scripts that run the tests + +configure_file( + "${CUSTOM_TEST_SCRIPTS_DIR}/${TEST_CHECK-FX25_FILE}" + "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-FX25_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-MODEM300_FILE}" + "${CUSTOM_TEST_BINARY_DIR}${CUSTOM_SCRIPT_SUFFIX}" + @ONLY + ) + +configure_file( + "${CUSTOM_TEST_SCRIPTS_DIR}/${TEST_CHECK-MODEM9600_FILE}" + "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM9600_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}" + @ONLY + ) + +configure_file( + "${CUSTOM_TEST_SCRIPTS_DIR}/${TEST_CHECK-MODEM2400-a_FILE}" + "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM2400-a_FILE}${CUSTOM_SCRIPT_SUFFIX}" + @ONLY + ) + +configure_file( + "${CUSTOM_TEST_SCRIPTS_DIR}/${TEST_CHECK-MODEM2400-b_FILE}" + "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM2400-b_FILE}${CUSTOM_SCRIPT_SUFFIX}" + @ONLY + ) + +configure_file( + "${CUSTOM_TEST_SCRIPTS_DIR}/${TEST_CHECK-MODEM2400-g_FILE}" + "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM2400-g_FILE}${CUSTOM_SCRIPT_SUFFIX}" + @ONLY + ) + +configure_file( + "${CUSTOM_TEST_SCRIPTS_DIR}/${TEST_CHECK-MODEM4800_FILE}" + "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM4800_FILE}${CUSTOM_SCRIPT_SUFFIX}" + @ONLY + ) + + +# global includes +# not ideal but not so slow +# otherwise use target_include_directories +include_directories( + ${CUSTOM_SRC_DIR} + ${GPSD_INCLUDE_DIRS} + ${HAMLIB_INCLUDE_DIRS} + ${ALSA_INCLUDE_DIRS} + ${UDEV_INCLUDE_DIRS} + ${PORTAUDIO_INCLUDE_DIRS} + ${CUSTOM_GEOTRANZ_DIR} + ${CMAKE_BINARY_DIR}/src + ) + +if(WIN32 OR CYGWIN) + include_directories( + ${CUSTOM_REGEX_DIR} + ) +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 + ${CUSTOM_SRC_DIR}/digipeater.c + ${CUSTOM_SRC_DIR}/ais.c + ${CUSTOM_SRC_DIR}/dedupe.c + ${CUSTOM_SRC_DIR}/pfilter.c + ${CUSTOM_SRC_DIR}/ax25_pad.c + ${CUSTOM_SRC_DIR}/fcs_calc.c + ${CUSTOM_SRC_DIR}/tq.c + ${CUSTOM_SRC_DIR}/textcolor.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}/telemetry.c + ${CUSTOM_SRC_DIR}/symbols.c + ${CUSTOM_SRC_DIR}/tt_text.c + ) + +if(WIN32 OR CYGWIN) + list(REMOVE_ITEM dtest_SOURCES + ${CUSTOM_SRC_DIR}/dwgpsd.c + ) +endif() + +add_executable(dtest + ${dtest_SOURCES} + ) + +set_target_properties(dtest + PROPERTIES COMPILE_FLAGS "-DDIGITEST -DUSE_REGEX_STATIC" + ) + +target_link_libraries(dtest + ${MISC_LIBRARIES} + ${REGEX_LIBRARIES} + ${GPSD_LIBRARIES} + Threads::Threads + ) + +if(WIN32 OR CYGWIN) + target_link_libraries(dtest ws2_32) +endif() + + +# Unit test for APRStt tone seqence parsing. +list(APPEND ttest_SOURCES + ${CUSTOM_SRC_DIR}/aprs_tt.c + ${CUSTOM_SRC_DIR}/tt_text.c + ${CUSTOM_SRC_DIR}/latlong.c + ${CUSTOM_SRC_DIR}/textcolor.c + ) + +add_executable(ttest + ${ttest_SOURCES} + ) + +set_target_properties(ttest + PROPERTIES COMPILE_FLAGS "-DTT_MAIN" + ) + +target_link_libraries(ttest + ${MISC_LIBRARIES} + ${GEOTRANZ_LIBRARIES} + ) + + +# Unit test for APRStt tone sequence / text conversions. +list(APPEND tttexttest_SOURCES + ${CUSTOM_SRC_DIR}/tt_text.c + ${CUSTOM_SRC_DIR}/textcolor.c + ) + +add_executable(tttexttest + ${tttexttest_SOURCES} + ) + +set_target_properties(tttexttest + PROPERTIES COMPILE_FLAGS "-DTTT_TEST" + ) + +target_link_libraries(tttexttest + ${MISC_LIBRARIES} + ) + + +# Unit test for Packet Filtering. +list(APPEND pftest_SOURCES + ${CUSTOM_SRC_DIR}/pfilter.c + ${CUSTOM_SRC_DIR}/ais.c + ${CUSTOM_SRC_DIR}/ax25_pad.c + ${CUSTOM_SRC_DIR}/textcolor.c + ${CUSTOM_SRC_DIR}/fcs_calc.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}/telemetry.c + ${CUSTOM_SRC_DIR}/tt_text.c + ) + +if(WIN32 OR CYGWIN) + list(REMOVE_ITEM pftest_SOURCES + ${CUSTOM_SRC_DIR}/dwgpsd.c + ) +endif() + +add_executable(pftest + ${pftest_SOURCES} + ) + +set_target_properties(pftest + PROPERTIES COMPILE_FLAGS "-DPFTEST -DUSE_REGEX_STATIC" + ) + +target_link_libraries(pftest + ${MISC_LIBRARIES} + ${REGEX_LIBRARIES} + ${GPSD_LIBRARIES} + Threads::Threads + ) + +if(WIN32 OR CYGWIN) + target_link_libraries(pftest ws2_32) +endif() + +# Unit test for telemetry decoding. +list(APPEND tlmtest_SOURCES + ${CUSTOM_SRC_DIR}/telemetry.c + ${CUSTOM_SRC_DIR}/ax25_pad.c + ${CUSTOM_SRC_DIR}/fcs_calc.c + ${CUSTOM_SRC_DIR}/textcolor.c + ) + +if(WIN32 OR CYGWIN) + list(REMOVE_ITEM tlmtest_SOURCES + ${CUSTOM_SRC_DIR}/dwgpsd.c + ) +endif() + +add_executable(tlmtest + ${tlmtest_SOURCES} + ) + +set_target_properties(tlmtest + PROPERTIES COMPILE_FLAGS "-DTEST -DUSE_REGEX_STATIC" + ) + +target_link_libraries(tlmtest + ${MISC_LIBRARIES} + ${REGEX_LIBRARIES} + ) + +if(WIN32 OR CYGWIN) + target_link_libraries(tlmtest ws2_32) +endif() + + +# Unit test for location coordinate conversion. +list(APPEND lltest_SOURCES + ${CUSTOM_SRC_DIR}/latlong.c + ${CUSTOM_SRC_DIR}/textcolor.c + ) + +add_executable(lltest + ${lltest_SOURCES} + ) + +set_target_properties(lltest + PROPERTIES COMPILE_FLAGS "-DLLTEST" + ) + +target_link_libraries(lltest + ${MISC_LIBRARIES} + ) + + +# Unit test for encoding position & object report. +list(APPEND enctest_SOURCES + ${CUSTOM_SRC_DIR}/encode_aprs.c + ${CUSTOM_SRC_DIR}/latlong.c + ${CUSTOM_SRC_DIR}/textcolor.c + ) + +add_executable(enctest + ${enctest_SOURCES} + ) + +set_target_properties(enctest + PROPERTIES COMPILE_FLAGS "-DEN_MAIN" + ) + +target_link_libraries(enctest + ${MISC_LIBRARIES} + ) + + +# Unit test for KISS encapsulation. +list(APPEND kisstest_SOURCES + ${CUSTOM_SRC_DIR}/kiss_frame.c + ) + +add_executable(kisstest + ${kisstest_SOURCES} + ) + +set_target_properties(kisstest + PROPERTIES COMPILE_FLAGS "-DKISSTEST" + ) + + +# Unit test for constructing frames besides UI. +list(APPEND pad2test_SOURCES + ${CUSTOM_SRC_DIR}/ax25_pad2.c + ${CUSTOM_SRC_DIR}/ax25_pad.c + ${CUSTOM_SRC_DIR}/fcs_calc.c + ${CUSTOM_SRC_DIR}/textcolor.c + ) + +add_executable(pad2test + ${pad2test_SOURCES} + ) + +set_target_properties(pad2test + PROPERTIES COMPILE_FLAGS "-DPAD2TEST -DUSE_REGEX_STATIC" + ) + +target_link_libraries(pad2test + ${MISC_LIBRARIES} + ${REGEX_LIBRARIES} + ) + +if(WIN32 OR CYGWIN) + target_link_libraries(pad2test ws2_32) +endif() + + +# Unit Test for XID frame encode/decode. +list(APPEND xidtest_SOURCES + ${CUSTOM_SRC_DIR}/xid.c + ${CUSTOM_SRC_DIR}/textcolor.c + ) + +add_executable(xidtest + ${xidtest_SOURCES} + ) + +set_target_properties(xidtest + PROPERTIES COMPILE_FLAGS "-DXIDTEST" + ) + +target_link_libraries(xidtest + ${MISC_LIBRARIES} + ) + + +# Unit Test for DTMF encode/decode. +list(APPEND dtmftest_SOURCES + ${CUSTOM_SRC_DIR}/dtmf.c + ${CUSTOM_SRC_DIR}/textcolor.c + ) + +add_executable(dtmftest + ${dtmftest_SOURCES} + ) + +set_target_properties(dtmftest + PROPERTIES COMPILE_FLAGS "-DDTMF_TEST" + ) + +# Unit Test FX.25 algorithm. + +list(APPEND fxsend_SOURCES + ${CUSTOM_SRC_DIR}/fx25_send.c + ${CUSTOM_SRC_DIR}/fx25_encode.c + ${CUSTOM_SRC_DIR}/fx25_init.c + ${CUSTOM_SRC_DIR}/fcs_calc.c + ${CUSTOM_SRC_DIR}/textcolor.c + ) + +add_executable(fxsend + ${fxsend_SOURCES} + ) + +set_target_properties(fxsend + PROPERTIES COMPILE_FLAGS "-DFXTEST" + ) + +list(APPEND fxrec_SOURCES + ${CUSTOM_SRC_DIR}/fx25_rec.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(fxrec + ${fxrec_SOURCES} + ) + +set_target_properties(fxrec + PROPERTIES COMPILE_FLAGS "-DFXTEST" + ) + + +# doing ctest on previous programs + +add_test(dtest dtest) +add_test(ttest ttest) +add_test(tttexttest tttexttest) +add_test(pftest pftest) +add_test(tlmtest tlmtest) +add_test(lltest lltest) +add_test(enctest enctest) +add_test(kisstest kisstest) +add_test(pad2test pad2test) +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-modem1200 "${CUSTOM_TEST_BINARY_DIR}/${TEST_CHECK-MODEM1200_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-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 --------------------------- +if(OPTIONAL_TEST) + + # Unit test for IGate + list(APPEND itest_SOURCES + ${CUSTOM_SRC_DIR}/igate.c + ${CUSTOM_SRC_DIR}/ais.c + ${CUSTOM_SRC_DIR}/ax25_pad.c + ${CUSTOM_SRC_DIR}/fcs_calc.c + ${CUSTOM_SRC_DIR}/mheard.c + ${CUSTOM_SRC_DIR}/pfilter.c + ${CUSTOM_SRC_DIR}/telemetry.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}/textcolor.c + ${CUSTOM_SRC_DIR}/dtime_now.c + ${CUSTOM_SRC_DIR}/latlong.c + ${CUSTOM_SRC_DIR}/tt_text.c + ${CUSTOM_SRC_DIR}/symbols.c + ) + + if(WIN32 OR CYGWIN) + list(REMOVE_ITEM itest_SOURCES + ${CUSTOM_SRC_DIR}/dwgpsd.c + ) + endif() + + add_executable(itest + ${itest_SOURCES} + ) + + set_target_properties(itest + PROPERTIES COMPILE_FLAGS "-DITEST" + ) + + target_link_libraries(itest + ${MISC_LIBRARIES} + ${GPSD_LIBRARIES} + Threads::Threads + ) + + if(WIN32 OR CYGWIN) + target_link_libraries(itest ws2_32) + endif() + + + # For demodulator tweaking experiments. + list(APPEND testagc_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}/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}/telemetry.c + ${CUSTOM_SRC_DIR}/dtime_now.c + ${CUSTOM_SRC_DIR}/latlong.c + ${CUSTOM_SRC_DIR}/tt_text.c + ${CUSTOM_SRC_DIR}/symbols.c + ${CUSTOM_SRC_DIR}/textcolor.c + ) + + if(WIN32 OR CYGWIN) + list(REMOVE_ITEM testagc_SOURCES + ${CUSTOM_SRC_DIR}/dwgpsd.c + ) + endif() + + add_executable(testagc + ${testagc_SOURCES} + ) + + target_link_libraries(testagc + ${MISC_LIBRARIES} + ${GPSD_LIBRARIES} + Threads::Threads + ) + + if(WIN32 OR CYGWIN) + target_link_libraries(testagc ws2_32) + endif() + + + # Send GPS location to KISS TNC each second. + list(APPEND walk96_SOURCES + ${CUSTOM_SRC_DIR}/walk96.c + ${CUSTOM_SRC_DIR}/ais.c + ${CUSTOM_SRC_DIR}/dwgps.c + ${CUSTOM_SRC_DIR}/dwgpsnmea.c + ${CUSTOM_SRC_DIR}/dwgpsd.c + ${CUSTOM_SRC_DIR}/kiss_frame.c + ${CUSTOM_SRC_DIR}/latlong.c + ${CUSTOM_SRC_DIR}/encode_aprs.c + ${CUSTOM_SRC_DIR}/serial_port.c + ${CUSTOM_SRC_DIR}/textcolor.c + ${CUSTOM_SRC_DIR}/ax25_pad.c + ${CUSTOM_SRC_DIR}/fcs_calc.c + ${CUSTOM_SRC_DIR}/xmit.c + ${CUSTOM_SRC_DIR}/xid.c + ${CUSTOM_SRC_DIR}/hdlc_send.c + ${CUSTOM_SRC_DIR}/gen_tone.c + ${CUSTOM_SRC_DIR}/ptt.c + ${CUSTOM_SRC_DIR}/tq.c + ${CUSTOM_SRC_DIR}/hdlc_rec.c + ${CUSTOM_SRC_DIR}/hdlc_rec2.c + ${CUSTOM_SRC_DIR}/rrbb.c + ${CUSTOM_SRC_DIR}/dsp.c + ${CUSTOM_SRC_DIR}/multi_modem.c + ${CUSTOM_SRC_DIR}/demod.c + ${CUSTOM_SRC_DIR}/demod_afsk.c + ${CUSTOM_SRC_DIR}/demod_psk.c + ${CUSTOM_SRC_DIR}/demod_9600.c + ${CUSTOM_SRC_DIR}/server.c + ${CUSTOM_SRC_DIR}/morse.c + ${CUSTOM_SRC_DIR}/dtmf.c + ${CUSTOM_SRC_DIR}/audio_stats.c + ${CUSTOM_SRC_DIR}/dtime_now.c + ${CUSTOM_SRC_DIR}/dlq.c + ) + + if(LINUX) + list(APPEND walk96_SOURCES + ${CUSTOM_SRC_DIR}/audio.c + ) + if(UDEV_FOUND) + list(APPEND walk96_SOURCES + ${CUSTOM_SRC_DIR}/cm108.c + ) + endif() + elseif(WIN32 OR CYGWIN) # windows + list(APPEND walk96_SOURCES + ${CUSTOM_SRC_DIR}/audio_win.c + ) + list(REMOVE_ITEM walk96_SOURCES + ${CUSTOM_SRC_DIR}/dwgpsd.c + ) + else() # macOS freebsd openbsd + list(APPEND walk96_SOURCES + ${CUSTOM_SRC_DIR}/audio_portaudio.c + ) + endif() + + add_executable(walk96 + ${walk96_SOURCES} + ) + + set_target_properties(walk96 + PROPERTIES COMPILE_FLAGS "-DWALK96 -DUSE_REGEX_STATIC" + ) + + target_link_libraries(walk96 + ${MISC_LIBRARIES} + ${REGEX_LIBRARIES} + ${GPSD_LIBRARIES} + ${HAMLIB_LIBRARIES} + ${ALSA_LIBRARIES} + ${PORTAUDIO_LIBRARIES} + ${UDEV_LIBRARIES} + Threads::Threads + ) + + if(WIN32 OR CYGWIN) + target_link_libraries(walk96 ws2_32) + endif() + + + # TODO miss the audio file + + # testagc + # ./atest -P H+ -F 0 ../01_Track_1.wav ../02_Track_2.wav | grep "packets decoded in" >atest.out + + # testagc3 + # ./gen_packets -B 300 -n 100 -o noisy3.wav + # ./atest3 -B 300 -P D -D 3 noisy3.wav | grep "packets decoded in" >atest.out + + # testagc96 + # ./gen_packets -B 9600 -n 100 -o noisy96.wav + # ./atest96 -B 9600 ../walkabout9600c.wav noisy96.wav zzz16.wav zzz16.wav zzz16.wav zzz8.wav zzz8.wav zzz8.wav | grep "packets decoded in" >atest.out + + # testagc24 + # ./atest24 -B 2400 test2400.wav | grep "packets decoded in" >atest.out + + # testagc24mfj + # ./atest24mfj -F 1 -B 2400 ../ref-doc/MFJ-2400-PSK/2k4_short.wav + + # testagc48 + # ./atest48 -B 4800 test4800.wav | grep "packets decoded in" >atest.out +endif() # OPTIONAL_TEST diff --git a/test/scripts/check-fx25 b/test/scripts/check-fx25 new file mode 100755 index 0000000..37df174 --- /dev/null +++ b/test/scripts/check-fx25 @@ -0,0 +1,5 @@ +@CUSTOM_SHELL_SHABANG@ + +@FXSEND_BIN@ +@FXREC_BIN@ + diff --git a/test/scripts/check-modem1200 b/test/scripts/check-modem1200 new file mode 100755 index 0000000..2b97587 --- /dev/null +++ b/test/scripts/check-modem1200 @@ -0,0 +1,5 @@ +@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 diff --git a/test/scripts/check-modem19200 b/test/scripts/check-modem19200 new file mode 100755 index 0000000..ae49e46 --- /dev/null +++ b/test/scripts/check-modem19200 @@ -0,0 +1,7 @@ +@CUSTOM_SHELL_SHABANG@ + +@GEN_PACKETS_BIN@ -r 96000 -B19200 -a 170 -o test19.wav +@ATEST_BIN@ -B19200 -F0 -L4 test19.wav +@GEN_PACKETS_BIN@ -r 96000 -B19200 -n 100 -o test19.wav +@ATEST_BIN@ -B19200 -F0 -L60 -G66 test19.wav +@ATEST_BIN@ -B19200 -F1 -L64 -G69 test19.wav diff --git a/test/scripts/check-modem2400-a b/test/scripts/check-modem2400-a new file mode 100755 index 0000000..33dfebf --- /dev/null +++ b/test/scripts/check-modem2400-a @@ -0,0 +1,5 @@ +@CUSTOM_SHELL_SHABANG@ + +@GEN_PACKETS_BIN@ -B2400 -j -n 100 -o test24-a.wav +@ATEST_BIN@ -B2400 -j -F0 -L76 -G83 test24-a.wav +@ATEST_BIN@ -B2400 -j -F1 -L84 -G89 test24-a.wav diff --git a/test/scripts/check-modem2400-b b/test/scripts/check-modem2400-b new file mode 100755 index 0000000..913a608 --- /dev/null +++ b/test/scripts/check-modem2400-b @@ -0,0 +1,5 @@ +@CUSTOM_SHELL_SHABANG@ + +@GEN_PACKETS_BIN@ -B2400 -J -n 100 -o test24-b.wav +@ATEST_BIN@ -B2400 -J -F0 -L81 -G88 test24-b.wav +@ATEST_BIN@ -B2400 -J -F1 -L86 -G90 test24-b.wav diff --git a/test/scripts/check-modem2400-g b/test/scripts/check-modem2400-g new file mode 100755 index 0000000..da7e233 --- /dev/null +++ b/test/scripts/check-modem2400-g @@ -0,0 +1,4 @@ +@CUSTOM_SHELL_SHABANG@ + +@GEN_PACKETS_BIN@ -B2400 -g -n 100 -o test24-g.wav +@ATEST_BIN@ -B2400 -g -F0 -L99 -G101 test24-g.wav diff --git a/test/scripts/check-modem300 b/test/scripts/check-modem300 new file mode 100755 index 0000000..da37dd2 --- /dev/null +++ b/test/scripts/check-modem300 @@ -0,0 +1,5 @@ +@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 diff --git a/test/scripts/check-modem4800 b/test/scripts/check-modem4800 new file mode 100755 index 0000000..7d511bb --- /dev/null +++ b/test/scripts/check-modem4800 @@ -0,0 +1,5 @@ +@CUSTOM_SHELL_SHABANG@ + +@GEN_PACKETS_BIN@ -B4800 -n 100 -o test48.wav +@ATEST_BIN@ -B4800 -F0 -L68 -G74 test48.wav +@ATEST_BIN@ -B4800 -F1 -L72 -G84 test48.wav diff --git a/test/scripts/check-modem9600 b/test/scripts/check-modem9600 new file mode 100755 index 0000000..fa5f658 --- /dev/null +++ b/test/scripts/check-modem9600 @@ -0,0 +1,7 @@ +@CUSTOM_SHELL_SHABANG@ + +@GEN_PACKETS_BIN@ -B9600 -a 170 -o test96.wav +@ATEST_BIN@ -B9600 -F0 -L4 -G4 test96.wav +@GEN_PACKETS_BIN@ -B9600 -n 100 -o test96.wav +@ATEST_BIN@ -B9600 -F0 -L61 -G65 test96.wav +@ATEST_BIN@ -B9600 -F1 -L62 -G66 test96.wav diff --git a/textcolor.c b/textcolor.c deleted file mode 100644 index e518b93..0000000 --- a/textcolor.c +++ /dev/null @@ -1,392 +0,0 @@ - -// -// This file is part of Dire Wolf, an amateur radio packet TNC. -// -// Copyright (C) 2011, 2012, 2013, 2014 John Langner, WB2OSZ -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - - -/*------------------------------------------------------------------- - * - * Name: textcolor.c - * - * Purpose: Originally this would only set color of text - * and we used printf everywhere. - * Now we also have a printf replacement that can - * be used to redirect all output to the desired place. - * This opens the door to using ncurses, a GUI, or - * running as a daemon. - * - * Description: For Linux and Cygwin use the ANSI escape sequences. - * In earlier versions of Windows, the cmd window and ANSI.SYS - * could interpret this but it doesn't seem to be available - * anymore so we use a different interface. - * - * References: - * http://en.wikipedia.org/wiki/ANSI_escape_code - * http://academic.evergreen.edu/projects/biophysics/technotes/program/ansi_esc.htm - * - * - ->>>> READ THIS PART!!! <<<< - - * - * - * Problem: The ANSI escape sequences, used on Linux, allow 8 basic colors. - * Unfortunately, white is not one of them. We only have dark - * white, also known as light gray. To get brighter colors, - * we need to apply an attribute. On some systems, the bold - * attribute produces a brighter color rather than a bold font. - * On other systems, we need to use the blink attribute to get - * bright colors, including white. However on others, blink - * does actually produce blinking characters. - * - * Several people have also complained that bright green is - * very hard to read against a light background. The current - * implementation does not allow easy user customization of colors. - * - * Currently, the only option is to put "-t 0" on the command - * line to disable all text color. This is more readable but - * makes it harder to distinguish different types of - * information, e.g. received packets vs. error messages. - * - * A few people have suggested ncurses. This needs to - * be investigated for a future version. The foundation has - * already been put in place. All of the printf's should have been - * replaced by dw_printf, defined in this file. All of the - * text output is now being funneled thru this one function - * so it should be easy to send it to the user by some - * other means. - * - *--------------------------------------------------------------------*/ - - -#include "direwolf.h" // Should be first. includes windows.h - -#include -#include -#include - - -#if __WIN32__ - -#define BACKGROUND_WHITE (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY) - - - -#elif __CYGWIN__ /* Cygwin */ - -/* For Cygwin we need "blink" (5) rather than the */ -/* expected bright/bold (1) to get bright white background. */ -/* Makes no sense but I stumbled across that somewhere. */ - -static const char background_white[] = "\e[5;47m"; - -/* Whenever a dark color is used, the */ -/* background is reset and needs to be set again. */ - -static const char black[] = "\e[0;30m" "\e[5;47m"; -static const char red[] = "\e[1;31m"; -static const char green[] = "\e[1;32m"; -static const char yellow[] = "\e[1;33m"; -static const char blue[] = "\e[1;34m"; -static const char magenta[] = "\e[1;35m"; -static const char cyan[] = "\e[1;36m"; -static const char dark_green[] = "\e[0;32m" "\e[5;47m"; - -/* Clear from cursor to end of screen. */ - -static const char clear_eos[] = "\e[0J"; - - -#elif __arm__ /* Linux on Raspberry Pi or similar */ - -/* We need "blink" (5) rather than the */ -/* expected bright/bold (1) to get bright white background. */ -/* Makes no sense but I stumbled across that somewhere. */ - -/* If you do get blinking, remove all references to "\e[5;47m" */ - -static const char background_white[] = "\e[5;47m"; - -/* Whenever a dark color is used, the */ -/* background is reset and needs to be set again. */ - -static const char black[] = "\e[0;30m" "\e[5;47m"; -static const char red[] = "\e[1;31m" "\e[5;47m"; -static const char green[] = "\e[1;32m" "\e[5;47m"; -//static const char yellow[] = "\e[1;33m" "\e[5;47m"; -static const char blue[] = "\e[1;34m" "\e[5;47m"; -static const char magenta[] = "\e[1;35m" "\e[5;47m"; -//static const char cyan[] = "\e[1;36m" "\e[5;47m"; -static const char dark_green[] = "\e[0;32m" "\e[5;47m"; - -/* Clear from cursor to end of screen. */ - -static const char clear_eos[] = "\e[0J"; - - -#else /* Other Linux */ - -#if 1 /* new in version 1.2, as suggested by IW2DHW */ - /* Test done using gnome-terminal and xterm */ - -static const char background_white[] = "\e[48;2;255;255;255m"; - -/* Whenever a dark color is used, the */ -/* background is reset and needs to be set again. */ - - -static const char black[] = "\e[0;30m" "\e[48;2;255;255;255m"; -static const char red[] = "\e[0;31m" "\e[48;2;255;255;255m"; -static const char green[] = "\e[0;32m" "\e[48;2;255;255;255m"; -//static const char yellow[] = "\e[0;33m" "\e[48;2;255;255;255m"; -static const char blue[] = "\e[0;34m" "\e[48;2;255;255;255m"; -static const char magenta[] = "\e[0;35m" "\e[48;2;255;255;255m"; -//static const char cyan[] = "\e[0;36m" "\e[48;2;255;255;255m"; -static const char dark_green[] = "\e[0;32m" "\e[48;2;255;255;255m"; - - -#else /* from version 1.1 */ - - -static const char background_white[] = "\e[47;1m"; - -/* Whenever a dark color is used, the */ -/* background is reset and needs to be set again. */ - -static const char black[] = "\e[0;30m" "\e[1;47m"; -static const char red[] = "\e[1;31m" "\e[1;47m"; -static const char green[] = "\e[1;32m" "\e[1;47m"; -//static const char yellow[] = "\e[1;33m" "\e[1;47m"; -static const char blue[] = "\e[1;34m" "\e[1;47m"; -static const char magenta[] = "\e[1;35m" "\e[1;47m"; -//static const char cyan[] = "\e[1;36m" "\e[1;47m"; -static const char dark_green[] = "\e[0;32m" "\e[1;47m"; - - -#endif - - -/* Clear from cursor to end of screen. */ - -static const char clear_eos[] = "\e[0J"; - -#endif /* end Linux */ - - -#include "textcolor.h" - - -/* - * g_enable_color: - * 0 = disable text colors. - * 1 = normal. - * others... future possibility. - */ - -static int g_enable_color = 1; - - -void text_color_init (int enable_color) -{ - - g_enable_color = enable_color; - - -#if __WIN32__ - - - if (g_enable_color) { - - HANDLE h; - CONSOLE_SCREEN_BUFFER_INFO csbi; - WORD attr = BACKGROUND_WHITE; - DWORD length; - COORD coord; - DWORD nwritten; - - h = GetStdHandle(STD_OUTPUT_HANDLE); - if (h != NULL && h != INVALID_HANDLE_VALUE) { - - GetConsoleScreenBufferInfo (h, &csbi); - - length = csbi.dwSize.X * csbi.dwSize.Y; - coord.X = 0; - coord.Y = 0; - FillConsoleOutputAttribute (h, attr, length, coord, &nwritten); - } - } - -#else - if (g_enable_color) { - //printf ("%s", clear_eos); - printf ("%s", background_white); - printf ("%s", clear_eos); - printf ("%s", black); - } -#endif -} - - -#if __WIN32__ - -/* Seems that ANSI.SYS is no longer available. */ - - -void text_color_set ( enum dw_color_e c ) -{ - WORD attr; - HANDLE h; - - if (g_enable_color == 0) { - return; - } - - switch (c) { - - default: - case DW_COLOR_INFO: - attr = BACKGROUND_WHITE; - break; - - case DW_COLOR_ERROR: - attr = FOREGROUND_RED | FOREGROUND_INTENSITY | BACKGROUND_WHITE; - break; - - case DW_COLOR_REC: - attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_WHITE; - break; - - case DW_COLOR_DECODED: - attr = FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_WHITE; - break; - - case DW_COLOR_XMIT: - attr = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_WHITE; - break; - - case DW_COLOR_DEBUG: - attr = FOREGROUND_GREEN | BACKGROUND_WHITE; - break; - } - - h = GetStdHandle(STD_OUTPUT_HANDLE); - - if (h != NULL && h != INVALID_HANDLE_VALUE) { - SetConsoleTextAttribute (h, attr); - } -} - -#else - -void text_color_set ( enum dw_color_e c ) -{ - - if (g_enable_color == 0) { - return; - } - - switch (c) { - - default: - case DW_COLOR_INFO: - printf ("%s", black); - break; - - case DW_COLOR_ERROR: - printf ("%s", red); - break; - - case DW_COLOR_REC: - printf ("%s", green); - break; - - case DW_COLOR_DECODED: - printf ("%s", blue); - break; - - case DW_COLOR_XMIT: - printf ("%s", magenta); - break; - - case DW_COLOR_DEBUG: - printf ("%s", dark_green); - break; - } -} - -#endif - - -/*------------------------------------------------------------------- - * - * Name: dw_printf - * - * Purpose: printf replacement that allows us to send all text - * output to stdout or other desired destination. - * - * Inputs: fmt - C language format. - * ... - Addtional arguments, just like printf. - * - * - * Returns: Number of characters in result. - * - * Bug: Fixed size buffer. - * I'd rather not do a malloc for each print. - * - *--------------------------------------------------------------------*/ - - -// TODO: replace all printf, look for stderr, perror -// TODO: $ grep printf *.c | grep -v dw_printf | grep -v fprintf | gawk '{ print $1 }' | sort -u - - -int dw_printf (const char *fmt, ...) -{ -#define BSIZE 1000 - va_list args; - char buffer[BSIZE]; - int len; - - va_start (args, fmt); - len = vsnprintf (buffer, BSIZE, fmt, args); - va_end (args); - -// TODO: other possible destinations... - - fputs (buffer, stdout); - return (len); -} - - - -#if TESTC -main () -{ - printf ("Initial condition\n"); - text_color_init (1); - printf ("After text_color_init\n"); - text_color_set(DW_COLOR_INFO); printf ("Info\n"); - text_color_set(DW_COLOR_ERROR); printf ("Error\n"); - text_color_set(DW_COLOR_REC); printf ("Rec\n"); - text_color_set(DW_COLOR_DECODED); printf ("Decoded\n"); - text_color_set(DW_COLOR_XMIT); printf ("Xmit\n"); - text_color_set(DW_COLOR_DEBUG); printf ("Debug\n"); -} -#endif - -/* end textcolor.c */ diff --git a/version.h b/version.h deleted file mode 100644 index 80787b7..0000000 --- a/version.h +++ /dev/null @@ -1,8 +0,0 @@ - -/* Dire Wolf version 1.5 */ - -#define APP_TOCALL "APDW" - -#define MAJOR_VERSION 1 -#define MINOR_VERSION 5 -//#define EXTRA_VERSION "Beta Test"