diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..82c129b0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,170 @@ +name: 'build direwolf' + +on: + # permit to manually trigger the CI + workflow_dispatch: + inputs: + cmake_flags: + description: 'Custom CMAKE flags' + required: false + push: + paths-ignore: + - '.github/**' + pull_request: + paths-ignore: + - '.github/**' + +jobs: + build: + name: ${{ matrix.config.name }} + runs-on: ${{ matrix.config.os }} + strategy: + fail-fast: false + matrix: + config: + - { + name: 'Windows Latest MinGW 64bit', + os: windows-latest, + cc: 'x86_64-w64-mingw32-gcc', + cxx: 'x86_64-w64-mingw32-g++', + ar: 'x86_64-w64-mingw32-ar', + windres: 'x86_64-w64-mingw32-windres', + arch: 'x86_64', + build_type: 'Release', + cmake_extra_flags: '-G "MinGW Makefiles"' + } + - { + name: 'Windows 2019 MinGW 32bit', + os: windows-2019, + cc: 'i686-w64-mingw32-gcc', + cxx: 'i686-w64-mingw32-g++', + ar: 'i686-w64-mingw32-ar', + windres: 'i686-w64-mingw32-windres', + arch: 'i686', + build_type: 'Release', + cmake_extra_flags: '-G "MinGW Makefiles"' + } + - { + name: 'macOS latest', + os: macos-latest, + cc: 'clang', + cxx: 'clang++', + arch: 'x86_64', + build_type: 'Release', + cmake_extra_flags: '' + } + - { + name: 'Ubuntu latest Debug', + os: ubuntu-latest, + cc: 'gcc', + cxx: 'g++', + arch: 'x86_64', + build_type: 'Debug', + cmake_extra_flags: '' + } + - { + name: 'Ubuntu 22.04', + os: ubuntu-22.04, + cc: 'gcc', + cxx: 'g++', + arch: 'x86_64', + build_type: 'Release', + cmake_extra_flags: '' + } + - { + name: 'Ubuntu 20.04', + os: ubuntu-20.04, + cc: 'gcc', + cxx: 'g++', + arch: 'x86_64', + build_type: 'Release', + cmake_extra_flags: '' + } + - { + name: 'Ubuntu 18.04', + os: ubuntu-18.04, + cc: 'gcc', + cxx: 'g++', + arch: 'x86_64', + build_type: 'Release', + cmake_extra_flags: '' + } + steps: + - name: checkout + uses: actions/checkout@v2 + with: + fetch-depth: 8 + - name: dependency + shell: bash + run: | + # this is not perfect but enought for now + if [ "$RUNNER_OS" == "Linux" ]; then + sudo apt-get update + sudo apt-get install libasound2-dev libudev-dev libhamlib-dev gpsd + elif [ "$RUNNER_OS" == "macOS" ]; then + # just to simplify I use homebrew but + # we can use macports (latest direwolf is already available as port) + brew install portaudio hamlib gpsd + elif [ "$RUNNER_OS" == "Windows" ]; then + # add the folder to PATH + echo "C:\msys64\mingw32\bin" >> $GITHUB_PATH + fi + - name: create build environment + run: | + cmake -E make_directory ${{github.workspace}}/build + - name: configure + shell: bash + working-directory: ${{github.workspace}}/build + run: | + if [ "$RUNNER_OS" == "Windows" ]; then + export CC=${{ matrix.config.cc }} + export CXX=${{ matrix.config.cxx }} + export AR=${{ matrix.config.ar }} + export WINDRES=${{ matrix.config.windres }} + fi + cmake $GITHUB_WORKSPACE \ + -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \ + -DCMAKE_C_COMPILER=${{ matrix.config.cc }} \ + -DCMAKE_CXX_COMPILER=${{ matrix.config.cxx }} \ + -DCMAKE_CXX_FLAGS="-Werror" -DUNITTEST=1 \ + ${{ matrix.config.cmake_extra_flags }} \ + ${{ github.event.inputs.cmake_flags }} + - name: build + shell: bash + working-directory: ${{github.workspace}}/build + run: | + if [ "$RUNNER_OS" == "Windows" ]; then + export CC=${{ matrix.config.cc }} + export CXX=${{ matrix.config.cxx }} + export AR=${{ matrix.config.ar }} + export WINDRES=${{ matrix.config.windres }} + fi + cmake --build . --config ${{ matrix.config.build_type }} \ + ${{ github.event.inputs.cmake_flags }} + - name: test + continue-on-error: true + shell: bash + working-directory: ${{github.workspace}}/build + run: | + ctest -C ${{ matrix.config.build_type }} \ + --parallel 2 --output-on-failure \ + ${{ github.event.inputs.cmake_flags }} + - name: package + shell: bash + working-directory: ${{github.workspace}}/build + run: | + if [ "$RUNNER_OS" == "Windows" ] || [ "$RUNNER_OS" == "macOS" ]; then + make package + fi + - name: archive binary + uses: actions/upload-artifact@v2 + with: + name: direwolf_${{ matrix.config.os }}_${{ matrix.config.arch }}_${{ github.sha }} + path: | + ${{github.workspace}}/build/direwolf-*.zip + ${{github.workspace}}/build/direwolf.conf + ${{github.workspace}}/build/src/* + ${{github.workspace}}/build/CMakeCache.txt + !${{github.workspace}}/build/src/cmake_install.cmake + !${{github.workspace}}/build/src/CMakeFiles + !${{github.workspace}}/build/src/Makefile diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..7134f213 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,73 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ dev ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ dev ] + schedule: + - cron: '25 8 * * 4' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'cpp', 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + - run: | + mkdir build + cd build + cmake -DUNITTEST=1 .. + make + make test + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/CHANGES.md b/CHANGES.md index f979b541..474752fd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,9 @@ ### New Features: ### +- Additional documentation location to slow down growth of main repository. [https://github.com/wb2osz/direwolf-doc](https://github.com/wb2osz/direwolf-doc) + +- New ICHANNEL configuration option to map a KISS client application channel to APRS-IS. Packets from APRS-IS will be presented to client applications as the specified channel. Packets sent, by client applications, to that channel will go to APRS-IS rather than a radio channel. Details in ***Internal-Packet-Routing.pdf***. - New variable speed option for gen_packets. For example, "-v 5,0.1" would generate packets from 5% too slow to 5% too fast with increments of 0.1. Some implementations might have imprecise timing. Use this to test how well TNCs tolerate sloppy timing. @@ -26,6 +29,10 @@ > > Add: "FX25TX 1" (or 16 or 32 or 64) +- stdout is now supported for audio output via piping to other utilities. To support this, all non-audio output must be redirected to stderr using the new -O option on the command line. + +- udp audio output is also now supported. Use udp:destination:port style output device in the configuration file. + ## Version 1.6 -- October 2020 ## diff --git a/CMakeLists.txt b/CMakeLists.txt index 61ffc4b4..e44f99b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,15 +167,16 @@ elseif(APPLE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_MACOS_DNSSD") elseif (WIN32) - if(NOT VS2015 AND NOT VS2017) - message(FATAL_ERROR "You must use Microsoft Visual Studio 2015 or 2017 as compiler") + if(C_MSVC) + if (NOT VS2015 AND NOT VS2017 AND NOT VS2019) + message(FATAL_ERROR "You must use Microsoft Visual Studio 2015, 2017 or 2019 as compiler") + else() + # compile with full multicore + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + set(CUSTOM_SHELL_BIN "") + endif() endif() - - # 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) diff --git a/README.md b/README.md index 0bf92daf..3006a1ef 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,11 @@ Why waste $200 and settle for mediocre receive performance from a 1980's technol ![](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. This was originally developed for satellites and is now seeing widespread use on HF. +Dire Wolf includes [FX.25](https://en.wikipedia.org/wiki/FX.25_Forward_Error_Correction) which adds Forward Error Correction (FEC) in a way that is completely compatible with existing systems. If both ends are capable of FX.25, your information will continue to get through under conditions where regular AX.25 is completely useless. This was originally developed for satellites and is now seeing widespread use on HF. ![](fx25.png) -Version 1.7 adds [IL2P](https://en.wikipedia.org/wiki/Improved_Layer_2_Protocol), a different method of FEC with less overhead. +Version 1.7 adds [IL2P](https://en.wikipedia.org/wiki/Improved_Layer_2_Protocol), a different method of FEC with less overhead but it is not compatible with AX.25. @@ -114,12 +114,16 @@ It can also be used as a virtual TNC for other applications such as [APRSIS32](h ## Documentation ## + [Stable Version](https://github.com/wb2osz/direwolf/tree/master/doc) -[Latest Development Version](https://github.com/wb2osz/direwolf/tree/dev/doc) +[Latest Development Version ("dev" branch)](https://github.com/wb2osz/direwolf/tree/dev/doc) + +[Additional Topics](https://github.com/wb2osz/direwolf-doc) -[Power Point presentation](https://github.com/wb2osz/direwolf-presentation) -- Why not give a talk at a local club meeting? +[Power Point presentations](https://github.com/wb2osz/direwolf-presentation) -- Why not give a talk at a local club meeting? +Youtube has many interesting and helpful videos. Searching for [direwolf tnc](https://www.youtube.com/results?search_query=direwolf+tnc) or [direwolf aprs](https://www.youtube.com/results?search_query=direwolf+aprs) will produce the most relevant results. ## Installation ## diff --git a/cmake/modules/FindCompiler.cmake b/cmake/modules/FindCompiler.cmake index f339a73e..fe036e4b 100644 --- a/cmake/modules/FindCompiler.cmake +++ b/cmake/modules/FindCompiler.cmake @@ -5,7 +5,9 @@ elseif(NOT DEFINED C_GCC AND CMAKE_CXX_COMPILER_ID MATCHES "GNU") set(C_GCC 1) elseif(NOT DEFINED C_MSVC AND CMAKE_CXX_COMPILER_ID MATCHES "MSVC") set(C_MSVC 1) - if(MSVC_VERSION GREATER 1910 AND MSVC_VERSION LESS 1919) + if(MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS_EQUAL 1929) + set(VS2019 ON) + elseif(MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS_EQUAL 1919) set(VS2017 ON) elseif(MSVC_VERSION GREATER 1899 AND MSVC_VERSION LESS 1910) set(VS2015 ON) diff --git a/conf/generic.conf b/conf/generic.conf index 887dc229..631824dc 100644 --- a/conf/generic.conf +++ b/conf/generic.conf @@ -29,16 +29,21 @@ %C%# Extensive documentation can be found here: %C%# Stable release - https://github.com/wb2osz/direwolf/tree/master/doc %C%# Latest development - https://github.com/wb2osz/direwolf/tree/dev/doc +%C%# Additional topics - https://github.com/wb2osz/direwolf-doc %C%# -%W%# The complete documentation set can also be found in the doc folder. -%L%# The complete documentation set can also be found in +%W%# The basic documentation set can also be found in the doc folder. +%L%# The basic documentation set can also be found in %L%# /usr/local/share/doc/direwolf/ or /usr/share/doc/direwolf/ %L%# Concise "man" pages are also available for Linux. %M%# /usr/local/share/doc/direwolf/ or /usr/share/doc/direwolf/ %M%# Concise "man" pages are also available for Mac OSX. %C%# +%C%# Questions??? Join the discussion forum: https://groups.io/g/direwolf +%C%# +%C%# %C%# This sample file does not have examples for all of the possibilities. -%C%# Consult the User Guide for more details on configuration options.%C%# +%C%# Consult the User Guide for more details on configuration options +%C%# and other documents for more details for different uses. %C%# %C%# These are the most likely settings you might change: %C%# @@ -127,10 +132,15 @@ %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%# "-" or "stdout" can be used to pipe audio out to another application. +%W%# The -O option must be specified on the command line to support this. +%W%# For UDP output, specify the destination IP address/hostname and port number +%W%# using "UDP:destination:port" syntax %W% %W%# ADEVICE stdin 0 %W%# ADEVICE UDP:7355 0 +%W%# ADEVICE UDP:7355 UDP:localhost:7356 +%W%# ADEVICE stdin stdout %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. @@ -153,10 +163,15 @@ %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%# "-" or "stdout" can be used to pipe audio out to another application. +%L%# The -O option must be specified on the command line to support this. +%L%# For UDP output, specify the destination IP address/hostname and port number +%L%# using "UDP:destination:port" syntax %L% %L%# ADEVICE stdin plughw:1,0 %L%# ADEVICE UDP:7355 default +%L%# ADEVICE UDP:7355 UDP:localhost:7356 +%L%# ADEVICE stdin stdout %L% %R% ---------- Mac ---------- %R% @@ -178,9 +193,14 @@ %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%# "-" or "stdout" can be used to pipe audio out to another application. +%M%# The -O option must be specified on the command line to support this. +%M%# For UDP output, specify the destination IP address/hostname and port number +%M%# using "UDP:destination:port" syntax %M% %M%# ADEVICE UDP:7355 default +%M%# ADEVICE UDP:7355 UDP:localhost:7356 +%M%# ADEVICE stdin stdout %M%# %C% %C%# @@ -231,14 +251,14 @@ %C%# It can be up to 6 letters and digits with an optional ssid. %C%# The APRS specification requires that it be upper case. %C%# -%C%# Example (don't use this unless you are me): MYCALL WB2OSZ-5 +%C%# Example (don't use this unless you are me): MYCALL WB2OSZ-5 %C%# %C% %C%MYCALL N0CALL %C% %C%# %C%# Pick a suitable modem speed based on your situation. -%C%# 1200 Most common for VHF/UHF. Default if not specified. +%C%# 1200 Most common for VHF/UHF. This is the default if not specified. %C%# 2400 QPSK compatible with MFJ-2400, and probably PK232-2400 & KPC-2400. %C%# 300 Low speed for HF SSB. Default tones 1600 & 1800. %C%# EAS Emergency Alert System (EAS) Specific Area Message Encoding (SAME). @@ -263,6 +283,10 @@ %C% %C%#DTMF %C% +%C%# Push to Talk (PTT) can be confusing because there are so many different cases. +%C%# Radio-Interface-Guide.pdf in https://github.com/wb2osz/direwolf-doc +%C%# goes into detail about the various options. +%C% %L%# If using a C-Media CM108/CM119 or similar USB Audio Adapter, %L%# you can use a GPIO pin for PTT control. This is very convenient %L%# because a single USB connection is used for both audio and PTT. @@ -290,6 +314,7 @@ %C%#PTT COM1 RTS %C%#PTT COM1 RTS -DTR %L%#PTT /dev/ttyUSB0 RTS +%L%#PTT /dev/ttyUSB0 RTS -DTR %C% %L%# %L%# On Linux, you can also use general purpose I/O pins if @@ -301,7 +326,7 @@ %L% %L%#PTT GPIO 25 %L% -%C%# The Data Carrier Detect (DCD) signal can be sent to the same places +%C%# The Data Carrier Detect (DCD) signal can be sent to most of the same places %C%# as the PTT signal. This could be used to light up an LED like a normal TNC. %C% %C%#DCD COM1 -DTR @@ -374,7 +399,6 @@ %C%# %C%# 0 - Don't try to repair. %C%# 1 - Attempt to fix single bit error. (default) -%C%# ... see User Guide for more values and in-depth discussion. %C%# %C% %C%#FIX_BITS 0 @@ -399,15 +423,21 @@ %C%# Example: %C%# %C%# This results in a broadcast once every 10 minutes. -%C%# Every half hour, it can travel via two digipeater hops. +%C%# Every half hour, it can travel via one digipeater hop. %C%# The others are kept local. %C%# %C% -%C%#PBEACON delay=1 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" via=WIDE1-1,WIDE2-1 +%C%#PBEACON delay=1 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" via=WIDE1-1 %C%#PBEACON delay=11 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" %C%#PBEACON delay=21 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" %C% -%C% +%C%# +%C%# Did you know that APRS comments and messages can contain UTF-8 characters, not only plain ASCII? +%C%# +%C%#PBEACON delay=1 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W comment=" Did you know that APRS comments and messages can contain UTF-8 characters? \xe0\xb8\xa7\xe0\xb8\xb4\xe0\xb8\x97\xe0\xb8\xa2\xe0\xb8\xb8\xe0\xb8\xaa\xe0\xb8\xa1\xe0\xb8\xb1\xe0\xb8\x84\xe0\xb8\xa3\xe0\xb9\x80\xe0\xb8\xa5\xe0\xb9\x88\xe0\xb8\x99" +%C%#PBEACON delay=11 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W comment=" Did you know that APRS comments and messages can contain UTF-8 characters? \xce\xa1\xce\xb1\xce\xb4\xce\xb9\xce\xbf\xce\xb5\xcf\x81\xce\xb1\xcf\x83\xce\xb9\xcf\x84\xce\xb5\xcf\x87\xce\xbd\xce\xb9\xcf\x83\xce\xbc\xcf\x8c\xcf\x82" +%C%#PBEACON delay=21 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W comment=" Did you know that APRS comments and messages can contain UTF-8 characters? \xe3\x82\xa2\xe3\x83\x9e\xe3\x83\x81\xe3\x83\xa5\xe3\x82\xa2\xe7\x84\xa1\xe7\xb7\x9a" +%C%# %C%# With UTM coordinates instead of latitude and longitude. %C% %C%#PBEACON delay=1 every=10 overlay=S symbol="digi" zone=19T easting=307477 northing=4720178 @@ -448,12 +478,6 @@ %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. @@ -487,6 +511,7 @@ %C%# without sending it over the air and relying on someone else to %C%# forward it to an IGate server. This is done by using sendto=IG rather %C%# than a radio channel number. Overlay R for receive only, T for two way. +%C%# There is no need to send it as often as you would over the radio. %C% %C%#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=R lat=42^37.14N long=071^20.83W %C%#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=T lat=42^37.14N long=071^20.83W @@ -502,6 +527,7 @@ %C%# The IGate function will limit the number of packets transmitted %C%# during 1 minute and 5 minute intervals. If a limit would %C%# be exceeded, the packet is dropped and message is displayed in red. +%C%# This might be low for APRS Thursday when there is abnormally high activity. %C% %C%IGTXLIMIT 6 10 %C% @@ -596,4 +622,4 @@ %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 +%C% diff --git a/man/direwolf.1 b/man/direwolf.1 index 93f786dc..aee9ee2b 100644 --- a/man/direwolf.1 +++ b/man/direwolf.1 @@ -155,6 +155,9 @@ x = Silence FX.25 information. .BI "-t " "n" Text colors. 0=disabled. 1=default. 2,3,4,... alternatives. Use 9 to test compatibility with your terminal. +.TP +.BI "-O " +Redirects all printed output to stderr so stdout can be used as audio device. .TP .B "-p " diff --git a/src/agwlib.c b/src/agwlib.c index 33d490c8..2c03adaa 100644 --- a/src/agwlib.c +++ b/src/agwlib.c @@ -365,7 +365,7 @@ static void * tnc_listen_thread (void *arg) } /* - * Call to/from fields are 10 bytes but contents must not exceeed 9 characters. + * Call to/from fields are 10 bytes but contents must not exceed 9 characters. * It's not guaranteed that unused bytes will contain 0 so we * don't issue error message in this case. */ diff --git a/src/atest.c b/src/atest.c index aec626f2..54a89b68 100644 --- a/src/atest.c +++ b/src/atest.c @@ -217,7 +217,7 @@ int main (int argc, char *argv[]) } #endif - text_color_init(1); + text_color_init(1, 0); text_color_set(DW_COLOR_INFO); /* diff --git a/src/audio.c b/src/audio.c index b8cf6b11..737247ad 100644 --- a/src/audio.c +++ b/src/audio.c @@ -75,6 +75,7 @@ #include #include #include +#include #include @@ -129,8 +130,11 @@ static struct adev_s { int outbuf_len; enum audio_in_type_e g_audio_in_type; + enum audio_out_type_e g_audio_out_type; - int udp_sock; /* UDP socket for receiving data */ + int udp_in_sock; /* UDP socket for receiving data */ + int udp_out_sock; /* UDP socket for sending data */ + struct sockaddr_storage udp_dest_addr; /* Destination address for UDP socket sending */ } adev[MAX_ADEVS]; @@ -222,8 +226,8 @@ int audio_open (struct audio_s *pa) #endif int chan; int a; - char audio_in_name[30]; - char audio_out_name[30]; + char audio_in_name[80]; + char audio_out_name[80]; save_audio_config_p = pa; @@ -238,7 +242,7 @@ int audio_open (struct audio_s *pa) #else adev[a].oss_audio_device_fd = -1; #endif - adev[a].udp_sock = -1; + adev[a].udp_in_sock = -1; } @@ -354,6 +358,10 @@ int audio_open (struct audio_s *pa) text_color_set(DW_COLOR_ERROR); dw_printf ("Could not open audio device %s for input\n%s\n", audio_in_name, snd_strerror(err)); + if (err == -EBUSY) { + dw_printf ("This means that some other application is using that device.\n"); + dw_printf ("The solution is to identify that other application and stop it.\n"); + } return (-1); } @@ -408,7 +416,7 @@ int audio_open (struct audio_s *pa) //int data_size = 0; //Create UDP Socket - if ((adev[a].udp_sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) { + if ((adev[a].udp_in_sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't create socket, errno %d\n", errno); return -1; @@ -420,7 +428,7 @@ int audio_open (struct audio_s *pa) si_me.sin_addr.s_addr = htonl(INADDR_ANY); //Bind to the socket - if (bind(adev[a].udp_sock, (const struct sockaddr *) &si_me, sizeof(si_me))==-1) { + if (bind(adev[a].udp_in_sock, (const struct sockaddr *) &si_me, sizeof(si_me))==-1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't bind socket, errno %d\n", errno); return -1; @@ -449,48 +457,134 @@ int audio_open (struct audio_s *pa) } /* - * Output device. Only "soundcard" is supported at this time. + * Output device. Soundcard, stdout, and UDP are supported at this time. */ + if (strcasecmp(pa->adev[a].adevice_out, "stdout") == 0 || strcmp(pa->adev[a].adevice_out, "-") == 0) { + adev[a].g_audio_out_type = AUDIO_OUT_TYPE_STDOUT; + if (!dw_printf_redirected()) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("stdout must only be used with the -O option\n"); + return (-1); + } + } else if (strncasecmp(pa->adev[a].adevice_out, "udp:", 4) == 0) { + adev[a].g_audio_out_type = AUDIO_OUT_TYPE_SDR_UDP; + /* User must supply address and port */ + if (strcasecmp(pa->adev[a].adevice_out,"udp:") == 0 || + strlen(pa->adev[a].adevice_out) < 7 || + strstr(pa->adev[a].adevice_out+5, ":") == 0) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Destination address and port must be supplied for UDP output\n"); + return (-1); + } + } else { + adev[a].g_audio_out_type = AUDIO_OUT_TYPE_SOUNDCARD; + } + + switch (adev[a].g_audio_out_type) { + case AUDIO_OUT_TYPE_STDOUT: + adev[a].outbuf_size_in_bytes = 1024; + break; + case AUDIO_OUT_TYPE_SOUNDCARD: #if USE_ALSA - err = snd_pcm_open (&(adev[a].audio_out_handle), audio_out_name, SND_PCM_STREAM_PLAYBACK, 0); + err = snd_pcm_open (&(adev[a].audio_out_handle), audio_out_name, SND_PCM_STREAM_PLAYBACK, 0); - if (err < 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Could not open audio device %s for output\n%s\n", - audio_out_name, snd_strerror(err)); - return (-1); - } + if (err < 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Could not open audio device %s for output\n%s\n", + audio_out_name, snd_strerror(err)); + if (err == -EBUSY) { + dw_printf ("This means that some other application is using that device.\n"); + dw_printf ("The solution is to identify that other application and stop it.\n"); + } + return (-1); + } - adev[a].outbuf_size_in_bytes = set_alsa_params (a, adev[a].audio_out_handle, pa, audio_out_name, "output"); + adev[a].outbuf_size_in_bytes = set_alsa_params (a, adev[a].audio_out_handle, pa, audio_out_name, "output"); - if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) { - return (-1); - } + if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) { + return (-1); + } #elif USE_SNDIO - adev[a].sndio_out_handle = sio_open (audio_out_name, SIO_PLAY, 0); - if (adev[a].sndio_out_handle == NULL) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Could not open audio device %s for output\n", - audio_out_name); - return (-1); - } + adev[a].sndio_out_handle = sio_open (audio_out_name, SIO_PLAY, 0); + if (adev[a].sndio_out_handle == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Could not open audio device %s for output\n", + audio_out_name); + return (-1); + } - adev[a].outbuf_size_in_bytes = set_sndio_params (a, adev[a].sndio_out_handle, pa, audio_out_name, "output"); + adev[a].outbuf_size_in_bytes = set_sndio_params (a, adev[a].sndio_out_handle, pa, audio_out_name, "output"); - if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) { - return (-1); - } + if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) { + return (-1); + } - if (!sio_start (adev[a].sndio_out_handle)) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Could not start audio device %s for output\n", - audio_out_name); - return (-1); - } + if (!sio_start (adev[a].sndio_out_handle)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Could not start audio device %s for output\n", + audio_out_name); + return (-1); + } #endif + break; + + case AUDIO_OUT_TYPE_SDR_UDP:; + + struct addrinfo ai_out; + struct addrinfo *ai_res; + char udp_outhost[256]; + char *udp_outport; + int res; + + // Initialize structure for addrinfo restrictions + memset((char *) &ai_out, 0, sizeof(ai_out)); + ai_out.ai_socktype = SOCK_DGRAM; + ai_out.ai_protocol = IPPROTO_UDP; + + // Separate out the host and port strings + strncpy(udp_outhost, pa->adev[a].adevice_out+4, 255); + udp_outhost[255] = 0; + udp_outport = strstr(udp_outhost,":"); + *udp_outport++ = 0; + + if (strlen(udp_outport) == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("UDP output destination port must be supplied\n"); + return -1; + } + // Get the sockaddr to represent the host/port provided + res = getaddrinfo(udp_outhost, udp_outport, &ai_out, &ai_res); + if (res != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Error parsing/resolving UDP output address\n"); + return -1; + } + + // IPv4 and IPv6 structs are different sizes + if (ai_res->ai_family == AF_INET6) { + res = sizeof(struct sockaddr_in6); + } else { + res = sizeof(struct sockaddr_in); + } + + //Create UDP Socket for the right address family + if ((adev[a].udp_out_sock=socket(ai_res->ai_family, SOCK_DGRAM, IPPROTO_UDP))==-1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Couldn't create socket, errno %d\n", errno); + return -1; + } + + // Save sockaddr needed later to send the data and set buffer size + memcpy(&adev[a].udp_dest_addr, ai_res->ai_addr, res); + adev[a].outbuf_size_in_bytes = SDR_UDP_BUF_MAXLEN; + freeaddrinfo(ai_res); + + break; + + } /* * Finally allocate buffer for each direction. */ @@ -1197,8 +1291,8 @@ int audio_get (int a) while (adev[a].inbuf_next >= adev[a].inbuf_len) { int res; - assert (adev[a].udp_sock > 0); - res = recv(adev[a].udp_sock, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes, 0); + assert (adev[a].udp_in_sock > 0); + res = recv(adev[a].udp_in_sock, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes, 0); if (res < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Can't read from udp socket, res=%d", res); @@ -1325,13 +1419,37 @@ int audio_put (int a, int c) int audio_flush (int a) { + int res; + unsigned char *ptr; + int len; + + switch (adev[a].g_audio_out_type) { + case AUDIO_OUT_TYPE_STDOUT:; + + ptr = adev[a].outbuf_ptr; + len = adev[a].outbuf_len; + + while (len > 0) { + res = write(STDOUT_FILENO, ptr, (size_t) len); + if (res <= 0) { + text_color_set(DW_COLOR_INFO); + dw_printf ("\nError writing to stdout. Exiting.\n"); + exit (0); + } + ptr += res; + len -= res; + } + adev[a].outbuf_len = 0; + return 0; + + case AUDIO_OUT_TYPE_SOUNDCARD:; #if USE_ALSA - int k; - unsigned char *psound; - int retries = 10; - snd_pcm_status_t *status; + int k; + unsigned char *psound; + int retries = 10; + snd_pcm_status_t *status; - assert (adev[a].audio_out_handle != NULL); + assert (adev[a].audio_out_handle != NULL); /* @@ -1344,159 +1462,186 @@ int audio_flush (int a) */ - snd_pcm_status_alloca(&status); + snd_pcm_status_alloca(&status); - k = snd_pcm_status (adev[a].audio_out_handle, status); - if (k != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Audio output get status error.\n%s\n", snd_strerror(k)); - } + k = snd_pcm_status (adev[a].audio_out_handle, status); + if (k != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Audio output get status error.\n%s\n", snd_strerror(k)); + } - if ((k = snd_pcm_status_get_state(status)) != SND_PCM_STATE_RUNNING) { + if ((k = snd_pcm_status_get_state(status)) != SND_PCM_STATE_RUNNING) { - //text_color_set(DW_COLOR_DEBUG); - //dw_printf ("Audio output state = %d. Try to start.\n", k); + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("Audio output state = %d. Try to start.\n", k); - k = snd_pcm_prepare (adev[a].audio_out_handle); + k = snd_pcm_prepare (adev[a].audio_out_handle); - if (k != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Audio output start error.\n%s\n", snd_strerror(k)); - } - } + if (k != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Audio output start error.\n%s\n", snd_strerror(k)); + } + } - psound = adev[a].outbuf_ptr; + psound = adev[a].outbuf_ptr; - while (retries-- > 0) { + while (retries-- > 0) { - k = snd_pcm_writei (adev[a].audio_out_handle, psound, adev[a].outbuf_len / adev[a].bytes_per_frame); + k = snd_pcm_writei (adev[a].audio_out_handle, psound, adev[a].outbuf_len / adev[a].bytes_per_frame); #if DEBUGx - text_color_set(DW_COLOR_DEBUG); - dw_printf ("audio_flush(): snd_pcm_writei %d frames returns %d\n", - adev[a].outbuf_len / adev[a].bytes_per_frame, k); - fflush (stdout); + text_color_set(DW_COLOR_DEBUG); + dw_printf ("audio_flush(): snd_pcm_writei %d frames returns %d\n", + adev[a].outbuf_len / adev[a].bytes_per_frame, k); + fflush (stdout); #endif - if (k == -EPIPE) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Audio output data underrun.\n"); + if (k == -EPIPE) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Audio output data underrun.\n"); - /* No problemo. Recover and go around again. */ + /* No problemo. Recover and go around again. */ - 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)); + 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)); - /* Some other error condition. */ - /* Try again. What do we have to lose? */ + /* Some other error condition. */ + /* Try again. What do we have to lose? */ - 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); - dw_printf ("Audio write took %d frames rather than %d.\n", - k, adev[a].outbuf_len / adev[a].bytes_per_frame); - - /* Go around again with the rest of it. */ + 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); + dw_printf ("Audio write took %d frames rather than %d.\n", + k, adev[a].outbuf_len / adev[a].bytes_per_frame); - psound += k * adev[a].bytes_per_frame; - adev[a].outbuf_len -= k * adev[a].bytes_per_frame; - } - else { - /* Success! */ - adev[a].outbuf_len = 0; - return (0); - } - } + /* Go around again with the rest of it. */ - text_color_set(DW_COLOR_ERROR); - dw_printf ("Audio write error retry count exceeded.\n"); + psound += k * adev[a].bytes_per_frame; + adev[a].outbuf_len -= k * adev[a].bytes_per_frame; + } + else { + /* Success! */ + adev[a].outbuf_len = 0; + return (0); + } + } - adev[a].outbuf_len = 0; - return (-1); + text_color_set(DW_COLOR_ERROR); + dw_printf ("Audio write error retry count exceeded.\n"); + + adev[a].outbuf_len = 0; + return (-1); #elif USE_SNDIO - int k; - unsigned char *ptr; - int len; + int k; + unsigned char *ptr; + int len; - ptr = adev[a].outbuf_ptr; - len = adev[a].outbuf_len; + ptr = adev[a].outbuf_ptr; + len = adev[a].outbuf_len; - while (len > 0) { - assert (adev[a].sndio_out_handle != NULL); - if (poll_sndio (adev[a].sndio_out_handle, POLLOUT) < 0) { - text_color_set(DW_COLOR_ERROR); - perror("Can't write to audio device"); - adev[a].outbuf_len = 0; - return (-1); - } + while (len > 0) { + assert (adev[a].sndio_out_handle != NULL); + if (poll_sndio (adev[a].sndio_out_handle, POLLOUT) < 0) { + text_color_set(DW_COLOR_ERROR); + perror("Can't write to audio device"); + adev[a].outbuf_len = 0; + return (-1); + } - k = sio_write (adev[a].sndio_out_handle, ptr, len); + k = sio_write (adev[a].sndio_out_handle, ptr, len); #if DEBUGx - text_color_set(DW_COLOR_DEBUG); - dw_printf ("audio_flush(): write %d returns %d\n", len, k); - fflush (stdout); + text_color_set(DW_COLOR_DEBUG); + dw_printf ("audio_flush(): write %d returns %d\n", len, k); + fflush (stdout); #endif - ptr += k; - len -= k; - } + ptr += k; + len -= k; + } - adev[a].outbuf_len = 0; - return (0); + adev[a].outbuf_len = 0; + return (0); #else /* OSS */ - int k; - unsigned char *ptr; - int len; + int k; + unsigned char *ptr; + int len; - ptr = adev[a].outbuf_ptr; - len = adev[a].outbuf_len; + ptr = adev[a].outbuf_ptr; + len = adev[a].outbuf_len; - while (len > 0) { - assert (adev[a].oss_audio_device_fd > 0); - k = write (adev[a].oss_audio_device_fd, ptr, len); + while (len > 0) { + assert (adev[a].oss_audio_device_fd > 0); + k = write (adev[a].oss_audio_device_fd, ptr, len); #if DEBUGx - text_color_set(DW_COLOR_DEBUG); - dw_printf ("audio_flush(): write %d returns %d\n", len, k); - fflush (stdout); + text_color_set(DW_COLOR_DEBUG); + dw_printf ("audio_flush(): write %d returns %d\n", len, k); + fflush (stdout); #endif - if (k < 0) { - text_color_set(DW_COLOR_ERROR); - perror("Can't write to audio device"); - adev[a].outbuf_len = 0; - return (-1); - } - if (k < len) { - /* presumably full but didn't block. */ - usleep (10000); - } - ptr += k; - len -= k; - } + if (k < 0) { + text_color_set(DW_COLOR_ERROR); + perror("Can't write to audio device"); + adev[a].outbuf_len = 0; + return (-1); + } + if (k < len) { + /* presumably full but didn't block. */ + usleep (10000); + } + ptr += k; + len -= k; + } - adev[a].outbuf_len = 0; - return (0); + adev[a].outbuf_len = 0; + return (0); #endif + case AUDIO_OUT_TYPE_SDR_UDP:; + + ptr = adev[a].outbuf_ptr; + len = adev[a].outbuf_len; + + while (len > 0) { + + assert (adev[a].udp_out_sock > 0); + + res = sendto(adev[a].udp_out_sock, adev[a].outbuf_ptr, len, 0, (struct sockaddr *)&adev[a].udp_dest_addr, sizeof(struct sockaddr_storage)); + + if (res <= 0) { + text_color_set(DW_COLOR_INFO); + dw_printf ("\nError %d writing to UDP socket. Exiting.\n", errno); + exit (0); + } + + ptr += res; + len -= res; + + } + + adev[a].outbuf_len = 0; + return 0; + + } + return (0); } /* end audio_flush */ @@ -1532,15 +1677,18 @@ int audio_flush (int a) * (3) Call this function, which might or might not wait long enough. * (4) Add (1) and (2) resulting in when PTT should be turned off. * (5) Take difference between current time and desired PPT off time - * and wait for additoinal time if required. + * and wait for additional time if required. * *----------------------------------------------------------------*/ void audio_wait (int a) { - audio_flush (a); + if (adev[a].g_audio_out_type != AUDIO_OUT_TYPE_SOUNDCARD) { + return; + } + #if USE_ALSA /* For playback, this should wait for all pending frames */ diff --git a/src/audio.h b/src/audio.h index 78327a73..bbe5892b 100644 --- a/src/audio.h +++ b/src/audio.h @@ -43,6 +43,11 @@ enum audio_in_type_e { AUDIO_IN_TYPE_SDR_UDP, AUDIO_IN_TYPE_STDIN }; +enum audio_out_type_e { + AUDIO_OUT_TYPE_SOUNDCARD, + AUDIO_OUT_TYPE_SDR_UDP, + AUDIO_OUT_TYPE_STDOUT }; + /* For option to try fixing frames with bad CRC. */ typedef enum retry_e { @@ -72,7 +77,7 @@ struct audio_s { struct adev_param_s { - /* Properites of the sound device. */ + /* Properties of the sound device. */ int defined; /* Was device defined? */ /* First one defaults to yes. */ @@ -102,7 +107,7 @@ struct audio_s { /* This is the probability, in per cent, of randomly corrupting it. */ /* Normally this is 0. 25 would mean corrupt it 25% of the time. */ - int recv_error_rate; /* Similar but the % probablity of dropping a received frame. */ + int recv_error_rate; /* Similar but the % probability of dropping a received frame. */ float recv_ber; /* Receive Bit Error Rate (BER). */ /* Probability of inverting a bit coming out of the modem. */ diff --git a/src/audio_portaudio.c b/src/audio_portaudio.c index 836390bc..cb6ccf10 100644 --- a/src/audio_portaudio.c +++ b/src/audio_portaudio.c @@ -1260,7 +1260,7 @@ int audio_flush (int a) * (3) Call this function, which might or might not wait long enough. * (4) Add (1) and (2) resulting in when PTT should be turned off. * (5) Take difference between current time and desired PPT off time - * and wait for additoinal time if required. + * and wait for additional time if required. * *----------------------------------------------------------------*/ diff --git a/src/audio_win.c b/src/audio_win.c index 1ba64bba..adba65ee 100644 --- a/src/audio_win.c +++ b/src/audio_win.c @@ -84,7 +84,7 @@ static struct audio_s *save_audio_config_p; */ /* - * Originally, we had an abitrary buf time of 40 mS. + * Originally, we had an arbitrary buf time of 40 mS. * * For mono, the buffer size was rounded up from 3528 to 4k so * it was really about 50 mS per buffer or about 20 per second. @@ -133,8 +133,8 @@ static int calcbufsize(int rate, int chans, int bits) static struct adev_s { - enum audio_in_type_e g_audio_in_type; - + enum audio_in_type_e g_audio_in_type; + enum audio_out_type_e g_audio_out_type; /* * UDP socket for receiving audio stream. * Buffer, length, and pointer for UDP or stdin. @@ -146,6 +146,14 @@ static struct adev_s { int stream_len; int stream_next; +/* + * UDP socket for transmitting audio stream. + * Buffer and index for stdout or UDP. + */ + SOCKET udp_out_sock; + char stream_out_data[SDR_UDP_BUF_MAXLEN]; + int stream_out_next; + struct sockaddr_storage udp_dest_addr; /* For sound output. */ /* out_wavehdr.dwUser is used to keep track of output buffer state. */ @@ -286,6 +294,7 @@ int audio_open (struct audio_s *pa) A->udp_sock = INVALID_SOCKET; + A->udp_out_sock = INVALID_SOCKET; in_dev_no[a] = WAVE_MAPPER; /* = ((UINT)-1) in mmsystem.h */ out_dev_no[a] = WAVE_MAPPER; @@ -343,28 +352,50 @@ int audio_open (struct audio_s *pa) /* * Select output device. - * Only soundcard at this point. - * Purhaps we'd like to add UDP for an SDR transmitter. + * Soundcard, UDP, and stdout supported. */ - if (strlen(pa->adev[a].adevice_out) == 1 && isdigit(pa->adev[a].adevice_out[0])) { - out_dev_no[a] = atoi(pa->adev[a].adevice_out); - } - else if (strlen(pa->adev[a].adevice_out) == 2 && isdigit(pa->adev[a].adevice_out[0]) && isdigit(pa->adev[a].adevice_out[1])) { - out_dev_no[a] = atoi(pa->adev[a].adevice_out); - } + if (strcasecmp(pa->adev[a].adevice_out, "stdout") == 0 || strcmp(pa->adev[a].adevice_out, "-") == 0) { + A->g_audio_out_type = AUDIO_OUT_TYPE_STDOUT; + if (!dw_printf_redirected()) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("stdout must only be used with the -O option\n"); + return (-1); + } + /* Change - to stdout for readability. */ + strlcpy (pa->adev[a].adevice_out, "stdout", sizeof(pa->adev[a].adevice_out)); + } else if (strncasecmp(pa->adev[a].adevice_out, "udp:", 4) == 0) { + A->g_audio_out_type = AUDIO_OUT_TYPE_SDR_UDP; + // User must supply address and port + if (strcasecmp(pa->adev[a].adevice_out, "udp:") == 0 || + strlen(pa->adev[a].adevice_out) < 7 || + strstr(pa->adev[a].adevice_out+5, ":") == 0) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Destination address and port must be supplied for UDP output\n"); + return (-1); + } + } else { + A->g_audio_out_type = AUDIO_OUT_TYPE_SOUNDCARD; - if ((UINT)(out_dev_no[a]) == WAVE_MAPPER && strlen(pa->adev[a].adevice_out) >= 1) { - num_devices = waveOutGetNumDevs(); - for (n=0 ; nadev[a].adevice_out) != NULL) { - out_dev_no[a] = n; + if (strlen(pa->adev[a].adevice_out) == 1 && isdigit(pa->adev[a].adevice_out[0])) { + out_dev_no[a] = atoi(pa->adev[a].adevice_out); + } + else if (strlen(pa->adev[a].adevice_out) == 2 && isdigit(pa->adev[a].adevice_out[0]) && isdigit(pa->adev[a].adevice_out[1])) { + out_dev_no[a] = atoi(pa->adev[a].adevice_out); + } + + if ((UINT)(out_dev_no[a]) == WAVE_MAPPER && strlen(pa->adev[a].adevice_out) >= 1) { + num_devices = waveOutGetNumDevs(); + for (n=0 ; nadev[a].adevice_out) != NULL) { + out_dev_no[a] = n; + } } } - } - if ((UINT)(out_dev_no[a]) == WAVE_MAPPER) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("\"%s\" doesn't match any of the output devices.\n", pa->adev[a].adevice_out); + if ((UINT)(out_dev_no[a]) == WAVE_MAPPER) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\"%s\" doesn't match any of the output devices.\n", pa->adev[a].adevice_out); + } } } } /* if defined */ @@ -424,7 +455,7 @@ int audio_open (struct audio_s *pa) struct adev_s *A = &(adev[a]); - /* Display stdin or udp:port if appropriate. */ + /* Display stdin or udp:port if appropriate. */ if (A->g_audio_in_type != AUDIO_IN_TYPE_SOUNDCARD) { @@ -498,6 +529,36 @@ int audio_open (struct audio_s *pa) } } +// Add UDP or stdout to end of device list if used. + + for (a=0; aadev[a].defined) { + + struct adev_s *A = &(adev[a]); + + /* Display stdout or udp:port if appropriate. */ + + if (A->g_audio_out_type != AUDIO_OUT_TYPE_SOUNDCARD) { + + int aaa; + for (aaa=0; aaaadev[aaa].defined) { + dw_printf (" %c", a == aaa ? '*' : ' '); + + } + } + dw_printf (" %s ", pa->adev[a].adevice_out); /* should be UDP:nnnn or stdout */ + + if (pa->adev[a].num_channels == 2) { + dw_printf (" (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1); + } + else { + dw_printf (" (channel %d)", ADEVFIRSTCHAN(a)); + } + dw_printf ("\n"); + } + } + } /* * Open for each audio device input/output pair. @@ -523,32 +584,112 @@ int audio_open (struct audio_s *pa) /* * Open the audio output device. - * Soundcard is only possibility at this time. + * Soundcard and stdout are only possibility at this time. */ - err = waveOutOpen (&(A->audio_out_handle), out_dev_no[a], &wf, (DWORD_PTR)out_callback, a, CALLBACK_FUNCTION); - if (err != MMSYSERR_NOERROR) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Could not open audio device for output.\n"); - return (-1); - } - + switch (A->g_audio_out_type) { + + case AUDIO_OUT_TYPE_SOUNDCARD: + err = waveOutOpen (&(A->audio_out_handle), out_dev_no[a], &wf, (DWORD_PTR)out_callback, a, CALLBACK_FUNCTION); + if (err != MMSYSERR_NOERROR) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Could not open audio device for output.\n"); + return (-1); + } + break; /* * Set up the output buffers. * We use dwUser to indicate it is available for filling. */ - memset ((void*)(A->out_wavehdr), 0, sizeof(A->out_wavehdr)); + memset ((void*)(A->out_wavehdr), 0, sizeof(A->out_wavehdr)); - for (n = 0; n < NUM_OUT_BUF; n++) { - A->out_wavehdr[n].lpData = malloc(A->outbuf_size); - A->out_wavehdr[n].dwUser = DWU_FILLING; - A->out_wavehdr[n].dwBufferLength = 0; - } - A->out_current = 0; + for (n = 0; n < NUM_OUT_BUF; n++) { + A->out_wavehdr[n].lpData = malloc(A->outbuf_size); + A->out_wavehdr[n].dwUser = DWU_FILLING; + A->out_wavehdr[n].dwBufferLength = 0; + } + A->out_current = 0; + case AUDIO_OUT_TYPE_SDR_UDP:; + WSADATA wsadata; + struct addrinfo ai_out; + struct addrinfo *ai_res; + char udp_outhost[256]; + char *udp_outport; + int err, res; + + err = WSAStartup (MAKEWORD(2,2), &wsadata); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf("WSAStartup failed: %d\n", err); + return (-1); + } + + if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Could not find a usable version of Winsock.dll\n"); + WSACleanup(); + return (-1); + } + + memset((char *) &ai_out, 0, sizeof(ai_out)); + ai_out.ai_socktype = SOCK_DGRAM; + ai_out.ai_protocol = IPPROTO_UDP; + + strncpy(udp_outhost, pa->adev[a].adevice_out + 4, 255); + udp_outhost[255] = 0; + udp_outport = strstr(udp_outhost, ":"); + *udp_outport++ = 0; + + if (strlen(udp_outport) == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf("UDP output destination port must be supplied\n"); + return -1; + } + + err = getaddrinfo(udp_outhost, udp_outport, &ai_out, &ai_res); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Error parsing/resolving UDP output address\n"); + return -1; + } + + if (ai_res->ai_family == AF_INET6) { + res = sizeof(struct sockaddr_in6); + } else { + res = sizeof(struct sockaddr_in); + } + + // Create UDP Socket + + A->udp_out_sock = socket(ai_res->ai_family, SOCK_DGRAM, IPPROTO_UDP); + if (A->udp_out_sock == INVALID_SOCKET) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Couldn't create socket, errno %d\n", WSAGetLastError()); + return -1; + } + + memcpy(&A->udp_dest_addr, ai_res->ai_addr, res); + A->stream_out_next = 0; + + break; + + case AUDIO_OUT_TYPE_STDOUT: + + setmode (STDOUT_FILENO, _O_BINARY); + A->stream_out_next= 0; + break; + + default: + + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error, invalid audio_out_type\n"); + return (-1); + } + /* * Open audio input device. * More possibilities here: soundcard, UDP port, stdin. @@ -942,49 +1083,69 @@ int audio_put (int a, int c) struct adev_s *A; A = &(adev[a]); - + + switch (A->g_audio_out_type) { + + case AUDIO_OUT_TYPE_SOUNDCARD: + /* * Wait if no buffers are available. * Don't use p yet because compiler might might consider dwFlags a loop invariant. */ - int timeout = 10; - while ( A->out_wavehdr[A->out_current].dwUser == DWU_PLAYING) { - SLEEP_MS (ONE_BUF_TIME); - timeout--; - if (timeout <= 0) { - text_color_set(DW_COLOR_ERROR); + int timeout = 10; + while ( A->out_wavehdr[A->out_current].dwUser == DWU_PLAYING) { + SLEEP_MS (ONE_BUF_TIME); + timeout--; + if (timeout <= 0) { + text_color_set(DW_COLOR_ERROR); // TODO: open issues 78 & 165. How can we avoid/improve this? - dw_printf ("Audio output failure waiting for buffer.\n"); - dw_printf ("This can occur when we are producing audio output for\n"); - dw_printf ("transmit and the operating system doesn't provide buffer\n"); - dw_printf ("space after waiting and retrying many times.\n"); - //dw_printf ("In recent years, this has been reported only when running the\n"); - //dw_printf ("Windows version with VMWare on a Macintosh.\n"); - ptt_term (); - return (-1); - } - } + dw_printf ("Audio output failure waiting for buffer.\n"); + dw_printf ("This can occur when we are producing audio output for\n"); + dw_printf ("transmit and the operating system doesn't provide buffer\n"); + dw_printf ("space after waiting and retrying many times.\n"); + //dw_printf ("In recent years, this has been reported only when running the\n"); + //dw_printf ("Windows version with VMWare on a Macintosh.\n"); + ptt_term (); + return (-1); + } + } - p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current])); + p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current])); + + if (p->dwUser == DWU_DONE) { + waveOutUnprepareHeader (A->audio_out_handle, p, sizeof(WAVEHDR)); + p->dwBufferLength = 0; + p->dwUser = DWU_FILLING; + } + + /* Should never be full at this point. */ + + assert (p->dwBufferLength >= 0); + assert (p->dwBufferLength < (DWORD)(A->outbuf_size)); + + p->lpData[p->dwBufferLength++] = c; + + if (p->dwBufferLength == (DWORD)(A->outbuf_size)) { + return (audio_flush(a)); + } + break; - if (p->dwUser == DWU_DONE) { - waveOutUnprepareHeader (A->audio_out_handle, p, sizeof(WAVEHDR)); - p->dwBufferLength = 0; - p->dwUser = DWU_FILLING; - } - /* Should never be full at this point. */ + case AUDIO_OUT_TYPE_SDR_UDP: + case AUDIO_OUT_TYPE_STDOUT: - assert (p->dwBufferLength >= 0); - assert (p->dwBufferLength < (DWORD)(A->outbuf_size)); + A->stream_out_data[A->stream_out_next++] = c; - p->lpData[p->dwBufferLength++] = c; + assert(A->stream_out_next > 0); + assert(A->stream_out_next <= SDR_UDP_BUF_MAXLEN); - if (p->dwBufferLength == (DWORD)(A->outbuf_size)) { - return (audio_flush(a)); + if (A->stream_out_next == SDR_UDP_BUF_MAXLEN) { + return (audio_flush(a)); + } + break; } return (0); @@ -1013,29 +1174,77 @@ int audio_flush (int a) WAVEHDR *p; MMRESULT e; struct adev_s *A; + int res; + char *ptr; + unsigned int len; + A = &(adev[a]); - - p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current])); - if (p->dwUser == DWU_FILLING && p->dwBufferLength > 0) { + switch (A->g_audio_out_type) { + case AUDIO_OUT_TYPE_SOUNDCARD: + p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current])); - p->dwUser = DWU_PLAYING; + if (p->dwUser == DWU_FILLING && p->dwBufferLength > 0) { - waveOutPrepareHeader(A->audio_out_handle, p, sizeof(WAVEHDR)); + p->dwUser = DWU_PLAYING; - e = waveOutWrite(A->audio_out_handle, p, sizeof(WAVEHDR)); - if (e != MMSYSERR_NOERROR) { - text_color_set (DW_COLOR_ERROR); - dw_printf ("audio out write error %d\n", e); + waveOutPrepareHeader(A->audio_out_handle, p, sizeof(WAVEHDR)); - /* I don't expect this to ever happen but if it */ - /* does, make the buffer available for filling. */ + e = waveOutWrite(A->audio_out_handle, p, sizeof(WAVEHDR)); + if (e != MMSYSERR_NOERROR) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("audio out write error %d\n", e); + + /* I don't expect this to ever happen but if it */ + /* does, make the buffer available for filling. */ + + p->dwUser = DWU_DONE; + return (-1); + } + A->out_current = (A->out_current + 1) % NUM_OUT_BUF; + } + break; + + case AUDIO_OUT_TYPE_STDOUT: + + ptr = A->stream_out_data; + len = A->stream_out_next; + + while (len > 0) { + res = write(STDOUT_FILENO, ptr, len); + if (res < 0) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("stdout audio write error %d\n", res); + return (-1); + } + ptr += res; + len -= res; + } + + A->stream_out_next = 0; + break; + + case AUDIO_OUT_TYPE_SDR_UDP: + + ptr = A->stream_out_data; + len = A->stream_out_next; + + while (len > 0) { + res = sendto(A->udp_out_sock, ptr, len, 0, (struct sockaddr *)&A->udp_dest_addr, sizeof(struct sockaddr_storage)); + if (res < 0) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Error %d writing to UDP socket.\n", res); + return (-1); + } + + ptr += res; + len -= res; + } + + A->stream_out_next = 0; + break; - p->dwUser = DWU_DONE; - return (-1); - } - A->out_current = (A->out_current + 1) % NUM_OUT_BUF; } return (0); @@ -1074,7 +1283,7 @@ int audio_flush (int a) * (3) Call this function, which might or might not wait long enough. * (4) Add (1) and (2) resulting in when PTT should be turned off. * (5) Take difference between current time and desired PPT off time - * and wait for additoinal time if required. + * and wait for additional time if required. * *----------------------------------------------------------------*/ diff --git a/src/ax25_link.c b/src/ax25_link.c index 09e71359..ab2875d9 100644 --- a/src/ax25_link.c +++ b/src/ax25_link.c @@ -347,7 +347,7 @@ typedef struct ax25_dlsm_s { // Sometimes the flow chart has SAT instead of SRT. // I think that is a typographical error. - float t1v; // How long to wait for an acknowlegement before resending. + float t1v; // How long to wait for an acknowledgement before resending. // Value used when starting timer T1, in seconds. // "FRACK" parameter in some implementations. // Typically it might be 3 seconds after frame has been @@ -6049,7 +6049,7 @@ static void check_need_for_response (ax25_dlsm_t *S, ax25_frame_type_t frame_typ * * Outputs: S->srt New smoothed roundtrip time. * - * S->t1v How long to wait for an acknowlegement before resending. + * S->t1v How long to wait for an acknowledgement before resending. * Value used when starting timer T1, in seconds. * Here it is dynamically adjusted. * diff --git a/src/ax25_pad.c b/src/ax25_pad.c index b5d47639..0f075808 100644 --- a/src/ax25_pad.c +++ b/src/ax25_pad.c @@ -1866,7 +1866,7 @@ packet_t ax25_get_nextp (packet_t this_p) * * Inputs: this_p - Current packet object. * - * release_time - Time as returned by dtime_now(). + * release_time - Time as returned by dtime_monotonic(). * *------------------------------------------------------------------------------*/ @@ -2923,7 +2923,9 @@ int ax25_alevel_to_text (alevel_t alevel, char text[AX25_ALEVEL_TO_TEXT_SIZE]) snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d(%+d/%+d)", alevel.rec, alevel.mark, alevel.space); } - else if (alevel.mark == -1 && alevel.space == -1) { /* PSK - single number. */ + else if ((alevel.mark == -1 && alevel.space == -1) || /* PSK */ + (alevel.mark == -99 && alevel.space == -99)) { /* v. 1.7 "B" FM demodulator. */ + // ?? Where does -99 come from? snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d", alevel.rec); } diff --git a/src/beacon.c b/src/beacon.c index 4c78198f..69a72701 100644 --- a/src/beacon.c +++ b/src/beacon.c @@ -162,6 +162,7 @@ void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct int chan = g_misc_config_p->beacon[j].sendto_chan; if (chan < 0) chan = 0; /* For IGate, use channel 0 call. */ + if (chan >= MAX_CHANS) chan = 0; // For ICHANNEL, use channel 0 call. if (g_modem_config_p->chan_medium[chan] == MEDIUM_RADIO || g_modem_config_p->chan_medium[chan] == MEDIUM_NETTNC) { @@ -621,6 +622,7 @@ static void * beacon_thread (void *arg) // On reboot, the time is in the past. // After time gets set from GPS, all beacons from that interval are sent. // FIXME: This will surely break time slotted scheduling. + // TODO: The correct fix will be using monotonic, rather than clock, time. /* craigerl: if next beacon is scheduled in the past, then set next beacon relative to now (happens when NTP pushes clock AHEAD) */ /* fixme: if NTP sets clock BACK an hour, this thread will sleep for that hour */ @@ -805,11 +807,17 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo) assert (bp->sendto_chan >= 0); - strlcpy (mycall, g_modem_config_p->achan[bp->sendto_chan].mycall, sizeof(mycall)); + if (g_modem_config_p->chan_medium[bp->sendto_chan] == MEDIUM_IGATE) { // ICHANNEL uses chan 0 mycall. + // TODO: Maybe it should be allowed to have own. + strlcpy (mycall, g_modem_config_p->achan[0].mycall, sizeof(mycall)); + } + else { + strlcpy (mycall, g_modem_config_p->achan[bp->sendto_chan].mycall, sizeof(mycall)); + } if (strlen(mycall) == 0 || strcmp(mycall, "NOCALL") == 0) { text_color_set(DW_COLOR_ERROR); - dw_printf ("MYCALL not set for beacon in config file line %d.\n", bp->lineno); + dw_printf ("MYCALL not set for beacon to chan %d in config file line %d.\n", bp->sendto_chan, bp->lineno); return; } @@ -1046,7 +1054,7 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo) text_color_set(DW_COLOR_XMIT); dw_printf ("[ig] %s\n", beacon_text); - igate_send_rec_packet (0, pp); + igate_send_rec_packet (-1, pp); // Channel -1 to avoid RF>IS filtering. ax25_delete (pp); break; diff --git a/src/cm108.c b/src/cm108.c index 8c8fc5ed..d55e8438 100644 --- a/src/cm108.c +++ b/src/cm108.c @@ -106,7 +106,7 @@ int main (void) { - text_color_init (0); // Turn off text color. + text_color_init (0, 0); // Turn off text color. #if defined(__OpenBSD__) || defined(__FreeBSD__) dw_printf ("CM108 PTT support is not available for this operating system.\n"); #else @@ -319,7 +319,7 @@ int main (int argc, char **argv) int num_things; int i; - text_color_init (0); // Turn off text color. + text_color_init (0, 0); // Turn off text color. text_color_set(DW_COLOR_INFO); if (argc >=2) { diff --git a/src/config.c b/src/config.c index 5a8eead4..fdaa143f 100644 --- a/src/config.c +++ b/src/config.c @@ -2780,6 +2780,12 @@ void config_init (char *fname, struct audio_s *p_audio_config, } if (*t == 'i' || *t == 'I') { from_chan = MAX_CHANS; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: FILTER IG ... on line %d.\n", line); + dw_printf ("Warning! Don't mess with IS>RF filtering unless you are an expert and have an unusual situation.\n"); + dw_printf ("Warning! The default is fine for nearly all situations.\n"); + dw_printf ("Warning! Be sure to read carefully and understand Successful-APRS-Gateway-Operation.pdf .\n"); + dw_printf ("Warning! If you insist, be sure to add \" | i/180 \" so you don't break messaging.\n"); } else { from_chan = isdigit(*t) ? atoi(t) : -999; @@ -2813,6 +2819,12 @@ void config_init (char *fname, struct audio_s *p_audio_config, } if (*t == 'i' || *t == 'I') { to_chan = MAX_CHANS; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: FILTER ... IG ... on line %d.\n", line); + dw_printf ("Warning! Don't mess with RF>IS filtering unless you are an expert and have an unusual situation.\n"); + dw_printf ("Warning! Expected behavior is for everything to go from RF to IS.\n"); + dw_printf ("Warning! The default is fine for nearly all situations.\n"); + dw_printf ("Warning! Be sure to read carefully and understand Successful-APRS-Gateway-Operation.pdf .\n"); } else { to_chan = isdigit(*t) ? atoi(t) : -999; @@ -4578,7 +4590,6 @@ void config_init (char *fname, struct audio_s *p_audio_config, * * In version 1.2 we allow 0 to disable listening. */ -// FIXME: complain if extra parameter e.g. port as in KISSPORT else if (strcasecmp(t, "AGWPORT") == 0) { int n; @@ -4589,6 +4600,13 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } n = atoi(t); + t = split(NULL,0); + if (t != NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Unexpected \"%s\" after the port number.\n", line, t); + dw_printf ("Perhaps you were trying to use feature available only with KISSPORT.\n"); + continue; + } if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) { p_misc_config->agwpe_port = n; } @@ -5594,7 +5612,8 @@ 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->chan_medium[n] == MEDIUM_NONE) { + if (( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) + && p_audio_config->chan_medium[n] != MEDIUM_IGATE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Simulated receive on channel %d is not valid.\n", line, n); continue; @@ -5604,7 +5623,8 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ } else if (value[0] == 't' || value[0] == 'T' || value[0] == 'x' || value[0] == 'X') { int n = atoi(value+1); - if ( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) { + if (( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) + && p_audio_config->chan_medium[n] != MEDIUM_IGATE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n); continue; @@ -5615,7 +5635,8 @@ 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->chan_medium[n] == MEDIUM_NONE) { + if (( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) + && p_audio_config->chan_medium[n] != MEDIUM_IGATE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n); continue; @@ -5829,7 +5850,7 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ /* * Process symbol now that we have any later overlay. * - * FIXME: Someone who used this was surprized to end up with Solar Powser (S-). + * FIXME: Someone who used this was surprised to end up with Solar Powser (S-). * overlay=S symbol="/-" * We should complain if overlay used with symtab other than \. */ @@ -5864,19 +5885,32 @@ 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->chan_medium[b->sendto_chan] == MEDIUM_NONE) { + if (( b->sendto_chan < 0 || b->sendto_chan >= MAX_CHANS || p_audio_config->chan_medium[b->sendto_chan] == MEDIUM_NONE) + && p_audio_config->chan_medium[b->sendto_chan] != MEDIUM_IGATE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, b->sendto_chan); return (0); } - if ( strcmp(p_audio_config->achan[b->sendto_chan].mycall, "") == 0 || - strcmp(p_audio_config->achan[b->sendto_chan].mycall, "NOCALL") == 0 || - strcmp(p_audio_config->achan[b->sendto_chan].mycall, "N0CALL") == 0 ) { + if (p_audio_config->chan_medium[b->sendto_chan] == MEDIUM_IGATE) { // Prevent subscript out of bounds. + // Will be using call from chan 0 later. + if ( strcmp(p_audio_config->achan[0].mycall, "") == 0 || + strcmp(p_audio_config->achan[0].mycall, "NOCALL") == 0 || + strcmp(p_audio_config->achan[0].mycall, "N0CALL") == 0 ) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Config file: MYCALL must be set for channel %d before beaconing is allowed.\n", b->sendto_chan); - return (0); + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: MYCALL must be set for channel %d before beaconing is allowed.\n", 0); + return (0); + } + } else { + if ( strcmp(p_audio_config->achan[b->sendto_chan].mycall, "") == 0 || + strcmp(p_audio_config->achan[b->sendto_chan].mycall, "NOCALL") == 0 || + strcmp(p_audio_config->achan[b->sendto_chan].mycall, "N0CALL") == 0 ) { + + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: MYCALL must be set for channel %d before beaconing is allowed.\n", b->sendto_chan); + return (0); + } } } diff --git a/src/decode_aprs.c b/src/decode_aprs.c index e2c25248..10186fce 100644 --- a/src/decode_aprs.c +++ b/src/decode_aprs.c @@ -504,8 +504,10 @@ void decode_aprs_print (decode_aprs_t *A) { //dw_printf ("DEBUG decode_aprs_print stemp3=%s mfr=%s\n", stemp, A->g_mfr); if (strlen(A->g_mfr) > 0) { - if (strcmp(A->g_dest, "APRS") == 0) { - strlcat (stemp, "\nUse of \"APRS\" in the destination field is obsolete.", sizeof(stemp)); + if (strcmp(A->g_dest, "APRS") == 0 || strcmp(A->g_dest, "BEACON") == 0) { + strlcat (stemp, "\nUse of \"", sizeof(stemp)); + strlcat (stemp, A->g_dest, sizeof(stemp)); + strlcat (stemp, "\" in the destination field is obsolete.", sizeof(stemp)); strlcat (stemp, " You can help to improve the quality of APRS signals.", sizeof(stemp)); strlcat (stemp, "\nTell the sender (", sizeof(stemp)); strlcat (stemp, A->g_src, sizeof(stemp)); @@ -858,6 +860,32 @@ static void aprs_ll_pos (decode_aprs_t *A, unsigned char *info, int ilen) strlcpy (A->g_data_type_desc, "Weather Report", sizeof(A->g_data_type_desc)); weather_data (A, p->comment, TRUE); +/* +Here is an interesting case. +The protocol spec states that a position report with symbol _ is a special case +and the information part must contain wxnow.txt format weather data. +But, here we see it being generated like a normal position report. + +N8VIM>BEACON,AB1OC-10*,WIDE2-1:!4240.85N/07133.99W_PHG72604/ Pepperell, MA. WX. 442.9+ PL100<0x0d> +Didn't find wind direction in form c999. +Didn't find wind speed in form s999. +Didn't find wind gust in form g999. +Didn't find temperature in form t999. +Weather Report, WEATHER Station (blue) +N 42 40.8500, W 071 33.9900 +, "PHG72604/ Pepperell, MA. WX. 442.9+ PL100" + +It seems, to me, that this is a violation of the protocol spec. +Then, immediately following, we have a positionless weather report in Ultimeter format. + +N8VIM>APN391,AB1OC-10*,WIDE2-1:$ULTW006F00CA01421C52275800008A00000102FA000F04A6000B002A<0x0d><0x0a> +Ultimeter, Kantronics KPC-3 rom versions +wind 6.9 mph, direction 284, temperature 32.2, barometer 29.75, humidity 76 + +aprs.fi merges these two together. Is that anywhere in the protocol spec or +just a heuristic added after noticing a pair of packets like this? +*/ + } else { /* Regular position report. */ @@ -1375,7 +1403,7 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int } } -/* 6th character of destintation indicates east / west. */ +/* 6th character of destination indicates east / west. */ /* * Example of apparently invalid encoding. 6th character missing. @@ -1579,7 +1607,7 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int * Purpose: Decode "Message Format." * The word message is used loosely all over the place, but it has a very specific meaning here. * - * Inputs: info - Pointer to Information field. Be carefull not to modify it here! + * Inputs: info - Pointer to Information field. Be careful not to modify it here! * ilen - Information field length. * quiet - suppress error messages. * @@ -2372,6 +2400,20 @@ static void aprs_status_report (decode_aprs_t *A, char *info, int ilen) * *------------------------------------------------------------------*/ +/* +https://groups.io/g/direwolf/topic/95961245#7357 + +What APRS queries should DireWolf respond to? Well, it should be configurable whether it responds to queries at all, in case some other application is using DireWolf as a dumb TNC (KISS or AGWPE style) and wants to handle the queries itself. + +Assuming query responding is enabled, the following broadcast queries should be supported (if the corresponding data is configured in DireWolf): + +?APRS (I am an APRS station) +?IGATE (I am operating as a I-gate) +?WX (I am providing local weather data in my beacon) + +*/ + + static void aprs_general_query (decode_aprs_t *A, char *info, int ilen, int quiet) { char *q2; @@ -2524,6 +2566,28 @@ static void aprs_general_query (decode_aprs_t *A, char *info, int ilen, int quie * *------------------------------------------------------------------*/ +/* +https://groups.io/g/direwolf/topic/95961245#7357 + +The following directed queries (sent as bodies of APRS text messages) would also be useful (if corresponding data configured): + +?APRSP (force my current beacon) +?APRST and ?PING (trace my path to requestor) +?APRSD (all stations directly heard [no digipeat hops] by local station) +?APRSO (any Objects/Items originated by this station) +?APRSH (how often or how many times the specified 3rd station was heard by the queried station) +?APRSS (immediately send the Status message if configured) (can DireWolf do Status messages?) + +Lynn KJ4ERJ and I have implemented a non-standard query which might be useful: + +?VER (send the human-readable software version of the queried station) + +Hope this is useful. It's just my $.02. + +Andrew, KA2DDO +author of YAAC +*/ + static void aprs_directed_station_query (decode_aprs_t *A, char *addressee, char *query, int quiet) { //char query_type[20]; /* Does the query type always need to be exactly 5 characters? */ @@ -5052,7 +5116,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_init(1, 0); text_color_set(DW_COLOR_INFO); while (fgets(stuff, sizeof(stuff), stdin) != NULL) diff --git a/src/demod.c b/src/demod.c index 482c1076..9f94dd8d 100644 --- a/src/demod.c +++ b/src/demod.c @@ -832,7 +832,7 @@ int demod_init (struct audio_s *pa) * * Name: demod_get_sample * - * Purpose: Get one audio sample fromt the specified sound input source. + * Purpose: Get one audio sample from the specified sound input source. * * Inputs: a - Index for audio device. 0 = first. * diff --git a/src/demod_afsk.c b/src/demod_afsk.c index f32137c5..b4d6c295 100644 --- a/src/demod_afsk.c +++ b/src/demod_afsk.c @@ -309,10 +309,6 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, D->lp_window = BP_WINDOW_TRUNCATED; } - D->agc_fast_attack = 0.820; - D->agc_slow_decay = 0.000214; - D->agc_fast_attack = 0.45; - D->agc_slow_decay = 0.000195; D->agc_fast_attack = 0.70; D->agc_slow_decay = 0.000090; @@ -372,10 +368,16 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, // For scaling phase shift into normallized -1 to +1 range for mark and space. D->u.afsk.normalize_rpsam = 1.0 / (0.5 * abs(mark_freq - space_freq) * 2 * M_PI / samples_per_sec); + // New "B" demodulator does not use AGC but demod.c needs this to derive "quick" and + // "sluggish" values for overall signal amplitude. That probably should be independent + // of these values. + D->agc_fast_attack = 0.70; + D->agc_slow_decay = 0.000090; + D->pll_locked_inertia = 0.74; D->pll_searching_inertia = 0.50; - D->alevel_mark_peak = -1; // FIXME: disable display + D->alevel_mark_peak = -1; // Disable received signal (m/s) display. D->alevel_space_peak = -1; break; @@ -868,6 +870,7 @@ static void nudge_pll (int chan, int subchan, int slice, float demod_out, struct { D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll; + // Perform the add as unsigned to avoid signed overflow error. D->slicer[slice].data_clock_pll = (signed)((unsigned)(D->slicer[slice].data_clock_pll) + (unsigned)(D->pll_step_per_sample)); @@ -901,7 +904,15 @@ static void nudge_pll (int chan, int subchan, int slice, float demod_out, struct #endif + +#if 1 hdlc_rec_bit (chan, subchan, slice, demod_out > 0, 0, quality); +#else // TODO: new feature to measure data speed error. +// Maybe hdlc_rec_bit could provide indication when frame starts. + hdlc_rec_bit_new (chan, subchan, slice, demod_out > 0, 0, quality, + &(D->slicer[slice].pll_nudge_total), &(D->slicer[slice].pll_symbol_count)); + D->slicer[slice].pll_symbol_count++; +#endif pll_dcd_each_symbol2 (D, chan, subchan, slice); } @@ -912,12 +923,14 @@ static void nudge_pll (int chan, int subchan, int slice, float demod_out, struct pll_dcd_signal_transition2 (D, slice, D->slicer[slice].data_clock_pll); +// TODO: signed int before = (signed int)(D->slicer[slice].data_clock_pll); // Treat as signed. if (D->slicer[slice].data_detect) { D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia); } else { D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_searching_inertia); } +// TODO: D->slicer[slice].pll_nudge_total += (int64_t)((signed int)(D->slicer[slice].data_clock_pll)) - (int64_t)before; } /* diff --git a/src/digipeater.c b/src/digipeater.c index 006ee7b7..4daf1890 100644 --- a/src/digipeater.c +++ b/src/digipeater.c @@ -164,7 +164,34 @@ void digipeater (int from_chan, packet_t pp) /* * First pass: Look at packets being digipeated to same channel. * - * We want these to get out quickly. + * We want these to get out quickly, bypassing the usual random wait time. + * + * Some may disagree but I followed what WB4APR had to say about it. + * + * http://www.aprs.org/balloons.html + * + * APRS NETWORK FRATRICIDE: Generally, all APRS digipeaters are supposed to transmit + * immediately and all at the same time. They should NOT wait long enough for each + * one to QRM the channel with the same copy of each packet. NO, APRS digipeaters + * are all supposed to STEP ON EACH OTHER with every packet. This makes sure that + * everyone in range of a digi will hear one and only one copy of each packet. + * and that the packet will digipeat OUTWARD and not backward. The goal is that a + * digipeated packet is cleared out of the local area in ONE packet time and not + * N packet times for every N digipeaters that heard the packet. This means no + * PERSIST times, no DWAIT times and no UIDWAIT times. Notice, this is contrary + * to other packet systems that might want to guarantee delivery (but at the + * expense of throughput). APRS wants to clear the channel quickly to maximize throughput. + * + * http://www.aprs.org/kpc3/kpc3+WIDEn.txt + * + * THIRD: Eliminate the settings that are detrimental to the network. + * + * * UIDWAIT should be OFF. (the default). With it on, your digi is not doing the + * fundamental APRS fratricide that is the primary mechanism for minimizing channel + * loading. All digis that hear the same packet are supposed to DIGI it at the SAME + * time so that all those copies only take up one additional time slot. (but outward + * located digs will hear it without collision (and continue outward propagation) + * */ for (to_chan=0; to_chanfilter_str[from_chan][to_chan]); if (result != NULL) { dedupe_remember (pp, to_chan); - tq_append (to_chan, TQ_PRIO_0_HI, result); + tq_append (to_chan, TQ_PRIO_0_HI, result); // High priority queue. digi_count[from_chan][to_chan]++; } } @@ -207,7 +234,7 @@ void digipeater (int from_chan, packet_t pp) save_digi_config_p->filter_str[from_chan][to_chan]); if (result != NULL) { dedupe_remember (pp, to_chan); - tq_append (to_chan, TQ_PRIO_1_LO, result); + tq_append (to_chan, TQ_PRIO_1_LO, result); // Low priority queue. digi_count[from_chan][to_chan]++; } } diff --git a/src/direwolf.c b/src/direwolf.c index 3ad404e6..5712a6f7 100644 --- a/src/direwolf.c +++ b/src/direwolf.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019, 2020, 2021 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019, 2020, 2021, 2023 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -241,6 +241,8 @@ int main (int argc, char *argv[]) char x_opt_mode = ' '; /* "-x N" option for transmitting calibration tones. */ int x_opt_chan = 0; /* Split into 2 parts. Mode e.g. m, a, and optional channel. */ + int O_opt = 0; /* Redirect text io to stderr for use with stdout audio */ + strlcpy(l_opt_logdir, "", sizeof(l_opt_logdir)); strlcpy(L_opt_logfile, "", sizeof(L_opt_logfile)); strlcpy(P_opt, "", sizeof(P_opt)); @@ -269,21 +271,28 @@ int main (int argc, char *argv[]) #endif /* - * Pre-scan the command line options for the text color option. - * We need to set this before any text output. + * Pre-scan the command line options for the text color and stdout redirect options. + * We need to set these 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. */ + for (j=1; j 0; + t_opt = _isatty(_fileno(O_opt ? stderr : stdout)) > 0; #else - t_opt = isatty(fileno(stdout)); + t_opt = isatty(fileno(O_opt ? stderr : stdout)); #endif /* 1 = normal, 0 = no text colors. */ /* 2, 3, ... alternate escape sequences for different terminals. */ - for (j=1; j 0) { - tone_gen_put_bit(x_opt_chan, 0); + tone_gen_put_bit(x_opt_chan, 1); } break; case 's': // "Space" tone: -x s @@ -1061,7 +1077,7 @@ int main (int argc, char *argv[]) audio_config.achan[x_opt_chan].space_freq, x_opt_chan); while (n-- > 0) { - tone_gen_put_bit(x_opt_chan, 1); + tone_gen_put_bit(x_opt_chan, 0); } break; case 'p': // Silence - set PTT only: -x p diff --git a/src/dtime_now.c b/src/dtime_now.c index af7b98a2..e5b40c6c 100644 --- a/src/dtime_now.c +++ b/src/dtime_now.c @@ -25,9 +25,9 @@ /*------------------------------------------------------------------ * - * Name: dtime_now + * Name: dtime_realtime * - * Purpose: Return current time as double precision. + * Purpose: Return current wall clock time as double precision. * * Input: none * @@ -40,15 +40,99 @@ * part of a second, and having extra calculations everywhere, * simply use double precision floating point to make usage * easier. - * + * + * NOTE: This is not a good way to calculate elapsed time because + * it can jump forward or backware via NTP or other manual setting. + * + * Use the monotonic version for measuring elapsed time. + * + * History: Originally I called this dtime_now. We ran into issues where + * we really cared about elapsed time, rather than wall clock time. + * The wall clock time could be wrong at start up time if there + * is no realtime clock or Internet access. It can then jump + * when GPS time or Internet access becomes available. + * All instances of dtime_now should be replaced by dtime_realtime + * if we want wall clock time, or dtime_monotonic if it is to be + * used for measuring elapsed time, such as between becons. + * *---------------------------------------------------------------*/ +double dtime_realtime (void) +{ + double result; + +#if __WIN32__ + /* 64 bit integer is number of 100 nanosecond intervals from Jan 1, 1601. */ + + FILETIME ft; + + GetSystemTimeAsFileTime (&ft); + + result = ((( (double)ft.dwHighDateTime * (256. * 256. * 256. * 256.) + + (double)ft.dwLowDateTime ) / 10000000.) - 11644473600.); +#else + /* tv_sec is seconds from Jan 1, 1970. */ + + struct timespec ts; + +#ifdef __APPLE__ + +// Why didn't I use clock_gettime? +// Not available before Max OSX 10.12? https://github.com/gambit/gambit/issues/293 + + struct timeval tp; + gettimeofday(&tp, NULL); + ts.tv_nsec = tp.tv_usec * 1000; + ts.tv_sec = tp.tv_sec; +#else + clock_gettime (CLOCK_REALTIME, &ts); +#endif + + result = ((double)(ts.tv_sec) + (double)(ts.tv_nsec) * 0.000000001); + +#endif + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("dtime_realtime() returns %.3f\n", result ); +#endif + + return (result); +} + -double dtime_now (void) +/*------------------------------------------------------------------ + * + * Name: dtime_monotonic + * + * Purpose: Return montonically increasing time, which is not influenced + * by the wall clock changing. e.g. leap seconds, NTP adjustments. + * + * Input: none + * + * Returns: Time as double precision, so we can get resolution + * finer than one second. + * + * Description: Use this when calculating elapsed time. + * + *---------------------------------------------------------------*/ + +double dtime_monotonic (void) { double result; #if __WIN32__ + +// FIXME: +// This is still returning wall clock time. +// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount64 +// GetTickCount64 would be ideal but it requires Vista or Server 2008. +// As far as I know, the current version of direwolf still works on XP. +// +// As a work-around, GetTickCount could be used if we add extra code to deal +// with the wrap around after about 49.7 days. +// Resolution is only about 10 or 16 milliseconds. Is that good enough? + /* 64 bit integer is number of 100 nanosecond intervals from Jan 1, 1601. */ FILETIME ft; @@ -63,12 +147,22 @@ double dtime_now (void) struct timespec ts; #ifdef __APPLE__ + +// FIXME: Does MacOS have a monotonically increasing time? +// https://stackoverflow.com/questions/41509505/clock-gettime-on-macos + struct timeval tp; gettimeofday(&tp, NULL); ts.tv_nsec = tp.tv_usec * 1000; ts.tv_sec = tp.tv_sec; #else - clock_gettime (CLOCK_REALTIME, &ts); + +// This is the only case handled properly. +// Probably the only one that matters. +// It is common to have a Raspberry Pi, without Internet, +// starting up direwolf before GPS/NTP adjusts the time. + + clock_gettime (CLOCK_MONOTONIC, &ts); #endif result = ((double)(ts.tv_sec) + (double)(ts.tv_nsec) * 0.000000001); @@ -84,6 +178,7 @@ double dtime_now (void) } + /*------------------------------------------------------------------ * * Name: timestamp_now @@ -104,7 +199,7 @@ double dtime_now (void) void timestamp_now (char *result, int result_size, int show_ms) { - double now = dtime_now(); + double now = dtime_realtime(); time_t t = (int)now; struct tm tm; @@ -150,7 +245,7 @@ void timestamp_now (char *result, int result_size, int show_ms) void timestamp_user_format (char *result, int result_size, char *user_format) { - double now = dtime_now(); + double now = dtime_realtime(); time_t t = (int)now; struct tm tm; @@ -191,7 +286,7 @@ void timestamp_user_format (char *result, int result_size, char *user_format) void timestamp_filename (char *result, int result_size) { - double now = dtime_now(); + double now = dtime_realtime(); time_t t = (int)now; struct tm tm; diff --git a/src/dtime_now.h b/src/dtime_now.h index ac22f7d5..411534bf 100644 --- a/src/dtime_now.h +++ b/src/dtime_now.h @@ -1,9 +1,18 @@ -extern double dtime_now (void); +extern double dtime_realtime (void); + +extern double dtime_monotonic (void); + void timestamp_now (char *result, int result_size, int show_ms); void timestamp_user_format (char *result, int result_size, char *user_format); -void timestamp_filename (char *result, int result_size); \ No newline at end of file +void timestamp_filename (char *result, int result_size); + + +// FIXME: remove temp workaround. +// Needs many scattered updates. + +#define dtime_now dtime_realtime diff --git a/src/fsk_demod_state.h b/src/fsk_demod_state.h index efb9d2ca..c9b26c23 100644 --- a/src/fsk_demod_state.h +++ b/src/fsk_demod_state.h @@ -367,7 +367,7 @@ struct demodulator_state_s // Add a sample to the total when putting it in our array of recent samples. // Subtract it from the total when it gets pushed off the end. // We can also eliminate the need to shift them all down by using a circular buffer. - // This only works with integers because float would have cummulated round off errors. + // This only works with integers because float would have cumulated round off errors. cic_t cic_center1; cic_t cic_above; diff --git a/src/igate.c b/src/igate.c index 719bef41..8bdf109b 100644 --- a/src/igate.c +++ b/src/igate.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, 2023 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -328,7 +328,7 @@ static int stats_uplink_packets; /* Number of packets passed along to the IGate /* server after filtering. */ static int stats_uplink_bytes; /* Total number of bytes sent to IGate server */ - /* including login, packets, and hearbeats. */ + /* including login, packets, and heartbeats. */ static int stats_downlink_bytes; /* Total number of bytes from IGate server including */ /* packets, heartbeats, other messages. */ @@ -855,6 +855,9 @@ static void * connnect_thread (void *arg) * Purpose: Send a packet to the IGate server * * Inputs: chan - Radio channel it was received on. + * This is required for the RF>IS filtering. + * Beaconing (sendto=ig, chan=-1) and a client app sending + * to ICHANNEL should bypass the filtering. * * recv_pp - Pointer to packet object. * *** CALLER IS RESPONSIBLE FOR DELETING IT! ** @@ -902,7 +905,12 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) * In that case, the payload will have TCPIP in the path and it will be dropped. */ - if (save_digi_config_p->filter_str[chan][MAX_CHANS] != NULL) { +// Apply RF>IS filtering only if it same from a radio channel. +// Beacon will be channel -1. +// Client app to ICHANNEL is outside of radio channel range. + + if (chan >= 0 && chan < MAX_CHANS && // in radio channel range + save_digi_config_p->filter_str[chan][MAX_CHANS] != NULL) { if (pfilter(chan, MAX_CHANS, save_digi_config_p->filter_str[chan][MAX_CHANS], recv_pp, 1) != 1) { @@ -1221,7 +1229,7 @@ static void send_packet_to_server (packet_t pp, int chan) * Name: send_msg_to_server * * Purpose: Send something to the IGate server. - * This one function should be used for login, hearbeats, + * This one function should be used for login, heartbeats, * and packets. * * Inputs: imsg - Message. We will add CR/LF here. @@ -1517,32 +1525,36 @@ static void * igate_recv_thread (void *arg) int ichan = save_audio_config_p->igate_vchannel; - // Try to parse it into a packet object. - // This will contain "q constructs" and we might see an address - // with two alphnumeric characters in the SSID so we must use - // the non-strict parsing. - - // Possible problem: Up to 8 digipeaters are allowed in radio format. - // There is a potential of finding a larger number here. - - packet_t pp3 = ax25_from_text((char*)message, 0); // 0 means not strict + // My original poorly thoughtout idea was to parse it into a packet object, + // using the non-strict option, and send to the client app. + // + // A lot of things can go wrong with that approach. + + // (1) Up to 8 digipeaters are allowed in radio format. + // There is a potential of finding a larger number here. + // + // (2) The via path can have names that are not valid in the radio format. + // e.g. qAC, T2HAKATA, N5JXS-F1. + // Non-strict parsing would force uppercase, truncate names too long, + // and drop unacceptable SSIDs. + // + // (3) The source address could be invalid for the RF address format. + // e.g. WHO-IS>APJIW4,TCPIP*,qAC,AE5PL-JF::ZL1JSH-9 :Charles Beadfield/New Zealand{583 + // That is essential information that we absolutely need to preserve. + // + // I think the only correct solution is to apply a third party header + // wrapper so the original contents are preserved. This will be a little + // more work for the application developer. Search for ":}" and use only + // the part after that. At this point, I don't see any value in encoding + // information in the source/destination so I will just use "X>X:}" as a prefix + + char stemp[AX25_MAX_INFO_LEN]; + strlcpy (stemp, "X>X:}", sizeof(stemp)); + strlcat (stemp, (char*)message, sizeof(stemp)); + + packet_t pp3 = ax25_from_text(stemp, 0); // 0 means not strict if (pp3 != NULL) { - // Should we remove the VIA path? - - // For example, we might get something like this from the server. - // Lower case 'q' and non-numeric SSID are not valid for AX.25 over the air. - // K1USN-1>APWW10,TCPIP*,qAC,N5JXS-F1:T#479,100,048,002,500,000,10000000 - - // Should we try to retain all information and pass that along, to the best of our ability, - // to the client app, or should we remove the via path so it looks like this? - // K1USN-1>APWW10:T#479,100,048,002,500,000,10000000 - - // For now, keep it intact and see if it causes problems. Easy to remove like this: - // while (ax25_get_num_repeaters(pp3) > 0) { - // ax25_remove_addr (pp3, AX25_REPEATER_1); - // } - alevel_t alevel; memset (&alevel, 0, sizeof(alevel)); alevel.mark = -2; // FIXME: Do we want some other special case? @@ -1831,8 +1843,19 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan) * If we recently transmitted a 'message' from some station, * send the position of the message sender when it comes along later. * + * Some refer to this as a courtesy posit report but I don't + * think that is an official term. + * * If we have a position report, look up the sender and see if we should * bypass the normal filtering. + * + * Reference: https://www.aprs-is.net/IGating.aspx + * + * "Passing all message packets also includes passing the sending station's position + * along with the message. When APRS-IS was small, we did this using historical position + * packets. This has become problematic as it introduces historical data on to RF. + * The IGate should note the station(s) it has gated messages to RF for and pass + * the next position packet seen for that station(s) to RF." */ // TODO: Not quite this simple. Should have a function to check for position. diff --git a/src/il2p_header.c b/src/il2p_header.c index 9a1e9ea4..94fd25ba 100644 --- a/src/il2p_header.c +++ b/src/il2p_header.c @@ -437,7 +437,7 @@ packet_t il2p_decode_header_type_1 (unsigned char *hdr, int num_sym_changed) // However, I have seen cases, where the error rate is very high, where the RS decoder // thinks it found a valid code block by changing one symbol but it was the wrong one. // The result is trash. This shows up as address fields like 'R&G4"A' and 'TEW\ !'. -// I added a sanity check here to catch characters other than uppper case letters and digits. +// I added a sanity check here to catch characters other than upper case letters and digits. // The frame should be rejected in this case. The question is whether to discard it // silently or print a message so the user can see that something strange is happening? // My current thinking is that it should be silently ignored if the header has been @@ -462,8 +462,11 @@ packet_t il2p_decode_header_type_1 (unsigned char *hdr, int num_sym_changed) for (int i = 0; i < strlen(addrs[AX25_DESTINATION]); i++) { if (! isupper(addrs[AX25_DESTINATION][i]) && ! isdigit(addrs[AX25_DESTINATION][i])) { if (num_sym_changed == 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("IL2P: Invalid character '%c' in destination address '%s'\n", addrs[AX25_DESTINATION][i], addrs[AX25_DESTINATION]); + // This can pop up sporadically when receiving random noise. + // Would be better to show only when debug is enabled but variable not available here. + // TODO: For now we will just suppress it. + //text_color_set(DW_COLOR_ERROR); + //dw_printf ("IL2P: Invalid character '%c' in destination address '%s'\n", addrs[AX25_DESTINATION][i], addrs[AX25_DESTINATION]); } return (NULL); } @@ -477,8 +480,11 @@ packet_t il2p_decode_header_type_1 (unsigned char *hdr, int num_sym_changed) for (int i = 0; i < strlen(addrs[AX25_SOURCE]); i++) { if (! isupper(addrs[AX25_SOURCE][i]) && ! isdigit(addrs[AX25_SOURCE][i])) { if (num_sym_changed == 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("IL2P: Invalid character '%c' in source address '%s'\n", addrs[AX25_SOURCE][i], addrs[AX25_SOURCE]); + // This can pop up sporadically when receiving random noise. + // Would be better to show only when debug is enabled but variable not available here. + // TODO: For now we will just suppress it. + //text_color_set(DW_COLOR_ERROR); + //dw_printf ("IL2P: Invalid character '%c' in source address '%s'\n", addrs[AX25_SOURCE][i], addrs[AX25_SOURCE]); } return (NULL); } diff --git a/src/il2p_payload.c b/src/il2p_payload.c index 67c79a98..d5fb4887 100644 --- a/src/il2p_payload.c +++ b/src/il2p_payload.c @@ -194,7 +194,7 @@ int il2p_encode_payload (unsigned char *payload, int payload_size, int max_fec, * Purpose: Extract original data from encoded payload. * * Inputs: received Array of bytes. Size is unknown but in practice it - * must not exceeed IL2P_MAX_ENCODED_SIZE. + * must not exceed IL2P_MAX_ENCODED_SIZE. * payload_size 0 to 1023. (IL2P_MAX_PAYLOAD_SIZE) * Expected result size based on header. * max_fec true for 16 parity symbols, false for automatic. diff --git a/src/il2p_test.c b/src/il2p_test.c index c983daff..ff0acfd0 100644 --- a/src/il2p_test.c +++ b/src/il2p_test.c @@ -53,7 +53,7 @@ static void decode_bitstream(void); int main () { int enable_color = 1; - text_color_init (enable_color); + text_color_init (enable_color, 0); int enable_debug_out = 0; il2p_init(enable_debug_out); @@ -974,4 +974,4 @@ alevel_t demod_get_audio_level (int chan, int subchan) return (alevel); } -// end il2p_test.c \ No newline at end of file +// end il2p_test.c diff --git a/src/kiss_frame.c b/src/kiss_frame.c index 862fb714..e304a838 100644 --- a/src/kiss_frame.c +++ b/src/kiss_frame.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2013, 2014, 2017 John Langner, WB2OSZ +// Copyright (C) 2013, 2014, 2017, 2023 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -611,7 +611,8 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, struct /* Verify that the radio channel number is valid. */ /* Any sort of medium should be OK here. */ - if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->chan_medium[chan] == MEDIUM_NONE) { + if ((chan < 0 || chan >= MAX_CHANS || save_audio_config_p->chan_medium[chan] == MEDIUM_NONE) + && save_audio_config_p->chan_medium[chan] != MEDIUM_IGATE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid transmit channel %d from KISS client app.\n", chan); dw_printf ("\n"); diff --git a/src/kissutil.c b/src/kissutil.c index fcd86088..46bcadf7 100644 --- a/src/kissutil.c +++ b/src/kissutil.c @@ -179,7 +179,7 @@ static void trim (char *stuff) int main (int argc, char *argv[]) { - text_color_init (0); // Turn off text color. + text_color_init (0, 0); // Turn off text color. // It could interfere with trying to pipe stdout to some other application. #if __WIN32__ diff --git a/src/server.c b/src/server.c index 8103ea3a..da20d0df 100644 --- a/src/server.c +++ b/src/server.c @@ -1421,7 +1421,7 @@ static THREAD_F cmd_listen_thread (void *arg) } /* - * Call to/from fields are 10 bytes but contents must not exceeed 9 characters. + * Call to/from fields are 10 bytes but contents must not exceed 9 characters. * It's not guaranteed that unused bytes will contain 0 so we * don't issue error message in this case. */ diff --git a/src/symbols.c b/src/symbols.c index 35dba807..c9f07e6e 100644 --- a/src/symbols.c +++ b/src/symbols.c @@ -681,7 +681,7 @@ void symbols_from_dest_or_src (char dti, char *src, char *dest, char *symtab, ch // The position and object formats all contain a proper symbol and table. // There doesn't seem to be much reason to have a symbol for something without -// a postion because it would not show up on a map. +// a position because it would not show up on a map. // This just seems to be a remnant of something used long ago and no longer needed. // The protocol spec mentions a "MIM tracker" but I can't find any references to it. diff --git a/src/textcolor.c b/src/textcolor.c index dea90f09..453d8287 100644 --- a/src/textcolor.c +++ b/src/textcolor.c @@ -171,14 +171,19 @@ static const char clear_eos[] = "\e[0J"; */ static int g_enable_color = 1; +static FILE *g_dw_printf_dest = 0; - -void text_color_init (int enable_color) +void text_color_init (int enable_color, int redirect_output) { - + if (redirect_output != 0) { + g_dw_printf_dest = stderr; + } else { + g_dw_printf_dest = stdout; + } #if __WIN32__ + g_enable_color = enable_color; if (g_enable_color != 0) { @@ -189,7 +194,12 @@ void text_color_init (int enable_color) COORD coord; DWORD nwritten; - h = GetStdHandle(STD_OUTPUT_HANDLE); + if (redirect_output != 0) { + h = GetStdHandle(STD_ERROR_HANDLE); + } else { + h = GetStdHandle(STD_OUTPUT_HANDLE); + } + if (h != NULL && h != INVALID_HANDLE_VALUE) { GetConsoleScreenBufferInfo (h, &csbi); @@ -208,18 +218,18 @@ void text_color_init (int enable_color) 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]); + text_color_init (t, redirect_output); + fprintf (g_dw_printf_dest,"-t %d", t); + if (t) fprintf (g_dw_printf_dest, " [white background] "); + fprintf (g_dw_printf_dest,"\n"); + fprintf (g_dw_printf_dest,"%sBlack ", t_black[t]); + fprintf (g_dw_printf_dest,"%sRed ", t_red[t]); + fprintf (g_dw_printf_dest,"%sGreen ", t_green[t]); + fprintf (g_dw_printf_dest,"%sDark-Green ", t_dark_green[t]); + fprintf (g_dw_printf_dest,"%sYellow ", t_yellow[t]); + fprintf (g_dw_printf_dest,"%sBlue ", t_blue[t]); + fprintf (g_dw_printf_dest, "%sMagenta ", t_magenta[t]); + fprintf (g_dw_printf_dest, "%sCyan \n", t_cyan[t]); } exit (EXIT_SUCCESS); } @@ -232,9 +242,9 @@ void text_color_init (int 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]); + fprintf (g_dw_printf_dest, "%s", t_background_white[t]); + fprintf (g_dw_printf_dest, "%s", clear_eos); + fprintf (g_dw_printf_dest, "%s", t_black[t]); } #endif } @@ -285,7 +295,11 @@ void text_color_set ( enum dw_color_e c ) break; } - h = GetStdHandle(STD_OUTPUT_HANDLE); + if (dw_printf_redirected()) { + h = GetStdHandle(STD_ERROR_HANDLE); + } else { + h = GetStdHandle(STD_OUTPUT_HANDLE); + } if (h != NULL && h != INVALID_HANDLE_VALUE) { SetConsoleTextAttribute (h, attr); @@ -310,30 +324,30 @@ void text_color_set ( enum dw_color_e c ) default: case DW_COLOR_INFO: - printf ("%s", t_black[t]); + fprintf (g_dw_printf_dest, "%s", t_black[t]); break; case DW_COLOR_ERROR: - printf ("%s", t_red[t]); + fprintf (g_dw_printf_dest, "%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]); + fprintf (g_dw_printf_dest, "%s", t_dark_green[t]); break; case DW_COLOR_DECODED: - printf ("%s", t_blue[t]); + fprintf (g_dw_printf_dest, "%s", t_blue[t]); break; case DW_COLOR_XMIT: - printf ("%s", t_magenta[t]); + fprintf (g_dw_printf_dest, "%s", t_magenta[t]); break; case DW_COLOR_DEBUG: - printf ("%s", t_dark_green[t]); + fprintf (g_dw_printf_dest, "%s", t_dark_green[t]); break; } } @@ -377,17 +391,21 @@ int dw_printf (const char *fmt, ...) // TODO: other possible destinations... - fputs (buffer, stdout); + fputs (buffer, g_dw_printf_dest); + fflush (g_dw_printf_dest); return (len); } - +int dw_printf_redirected () +{ + return g_dw_printf_dest != stdout; +} #if TESTC main () { printf ("Initial condition\n"); - text_color_init (1); + text_color_init (1, 0); printf ("After text_color_init\n"); text_color_set(DW_COLOR_INFO); printf ("Info\n"); text_color_set(DW_COLOR_ERROR); printf ("Error\n"); diff --git a/src/textcolor.h b/src/textcolor.h index 4e38c83e..123430e5 100644 --- a/src/textcolor.h +++ b/src/textcolor.h @@ -22,7 +22,7 @@ enum dw_color_e { DW_COLOR_INFO, /* black */ typedef enum dw_color_e dw_color_t; -void text_color_init (int enable_color); +void text_color_init (int enable_color, int redirect_output); void text_color_set (dw_color_t c); void text_color_term (void); @@ -55,4 +55,6 @@ int dw_printf (const char *fmt, ...) __attribute__((format(printf,1,2))); /* gnu C lib. */ #endif +int dw_printf_redirected (); + #endif diff --git a/src/tq.c b/src/tq.c index 3d1b056b..c656af54 100644 --- a/src/tq.c +++ b/src/tq.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2014, 2015, 2016 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2014, 2015, 2016, 2023 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -50,7 +50,8 @@ #include "audio.h" #include "tq.h" #include "dedupe.h" - +#include "igate.h" +#include "dtime_now.h" @@ -195,6 +196,9 @@ void tq_init (struct audio_s *audio_config_p) * * Inputs: chan - Channel, 0 is first. * + * New in 1.7: + * Channel can be assigned to IGate rather than a radio. + * * prio - Priority, use TQ_PRIO_0_HI for digipeated or * TQ_PRIO_1_LO for normal. * @@ -247,6 +251,43 @@ void tq_append (int chan, int prio, packet_t pp) } #endif +// New in 1.7 - A channel can be assigned to the IGate rather than a radio. + +#ifndef DIGITEST // avoid dtest link error + + if (save_audio_config_p->chan_medium[chan] == MEDIUM_IGATE) { + + char ts[100]; // optional time stamp. + + if (strlen(save_audio_config_p->timestamp_format) > 0) { + char tstmp[100]; + timestamp_user_format (tstmp, sizeof(tstmp), save_audio_config_p->timestamp_format); + strlcpy (ts, " ", sizeof(ts)); // space after channel. + strlcat (ts, tstmp, sizeof(ts)); + } + else { + strlcpy (ts, "", sizeof(ts)); + } + + char stemp[256]; // Formated addresses. + ax25_format_addrs (pp, stemp); + unsigned char *pinfo; + int info_len = ax25_get_info (pp, &pinfo); + text_color_set(DW_COLOR_XMIT); + dw_printf ("[%d>is%s] ", chan, ts); + dw_printf ("%s", stemp); /* stations followed by : */ + ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp)); + dw_printf ("\n"); + + igate_send_rec_packet (chan, pp); + ax25_delete(pp); + return; + } +#endif + +// Normal case - put in queue for radio transmission. +// Error if trying to transmit to a radio channel which was not configured. + if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->chan_medium[chan] == MEDIUM_NONE) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan); @@ -281,8 +322,6 @@ void tq_append (int chan, int prio, packet_t pp) * The check would allow an unlimited number of other types. * * Limit was 20. Changed to 100 in version 1.2 as a workaround. - * - * Implementing the 6PACK protocol is probably the proper solution. */ if (ax25_is_aprs(pp) && tq_count(chan,prio,"","",0) > 100) { diff --git a/src/tt_user.c b/src/tt_user.c index 46e44453..a73d6a46 100644 --- a/src/tt_user.c +++ b/src/tt_user.c @@ -882,7 +882,7 @@ static void xmit_object_report (int i, int first_time) * IGate. * * When transmitting over the radio, it gets sent multiple times, to help - * probablity of being heard, with increasing delays between. + * probability of being heard, with increasing delays between. * * The other methods are reliable so we only want to send it once. */ diff --git a/src/waypoint.c b/src/waypoint.c index 70ea3205..20c1cdbc 100644 --- a/src/waypoint.c +++ b/src/waypoint.c @@ -298,7 +298,7 @@ void waypoint_send_sentence (char *name_in, double dlat, double dlong, char symt dw_printf ("waypoint_send_sentence (\"%s\", \"%c%c\")\n", name_in, symtab, symbol); #endif -// Don't waste time if no destintations specified. +// Don't waste time if no destinations specified. if (s_waypoint_serial_port_fd == MYFDERROR && s_waypoint_udp_sock_fd == -1) { diff --git a/src/xmit.c b/src/xmit.c index 9494dac9..13bbaecb 100644 --- a/src/xmit.c +++ b/src/xmit.c @@ -600,7 +600,7 @@ static void * xmit_thread (void *arg) // I don't know if this in some official specification // somewhere, but it is generally agreed that APRS digipeaters // should send only one frame at a time rather than - // bunding multiple frames into a single transmission. + // bundling multiple frames into a single transmission. // Discussion here: http://lists.tapr.org/pipermail/aprssig_lists.tapr.org/2021-September/049034.html break;