diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0cc4d34d..3eecf0bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,7 +83,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 8 - name: dependency @@ -96,7 +96,7 @@ jobs: 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 + brew install portaudio hamlib gpsd hidapi elif [ "$RUNNER_OS" == "Windows" ]; then # add the folder to PATH echo "C:\msys64\mingw32\bin" >> $GITHUB_PATH @@ -149,7 +149,7 @@ jobs: make package fi - name: archive binary - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: direwolf_${{ matrix.config.os }}_${{ matrix.config.arch }}_${{ github.sha }} path: | diff --git a/.github/workflows/codeql-analysis-python.yml b/.github/workflows/codeql-analysis-python.yml new file mode 100644 index 00000000..a47a8f8d --- /dev/null +++ b/.github/workflows/codeql-analysis-python.yml @@ -0,0 +1,64 @@ +# 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 - Python" + +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: [ '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@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + setup-python-dependencies: true + # 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@v3 + + # âšī¸ Command-line programs to run using the OS shell. + # đ https://git.io/JvXDl + + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 7134f213..a86300f3 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -9,7 +9,7 @@ # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # -name: "CodeQL" +name: "CodeQL - CPP" on: push: @@ -32,19 +32,20 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'cpp', 'python' ] + language: [ 'cpp' ] # 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 + uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} + setup-python-dependencies: true # 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. @@ -53,7 +54,7 @@ jobs: # 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 + uses: github/codeql-action/autobuild@v3 # âšī¸ Command-line programs to run using the OS shell. # đ https://git.io/JvXDl @@ -70,4 +71,4 @@ jobs: make test - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v3 diff --git a/.gitignore b/.gitignore index 659c845b..b917a7ab 100644 --- a/.gitignore +++ b/.gitignore @@ -109,5 +109,5 @@ $RECYCLE.BIN/ *.dSYM # cmake -build/ +build*/ tmp/ \ No newline at end of file diff --git a/CHANGES.md b/CHANGES.md index 4b78ca14..a1e2c255 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,11 +1,22 @@ +# Revision History -# Revision History # +## Version 1.8 -- Development Version +### New Features: -## Version 1.7 -- October 2023 ## +- Support for CM108 PTT on Mac. +- New NCHANNEL feature to map a channel number to an external network TCP KISS TNC. See xxx for example of a bridge to LoRa APRS. See [APRS-LoRa-VHF-APRS-Bridge.pdf](https://github.com/wb2osz/direwolf-doc/blob/main/APRS-LoRa-VHF-APRS-Bridge.pdf) for explanation. -### New Documentation: ### +- [http://www.aprs.org/aprs11/tocalls.txt](http://www.aprs.org/aprs11/tocalls.txt) has been abandoned since the end of 2021. [https://github.com/aprsorg/aprs-deviceid](https://github.com/aprsorg/aprs-deviceid) is now considered to be the authoritative source of truth for the vendor/model encoding. + +- New direwolf icon. + +- Include the direwolf icon in the Windows executable. Note: When building from source, environment variable RC must point to windres location. + +## Version 1.7 -- October 2023 + +### New Documentation: Additional documentation location to slow down growth of main repository. [https://github.com/wb2osz/direwolf-doc](https://github.com/wb2osz/direwolf-doc) . These are more oriented toward achieving a goal and understanding, as opposed to the User Guide which describes the functionality. @@ -19,20 +30,17 @@ Additional documentation location to slow down growth of main repository. [http - ***Understanding APRS Packets*** - -### New Features: ### - - +### New Features: - New ICHANNEL configuration option to map a KISS client application channel to APRS-IS. Packets from APRS-IS will be presented to client applications as the specified channel. Packets sent, by client applications, to that channel will go to APRS-IS rather than a radio channel. Details in ***Internal-Packet-Routing.pdf***. - New variable speed option for gen_packets. For example, "-v 5,0.1" would generate packets from 5% too slow to 5% too fast with increments of 0.1. Some implementations might have imprecise timing. Use this to test how well TNCs tolerate sloppy timing. - Improved Layer 2 Protocol [(IL2P)](https://en.wikipedia.org/wiki/FX.25_Forward_Error_Correction). Compatible with Nino TNC for 1200 and 9600 bps. Use "-I 1" on command line to enable transmit for first channel. For more general case, add to config file (simplified version, see User Guide for more details): - - > After: "CHANNEL 1" (or other channel) - > - > Add: "IL2PTX 1" + + > After: "CHANNEL 1" (or other channel) + > + > Add: "IL2PTX 1" - Limited support for CM108/CM119 GPIO PTT on Windows. @@ -43,14 +51,12 @@ Additional documentation location to slow down growth of main repository. [http - The BEACON configuration now recognizes the SOURCE= option. This replaces the AX.25 source address rather than using the MYCALL value for the channel. This is useful for sending more than 5 analog telemetry channels. Use two, or more, source addresses with up to 5 analog channels each. - For more flexibility, the FX.25 transmit property can now be set individually by channel, rather than having a global setting for all channels. The -X on the command line applies only to channel 0. For other channels you need to add a new line to the configuration file. You can specify a specific number of parity bytes (16, 32, 64) or 1 to choose automatically based on packet size. + + > After: "CHANNEL 1" (or other channel) + > + > Add: "FX25TX 1" (or 16 or 32 or 64) - > After: "CHANNEL 1" (or other channel) - > - > Add: "FX25TX 1" (or 16 or 32 or 64) - - - -### Bugs Fixed: ### +### Bugs Fixed: - The t/m packet filter incorrectly included bulletins. It now allows only "messages" to specific stations. Use of t/m is discouraged. i/180 is the preferred filter for messages to users recently heard locally. @@ -58,24 +64,21 @@ Additional documentation location to slow down growth of main repository. [http - Fixed build for Alpine Linux. -### Notes: ### +### Notes: The Windows binary distribution now uses gcc (MinGW) version 11.3.0. The Windows version is built for both 32 and 64 bit operating systems. Use the 64 bit version if possible; it runs considerably faster. -## Version 1.6 -- October 2020 ## - -### New Build Procedure: ### +## Version 1.6 -- October 2020 +### New Build Procedure: - Rather than trying to keep a bunch of different platform specific Makefiles in sync, "cmake" is now used for greater portability and easier maintenance. This was contributed by Davide Gerhard. - README.md has a quick summary of the process. More details in the ***User Guide***. - -### New Features: ### - +### New Features: - "-X" option enables FX.25 transmission. FX.25 reception is always enabled so you don't need to do anything special. "What is FX.25?" you might ask. It is forward error correction (FEC) added in a way that is completely compatible with an ordinary AX.25 frame. See new document ***AX25\_plus\_FEC\_equals\_FX25.pdf*** for details. @@ -85,7 +88,6 @@ Use the 64 bit version if possible; it runs considerably faster. - "-t" option now accepts more values to accommodate inconsistent handling of text color control codes by different terminal emulators. The default, 1, should work with most modern terminal types. If the colors are not right, try "-t 9" to see the result of the different choices and pick the best one. If none of them look right, file a bug report and specify: operating system version (e.g. Raspbian Buster), terminal emulator type and version (e.g. LXTerminal 0.3.2). Include a screen capture. - - "-g" option to force G3RUH mode for lower speeds where a different modem type may be the default. - 2400 bps compatibility with MFJ-2400. See ***2400-4800-PSK-for-APRS-Packet-Radio.pdf*** for details @@ -94,15 +96,11 @@ Use the 64 bit version if possible; it runs considerably faster. - Add support for Multi-GNSS NMEA sentences. - - -### Bugs Fixed: ### +### Bugs Fixed: - Proper counting of frames in transmit queue for AGW protocol 'Y' command. - - -### New Documentation: ### +### New Documentation: - ***AX.25 + FEC = FX.25*** @@ -116,18 +114,15 @@ Use the 64 bit version if possible; it runs considerably faster. - [***Dire Wolf PowerPoint Slide Show***](https://github.com/wb2osz/direwolf-presentation) -### Notes: ### +### Notes: The Windows binary distribution now uses gcc (MinGW) version 7.4.0. The Windows version is built for both 32 and 64 bit operating systems. Use the 64 bit version if possible; it runs considerably faster. +## Version 1.5 -- September 2018 - -## Version 1.5 -- September 2018 ## - - -### New Features: ### +### New Features: - PTT using GPIO pin of CM108/CM119 (e.g. DMK URI, RB-USB RIM), Linux only. @@ -153,9 +148,7 @@ Use the 64 bit version if possible; it runs considerably faster. - Allow single log file with fixed name rather than starting a new one each day. - - -### Bugs Fixed: ### +### Bugs Fixed: - Possible crash when CDIGIPEAT did not include the optional alias. @@ -165,27 +158,23 @@ Use the 64 bit version if possible; it runs considerably faster. - Under certain conditions, outgoing connected mode data would get stuck in a queue and not be transmitted. This could happen if client application sends a burst of data larger than the "window" size (MAXFRAME or EMAXFRAME option). - - Little typographical / spelling errors in messages. - -### Documentation: ### - +### Documentation: - New document ***Bluetooth-KISS-TNC.pdf*** explaining how to use KISS over Bluetooth. - Updates describing cheap SDR frequency inaccuracy and how to compensate for it. -### Notes: ### +### Notes: Windows binary distribution now uses gcc (MinGW) version 6.3.0. ---------- -## Version 1.4 -- April 2017 ## - +## Version 1.4 -- April 2017 -### New Features: ### +### New Features: - AX.25 v2.2 connected mode. See chapter 10 of User Guide for details. @@ -196,6 +185,7 @@ Windows binary distribution now uses gcc (MinGW) version 6.3.0. - Expanded debug options so you can understand what is going on with packet filtering. - Added new document ***Successful-APRS-IGate-Operation.pdf*** with IGate background, configuration, and troubleshooting tips. + - 2400 & 4800 bps PSK modems. See ***2400-4800-PSK-for-APRS-Packet-Radio.pdf*** in the doc directory for discussion. - The top speed of 9600 bps has been increased to 38400. You will need a sound card capable of 96k or 192k samples per second for the higher rates. Radios must also have adequate bandwidth. See ***Going-beyond-9600-baud.pdf*** in the doc directory for more details. @@ -203,11 +193,11 @@ Windows binary distribution now uses gcc (MinGW) version 6.3.0. - Better decoder performance for 9600 and higher especially for low audio sample rate to baud ratios. - Generate waypoint sentences for use by AvMap G5 / G6 or other mapping devices or applications. Formats include - - $GPWPL - NMEA generic with only location and name. - - $PGRMW - Garmin, adds altitude, symbol, and comment to previously named waypoint. - - $PMGNWPL - Magellan, more complete for stationary objects. - - $PKWDWPL - Kenwood with APRS style symbol but missing comment. - + + - $GPWPL - NMEA generic with only location and name. + - $PGRMW - Garmin, adds altitude, symbol, and comment to previously named waypoint. + - $PMGNWPL - Magellan, more complete for stationary objects. + - $PKWDWPL - Kenwood with APRS style symbol but missing comment. - DTMF tones can be sent by putting "DTMF" in the destination address, similar to the way that Morse Code is sent. @@ -217,9 +207,7 @@ Windows binary distribution now uses gcc (MinGW) version 6.3.0. - More flexible dw-start.sh start up script for both GUI and CLI environments. - - -### Bugs Fixed: ### +### Bugs Fixed: - The transmitter (PTT control) was being turned off too soon when sending Morse Code. @@ -228,71 +216,64 @@ Windows binary distribution now uses gcc (MinGW) version 6.3.0. - Longer tocall.txt files can now be handled. - Sometimes kissattach would have an issue with the Dire Wolf pseudo terminal. This showed up most often on Raspbian but sometimes occurred with other versions of Linux. - - *kissattach: Error setting line discipline: TIOCSETD: Device or resource busy - Are you sure you have enabled MKISS support in the kernel - or, if you made it a module, that the module is loaded?* - + + *kissattach: Error setting line discipline: TIOCSETD: Device or resource busy + Are you sure you have enabled MKISS support in the kernel + or, if you made it a module, that the module is loaded?* - Sometimes writes to a pseudo terminal would block causing the received -frame processing thread to hang. The first thing you will notice is that -received frames are not being printed. After a while this message will appear: - - *Received frame queue is out of control. Length=... Reader thread is probably - frozen. This can be caused by using a pseudo terminal (direwolf -p) where - another application is not reading the frames from the other side.* + frame processing thread to hang. The first thing you will notice is that + received frames are not being printed. After a while this message will appear: + + *Received frame queue is out of control. Length=... Reader thread is probably + frozen. This can be caused by using a pseudo terminal (direwolf -p) where + another application is not reading the frames from the other side.* - -p command line option caused segmentation fault with glibc >= 2.24. - - The Windows version 1.3 would crash when starting to transmit on Windows XP. There have also been some other reports of erratic behavior on Windows. The crashing problem was fixed in in the 1.3.1 patch release. Linux version was not affected. - IGate did not retain nul characters in the information part of a packet. This should never happen with a valid APRS packet but there are a couple cases where it has. If we encounter these malformed packets, pass them along as-is, rather than truncating. - Don't digipeat packets when the source is my call. - - ---------- -## Version 1.3 -- May 2016 ## +## Version 1.3 -- May 2016 -### New Features: ### +### New Features: - Support for Mac OS X. - Many APRStt enhancements including: Morse code and speech responses to to APRStt tone sequences, new 5 digit callsign suffix abbreviation, -position ambiguity for latitude and longitude in object reports + position ambiguity for latitude and longitude in object reports - APRS Telemetry Toolkit. - + - GPS Tracker beacons are now available for the Windows version. Previously this was only in the Linux version. - SATgate mode for IGate. Packets heard directly are delayed before being sent -to the Internet Server. This favors digipeated packets because the original -arrives later and gets dropped if there are duplicates. + to the Internet Server. This favors digipeated packets because the original + arrives later and gets dropped if there are duplicates. - Added support for hamlib. This provides more flexible options for PTT control. - Implemented AGW network protocol 'M' message for sending UNPROTO information without digipeater path. - - A list of all symbols available can be obtained with the -S -command line option. + command line option. - Command line option "-a n" to print audio device statistics each n seconds. Previously this was always each 100 seconds on Linux and not available on Windows. -### Bugs Fixed: ### - - +### Bugs Fixed: - Fixed several cases where crashes were caused by unexpected packet contents: - - - When receiving packet with unexpected form of GPS NMEA sentence. - - - When receiving packet with comment of a few hundred characters. - - - Address in path, from Internet server, more than 9 characters. + + - When receiving packet with unexpected form of GPS NMEA sentence. + + - When receiving packet with comment of a few hundred characters. + + - Address in path, from Internet server, more than 9 characters. - "INTERNAL ERROR: dlq_append NULL packet pointer." when using PASSALL. @@ -301,27 +282,27 @@ command line option. - Tracker beacons were not always updating the location properly. - AGW network protocol now works properly for big-endian processors -such as PowerPC or MIPS. + such as PowerPC or MIPS. - Packet filtering treated telemetry metadata as messages rather than telemetry. ---------- -## Version 1.2 -- June 2015 ## +## Version 1.2 -- June 2015 -### New Features ### +### New Features - Improved decoder performance. -Over 1000 error-free frames decoded from WA8LMF TNC Test CD. -See ***A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf*** for details. + Over 1000 error-free frames decoded from WA8LMF TNC Test CD. + See ***A-Better-APRS-Packet-Demodulator-Part-1-1200-baud.pdf*** for details. - Up to 3 soundcards and 6 radio channels can be handled at the same time. - New framework for applications which listen for Touch Tone commands -and respond with voice. A sample calculator application is included -as a starting point for building more interesting applications. -For example, if it hears the DTMF sequence "2*3*4#" it will respond -with the spoken words "Twenty Four." + and respond with voice. A sample calculator application is included + as a starting point for building more interesting applications. + For example, if it hears the DTMF sequence "2*3*4#" it will respond + with the spoken words "Twenty Four." - Reduced latency for transfers to/from soundcards. @@ -334,146 +315,140 @@ with the spoken words "Twenty Four." - Attempted fixing of corrupted bits now works for 9600 baud. - Implemented AGW network protocol 'y' message so applications can -throttle generation of packets when sending a large file. + throttle generation of packets when sending a large file. - When using serial port RTS/DTR to activate transmitter, the two -control lines can now be driven with opposite polarity as required -by some interfaces. + control lines can now be driven with opposite polarity as required + by some interfaces. - Data Carrier Detect (DCD) can be sent to an output line (just -like PTT) to activate a carrier detect light. + like PTT) to activate a carrier detect light. - Linux "man" pages for on-line documentation. - AGWPORT and KISSPORT can be set to 0 to disable the interfaces. - APRStt gateway enhancements: MGRS/USNG coordinates, new APRStt3 -format call, satellite grid squares. - + format call, satellite grid squares. -### Bugs fixed ### +### Bugs fixed - Fixed "gen_packets" so it now handles user-specified messages correctly. - Under some circumstances PTT would be held on long after the transmit -audio was finished. + audio was finished. - - -### Known problems ### +### Known problems - Sometimes writes to a pseudo terminal will block causing the received -frame processing thread to hang. The first thing you will notice is that -received frames are not being printed. After a while this message will appear: - + frame processing thread to hang. The first thing you will notice is that + received frames are not being printed. After a while this message will appear: + Received frame queue is out of control. Length=... Reader thread is probably frozen. This can be caused by using a pseudo terminal (direwolf -p) where another application is not reading the frames from the other side. ----------- -## Version 1.1 -- December 2014 ## +## Version 1.1 -- December 2014 -### New Features ### +### New Features - Logging of received packets and utility to convert log file -into GPX format. + into GPX format. - AGW network port formerly allowed only one connection at a -time. It can now accept 3 client applications at the same time. -(Same has not yet been done for network KISS port.) + time. It can now accept 3 client applications at the same time. + (Same has not yet been done for network KISS port.) - Frequency / offset / tone standard formats are now recognized. -Non-standard attempts, in the comment, are often detected and -a message suggests the correct format. + Non-standard attempts, in the comment, are often detected and + a message suggests the correct format. - Telemetry is now recognized. Messages are printed for -usage that does not adhere to the published standard. + usage that does not adhere to the published standard. - Tracker function transmits location from GPS position. -New configuration file options: TBEACON and SMARTBEACONING. -(For Linux only. Warning - has not been well tested.) + New configuration file options: TBEACON and SMARTBEACONING. + (For Linux only. Warning - has not been well tested.) - Experimental packet regeneration feature for HF use. -Will be documented later if proves to be useful... + Will be documented later if proves to be useful... - Several enhancements for trying to fix incorrect CRC: -Additional types of attempts to fix a bad CRC. -Optimized code to reduce execution time. -Improved detection of duplicate packets from different fixup attempts. -Set limit on number of packets in fix up later queue. + Additional types of attempts to fix a bad CRC. + Optimized code to reduce execution time. + Improved detection of duplicate packets from different fixup attempts. + Set limit on number of packets in fix up later queue. - Beacon positions can be specified in either latitude / longitude -or UTM coordinates. + or UTM coordinates. - It is still highly recommended, but no longer mandatory, that -beaconing be enabled for digipeating to work. - + beaconing be enabled for digipeating to work. * Bugs fixed: - - For Windows version, maximum serial port was COM9. -It is now possible to use COM10 and higher. + It is now possible to use COM10 and higher. - Fixed issue with KISS protocol decoder state that showed up -only with "binary" data in packets (e.g. RMS Express). + only with "binary" data in packets (e.g. RMS Express). - An extra 00 byte was being appended to packets from AGW -network protocol 'K' messages. + network protocol 'K' messages. - Invalid data from an AGW client application could cause an -application crash. + application crash. - OSS (audio interface for non-Linux versions of Unix) should -be better now. + be better now. -### Known problems ### +### Known problems - Sometimes kissattach fails to connect with "direwolf -p". -The User Guide and Raspberry Pi APRS document have a couple work-arounds. + The User Guide and Raspberry Pi APRS document have a couple work-arounds. ----------- -## Version 1.0a -- May 2014 ## +## Version 1.0a -- May 2014 -### Bug fixed ### +### Bug fixed - Beacons sent directly to IGate server had incorrect source address. ----------- -## Version 1.0 -- May 2014 ## +## Version 1.0 -- May 2014 -### New Features ### +### New Features - Received audio can be obtained with a UDP socket or stdin. -This can be used to take audio from software defined radios -such as rtl_fm or gqrx. + This can be used to take audio from software defined radios + such as rtl_fm or gqrx. - 9600 baud data rate. - New PBEACON and OBEACON configuration options. Previously -it was necessary to handcraft beacons. + it was necessary to handcraft beacons. - Less CPU power required for 300 baud. This is important -if you want to run a bunch of decoders at the same time -to tolerate off-frequency HF SSB signals. + if you want to run a bunch of decoders at the same time + to tolerate off-frequency HF SSB signals. - Improved support for UTF-8 character set. - Improved troubleshooting display for APRStt macros. - In earlier versions, the DTMF decoder was always active because it -took a negligible amount of CPU time. Unfortunately this sometimes -resulted in too many false positives from some other types of digital -transmissions heard on HF. Starting in version 1.0, the DTMF decoder -is enabled only when the APRStt gateway is configured. - + took a negligible amount of CPU time. Unfortunately this sometimes + resulted in too many false positives from some other types of digital + transmissions heard on HF. Starting in version 1.0, the DTMF decoder + is enabled only when the APRStt gateway is configured. ----------- -## Version 0.9 --November 2013 ## +## Version 0.9 --November 2013 -### New Features ### +### New Features - Selection of non-default audio device for Linux ALSA. @@ -488,25 +463,23 @@ is enabled only when the APRStt gateway is configured. - Command line option "-t 0" to disable text colors. - APRStt macros which allow short numeric only touch tone -sequences to be processed as much longer predefined sequences. - + sequences to be processed as much longer predefined sequences. -### Bugs Fixed ### +### Bugs Fixed - Now works on 64 bit target. -### New Restriction for Windows version ### +### New Restriction for Windows version - Minimum processor is now Pentium 3 or equivalent or later. -It's possible to run on something older but you will need -to rebuild it from source. - + It's possible to run on something older but you will need + to rebuild it from source. ----------- -## Version 0.8 -- August 2013 ## +## Version 0.8 -- August 2013 -### New Features ### +### New Features - Internet Gateway (IGate) including IPv6 support. @@ -515,64 +488,59 @@ to rebuild it from source. - Preemptive digipeating option. - KISS TNC should now work with connected AX.25 protocols -(e.g. AX25 for Linux), not just APRS. - + (e.g. AX25 for Linux), not just APRS. ---------- -## Version 0.7 -- March 2013 ## +## Version 0.7 -- March 2013 -### New Features: ### +### New Features: - Added APRStt gateway capability. For details, see ***APRStt-Implementation-Notes.pdf*** - ----------- -## Version 0.6 -- February 2013 ## +## Version 0.6 -- February 2013 -### New Features ### +### New Features - Improved performance of AFSK demodulator. -Now decodes 965 frames from Track 2 of WA8LMF's TNC Test CD. + Now decodes 965 frames from Track 2 of WA8LMF's TNC Test CD. - KISS protocol now available thru a TCP socket. -Default port is 8001. -Change it with KISSPORT option in configuration file. + Default port is 8001. + Change it with KISSPORT option in configuration file. - Ability to salvage frames with bad FCS. -See section mentioning "bad apple" in the user guide. -Default of fixing 1 bit works well. -Fixing more bits not recommended because there is a high -probability of occasional corrupted data getting thru. + See section mentioning "bad apple" in the user guide. + Default of fixing 1 bit works well. + Fixing more bits not recommended because there is a high + probability of occasional corrupted data getting thru. - Added AGW "monitor" format messages. -Now compatible with APRS-TW for telemetry. - + Now compatible with APRS-TW for telemetry. -### Known Problem ### +### Known Problem - The Linux (but not Cygwin) version eventually hangs if nothing is -reading from the KISS pseudo terminal. Some operating system -queue fills up, the application write blocks, and decoding stops. + reading from the KISS pseudo terminal. Some operating system + queue fills up, the application write blocks, and decoding stops. - -### Workaround ### +### Workaround - If another application is not using the serial KISS interface, -run this in another window: - - tail -f /tmp/kisstnc + run this in another window: + + tail -f /tmp/kisstnc ----------- -## Version 0.5 -- March 2012 ## +## Version 0.5 -- March 2012 - More error checking and messages for invalid APRS data. ----------- -## Version 0.4 -- September 2011 ## +## Version 0.4 -- September 2011 - First general availability. - diff --git a/CMakeLists.txt b/CMakeLists.txt index 84aeb738..7d0e1fb2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,10 @@ -cmake_minimum_required(VERSION 3.1.0) +cmake_minimum_required(VERSION 3.5.0) project(direwolf) # configure version set(direwolf_VERSION_MAJOR "1") -set(direwolf_VERSION_MINOR "7") +set(direwolf_VERSION_MINOR "8") set(direwolf_VERSION_PATCH "0") set(direwolf_VERSION_SUFFIX "Development") @@ -210,6 +210,7 @@ if (C_CLANG OR C_GCC) # TODO: # Try error checking -fsanitize=bounds-strict -fsanitize=leak # Requires libubsan and liblsan, respectively. + # Maybe -fstack-protector-all, -fstack-check ###set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wvla -ffast-math -ftree-vectorize -D_XOPEN_SOURCE=600 -D_DEFAULT_SOURCE ${EXTRA_FLAGS}") if(FREEBSD) @@ -320,6 +321,14 @@ else() set(HAMLIB_LIBRARIES "") endif() +find_package(gpiod) +if(GPIOD_FOUND) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_GPIOD") +else() + set(GPIOD_INCLUDE_DIRS "") + set(GPIOD_LIBRARIES "") +endif() + if(LINUX) find_package(ALSA REQUIRED) if(ALSA_FOUND) @@ -342,6 +351,17 @@ elseif (HAVE_SNDIO) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_SNDIO") endif() +elseif (APPLE) + find_package(Portaudio REQUIRED) + if(PORTAUDIO_FOUND) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_PORTAUDIO") + endif() + + find_package(hidapi REQUIRED) + if(HIDAPI_FOUND) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_CM108") + endif() + elseif (NOT WIN32 AND NOT CYGWIN) find_package(Portaudio REQUIRED) if(PORTAUDIO_FOUND) @@ -367,7 +387,10 @@ add_subdirectory(data) # external libraries add_subdirectory(${CUSTOM_GEOTRANZ_DIR}) add_subdirectory(${CUSTOM_REGEX_DIR}) -add_subdirectory(${CUSTOM_HIDAPI_DIR}) +if(NOT APPLE) + # Mac builds use the hidapi library, not custom local files + add_subdirectory(${CUSTOM_HIDAPI_DIR}) +endif() add_subdirectory(${CUSTOM_MISC_DIR}) # direwolf source code and utilities diff --git a/README.md b/README.md index 3006a1ef..f8de5897 100644 --- a/README.md +++ b/README.md @@ -208,13 +208,43 @@ Results will vary depending on your hardware platform and operating system versi sudo yum install direwolf -### Macintosh OS X ### +### Macintosh macOS - Using Homebrew ### -Read the **User Guide** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). It is more complicated than Linux. +The following instructions have been verified on macOS Ventura 13.6 (M2) and macOS High Sierra 10.13.6 (Intel). + +First make sure that you have the following tools installed on your Mac: + +- [Xcode or Xcode Command Line Tools](https://developer.apple.com/xcode/resources/) +- [Homebrew](https://brew.sh/) + +You will need to install the following packages using Homebrew: + + brew install cmake + brew install portaudio + brew install hidapi + +Then follow the same instructions as above for the Linux `git clone` build: + + cd ~ + git clone https://www.github.com/wb2osz/direwolf + cd direwolf + git checkout dev + mkdir build && cd build + cmake .. + make -j4 + sudo make install + make install-conf + +This gives you the latest development version. Leave out the "git checkout dev" to get the most recent stable release. + +For more information, see the ***User Guide*** in the [**doc** directory](https://github.com/wb2osz/direwolf/tree/master/doc). If you have problems, post them to the [Dire Wolf packet TNC](https://groups.io/g/direwolf) discussion group. -You can also install a pre-built version from Mac Ports. Keeping this up to date depends on volunteers who perform the packaging. This version could lag behind development. + +### Macintosh macOS - Prebuilt version ### + +You can also install a pre-built version from MacPorts. Keeping this up to date depends on volunteers who perform the packaging. This version could lag behind development. sudo port install direwolf diff --git a/cmake/cpack/direwolf.desktop.in b/cmake/cpack/direwolf.desktop.in index 79c63aa6..6546ad7f 100644 --- a/cmake/cpack/direwolf.desktop.in +++ b/cmake/cpack/direwolf.desktop.in @@ -6,5 +6,5 @@ Icon=@CMAKE_PROJECT_NAME@_icon.png StartupNotify=true Terminal=false Type=Application -Categories=HamRadio -Keywords=Ham Radio;APRS;Soundcard TNC;KISS;AGWPE;AX.25 \ No newline at end of file +Categories=Network;HamRadio +Keywords=Ham Radio;APRS;Soundcard TNC;KISS;AGWPE;AX.25 diff --git a/cmake/cpack/direwolf_icon.ico b/cmake/cpack/direwolf_icon.ico index 00714be0..6091cebb 100644 Binary files a/cmake/cpack/direwolf_icon.ico and b/cmake/cpack/direwolf_icon.ico differ diff --git a/cmake/cpack/direwolf_icon.png b/cmake/cpack/direwolf_icon.png index 4898018f..01b7d133 100644 Binary files a/cmake/cpack/direwolf_icon.png and b/cmake/cpack/direwolf_icon.png differ diff --git a/cmake/include/uninstall.cmake.in b/cmake/include/uninstall.cmake.in index 2037e365..8ddc56a6 100644 --- a/cmake/include/uninstall.cmake.in +++ b/cmake/include/uninstall.cmake.in @@ -7,10 +7,10 @@ string(REGEX REPLACE "\n" ";" files "${files}") foreach(file ${files}) message(STATUS "Uninstalling $ENV{DESTDIR}${file}") if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") - exec_program( - "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + execute_process( + COMMAND "@CMAKE_COMMAND@" -E remove "$ENV{DESTDIR}${file}" OUTPUT_VARIABLE rm_out - RETURN_VALUE rm_retval + RESULT_VARIABLE rm_retval ) if(NOT "${rm_retval}" STREQUAL 0) message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") diff --git a/cmake/modules/FindCompiler.cmake b/cmake/modules/FindCompiler.cmake index fe036e4b..91e1b89c 100644 --- a/cmake/modules/FindCompiler.cmake +++ b/cmake/modules/FindCompiler.cmake @@ -5,9 +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_EQUAL 1920 AND MSVC_VERSION LESS_EQUAL 1929) + if(MSVC_VERSION GREATER 1919 AND MSVC_VERSION LESS 1926) set(VS2019 ON) - elseif(MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS_EQUAL 1919) + elseif(MSVC_VERSION GREATER 1910 AND MSVC_VERSION LESS 1919) set(VS2017 ON) elseif(MSVC_VERSION GREATER 1899 AND MSVC_VERSION LESS 1910) set(VS2015 ON) diff --git a/cmake/modules/Findgpiod.cmake b/cmake/modules/Findgpiod.cmake new file mode 100644 index 00000000..bf5be305 --- /dev/null +++ b/cmake/modules/Findgpiod.cmake @@ -0,0 +1,23 @@ +# - Try to find libgpiod +# Once done this will define +# GPIOD_FOUND - System has libgpiod +# GPIOD_INCLUDE_DIRS - The libgpiod include directories +# GPIOD_LIBRARIES - The libraries needed to use libgpiod +# GPIOD_DEFINITIONS - Compiler switches required for using libgpiod + +find_package(PkgConfig) +pkg_check_modules(PC_GPIOD QUIET gpiod) + +find_path(GPIOD_INCLUDE_DIR gpiod.h) +find_library(GPIOD_LIBRARY NAMES gpiod) + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set GPIOD_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args(gpiod DEFAULT_MSG + GPIOD_LIBRARY GPIOD_INCLUDE_DIR) + +mark_as_advanced(GPIOD_INCLUDE_DIR GPIOD_LIBRARY) + +set(GPIOD_LIBRARIES ${GPIOD_LIBRARY}) +set(GPIOD_INCLUDE_DIRS ${GPIOD_INCLUDE_DIR}) diff --git a/cmake/modules/Findhidapi.cmake b/cmake/modules/Findhidapi.cmake new file mode 100644 index 00000000..163c8c2a --- /dev/null +++ b/cmake/modules/Findhidapi.cmake @@ -0,0 +1,44 @@ +# - Try to find hidapi +# +# HIDAPI_FOUND - system has hidapi +# HIDAPI_LIBRARIES - location of the library for hidapi +# HIDAPI_INCLUDE_DIRS - location of the include files for hidapi + +set(HIDAPI_ROOT_DIR + "${HIDAPI_ROOT_DIR}" + CACHE + PATH + "Directory to search for hidapi") + +# no need to check pkg-config + +find_path(HIDAPI_INCLUDE_DIRS + NAMES + hidapi.h + PATHS + /usr/local/include + /usr/include + /opt/local/include + HINTS + ${HIDAPI_ROOT_DIR} + PATH_SUFFIXES + hidapi + ) + +find_library(HIDAPI_LIBRARIES + NAMES + hidapi + PATHS + /usr/local/lib + /usr/lib + /usr/lib64 + /opt/local/lib + HINTS + ${HIDAPI_ROOT_DIR} + ) + + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(HIDAPI DEFAULT_MSG HIDAPI_INCLUDE_DIRS HIDAPI_LIBRARIES) + +mark_as_advanced(HIDAPI_INCLUDE_DIRS HIDAPI_LIBRARIES) diff --git a/conf/CMakeLists.txt b/conf/CMakeLists.txt index d4a229d7..9f45e15c 100644 --- a/conf/CMakeLists.txt +++ b/conf/CMakeLists.txt @@ -25,8 +25,18 @@ string(REGEX REPLACE "^%C%([^\n]*)" "\\1" file_content "${file_content}") file(WRITE "${CMAKE_BINARY_DIR}/direwolf.conf" "${file_content}") # install udev rules for CM108 +# There are two locations. The one in /etc/udev/rules.d is meant for local customization and +# takes precedence for the same name. +# https://sources.debian.org/src/direwolf/1.7+dfsg-2/debian/patches/lib-udev-rules/ +# says that we should use the /usr/lib/udev/rules.d location when building a package. +# TODO: I think the proper solution is to select the location based on whether +# the application installation location is /usr/local or /usr. if(LINUX) - install(FILES "${CUSTOM_CONF_DIR}/99-direwolf-cmedia.rules" DESTINATION /etc/udev/rules.d/) + if (CMAKE_INSTALL_PREFIX STREQUAL "/usr/local") + install(FILES "${CUSTOM_CONF_DIR}/99-direwolf-cmedia.rules" DESTINATION /etc/udev/rules.d/) + else() + install(FILES "${CUSTOM_CONF_DIR}/99-direwolf-cmedia.rules" DESTINATION /usr/lib/udev/rules.d/) + endif() endif() install(FILES "${CMAKE_BINARY_DIR}/direwolf.conf" DESTINATION ${INSTALL_CONF_DIR}) diff --git a/conf/generic.conf b/conf/generic.conf index 9a19d8a2..58c4ea16 100644 --- a/conf/generic.conf +++ b/conf/generic.conf @@ -1,6 +1,6 @@ %C%############################################################# %C%# # -%C%# Configuration file for Dire Wolf # +%C%# Sample configuration file for Dire Wolf # %C%# # %L%# Linux version # %W%# Windows version # @@ -14,7 +14,7 @@ %R% It would be a maintenance burden to keep most of %R% two different versions in sync. %R% This common source is now used to generate the -%R% two different variations while having only a single +%R% three different variations while having only a single %R% copy of the common parts. %R% %R% The first column contains one of the following: @@ -38,6 +38,10 @@ %M%# /usr/local/share/doc/direwolf/ or /usr/share/doc/direwolf/ %M%# Concise "man" pages are also available for Mac OSX. %C%# +%C%# Recommended Reading for everyone: +%C%# "Understanding APRS Packets" in https://github.com/wb2osz/aprsspec +%C%# +%C%# %C%# Questions??? Join the discussion forum: https://groups.io/g/direwolf %C%# %C%# @@ -90,7 +94,7 @@ %C%############################################################# %C%# # %C%# FIRST AUDIO DEVICE PROPERTIES # -%C%# (Channel 0 + 1 if in stereo) # +%C%# (Channel 0 or 0 + 1 if in stereo) # %C%# # %C%############################################################# %C% @@ -121,11 +125,12 @@ %W%# * 4: Speakers (Realtek High Definiti (channels 0 & 1) %W%# 5: Realtek Digital Output (Realtek %W%# -%W%# Example: To use the microphone and speaker connections on the -%W%# system board, either of these forms can be used: +%W%# It is recommended that you use a unique substring of the device description. +%W%# For example, use "High" or "Realtek High Def" for the built in sound system. +%W%# Use "USB", or a longer string to distinguish amount multiple devices for a USB audio. +%W%# You can also use numbers but you are asking for trouble. Device numbers can change. %W% -%W%#ADEVICE High -%W%#ADEVICE 3 4 +%W%#ADEVICE USB %W% %W% %W%# Example: To use the USB Audio, use a command like this with @@ -158,16 +163,6 @@ %L% %L%# ADEVICE plughw:1,0 %L% -%L%# You can also use "-" or "stdin" to pipe stdout from -%L%# some other application such as a software defined radio. -%L%# "stdin" is not an audio device. Don't use this unless you -%L%# understand what this means. Read the User Guide. -%L%# You can also specify "UDP:" and an optional port for input. -%L%# Something different must be specified for output. -%L% -%L%# ADEVICE stdin plughw:1,0 -%L%# ADEVICE UDP:7355 default -%L% %R% ---------- Mac ---------- %R% %M%# Macintosh Operating System uses portaudio driver for audio @@ -182,44 +177,9 @@ %M% %M%# ADEVICE "USB Audio Codec:6" "USB Audio Codec:5" %M%# -%M%# -%M%# You can also use "-" or "stdin" to pipe stdout from -%M%# some other application such as a software defined radio. -%M%# "stdin" is not an audio device. Don't use this unless you -%M%# understand what this means. Read the User Guide. -%M%# You can also specify "UDP:" and an optional port for input. -%M%# Something different must be specified for output. %M% -%M%# ADEVICE UDP:7355 default -%M%# -%C% -%C%# -%C%# Number of audio channels for this souncard: 1 (mono) or 2 (stereo). -%C%# 1 is the default so there is no need to specify it. -%C%# -%C% -%C%#ACHANNELS 2 -%C% -%C% -%C%############################################################# -%C%# # -%C%# SECOND AUDIO DEVICE PROPERTIES # -%C%# (Channel 2 + 3 if in stereo) # -%C%# # -%C%############################################################# -%C% -%C%#ADEVICE1 ... -%C% -%C% -%C%############################################################# -%C%# # -%C%# THIRD AUDIO DEVICE PROPERTIES # -%C%# (Channel 4 + 5 if in stereo) # -%C%# # -%C%############################################################# -%C% -%C%#ADEVICE2 ... -%C% +%C%# Many more details and examples can be found in: +%C%# https://github.com/wb2osz/direwolf-doc/blob/main/Radio-Interface-Guide.pdf %C% %C%############################################################# %C%# # @@ -230,11 +190,6 @@ %C%CHANNEL 0 %C% %C%# -%C%# The following MYCALL, MODEM, PTT, etc. configuration items -%C%# apply to the most recent CHANNEL. -%C%# -%C% -%C%# %C%# Station identifier for this channel. %C%# Multiple channels can have the same or different names. %C%# @@ -259,7 +214,7 @@ %C%# In most cases you can just specify the speed. Examples: %C%# %C% -%C%MODEM 1200 +%C%#MODEM 300 %C%#MODEM 9600 %C% %C%# @@ -267,83 +222,21 @@ %C%# See User Guide for details. %C%# %C% -%C%# -%C%# Uncomment line below to enable the DTMF decoder for this channel. -%C%# -%C% -%C%#DTMF -%C% %C%# 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%# https://github.com/wb2osz/direwolf-doc/blob/main/Radio-Interface-Guide.pdf %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. -%L%# Example: -%L% -%L%#PTT CM108 -%L% -%W%# If using a C-Media CM108/CM119 or similar USB Audio Adapter, -%W%# you can use a GPIO pin for PTT control. This is very convenient -%W%# because a single USB connection is used for both audio and PTT. -%W%# Example: -%W% -%W%#PTT CM108 -%W%%C%# -%C%# The transmitter Push to Talk (PTT) control can be wired to a serial port -%C%# with a suitable interface circuit. DON'T connect it directly! -%C%# -%C%# For the PTT command, specify the device and either RTS or DTR. -%C%# RTS or DTR may be preceded by "-" to invert the signal. -%C%# Both can be used for interfaces that want them driven with opposite polarity. -%C%# -%L%# COM1 can be used instead of /dev/ttyS0, COM2 for /dev/ttyS1, and so on. -%L%# -%C% -%C%#PTT COM1 RTS -%C%#PTT COM1 RTS -DTR -%L%#PTT /dev/ttyUSB0 RTS -%L%#PTT /dev/ttyUSB0 RTS -DTR -%C% -%L%# -%L%# On Linux, you can also use general purpose I/O pins if -%L%# your system is configured for user access to them. -%L%# This would apply mostly to microprocessor boards, not a regular PC. -%L%# See separate Raspberry Pi document for more details. -%L%# The number may be preceded by "-" to invert the signal. -%L%# -%L% -%L%#PTT GPIO 25 -%L% -%C%# The Data Carrier Detect (DCD) signal can be sent to 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 -%L%#DCD GPIO 24 -%C% -%C% -%C%############################################################# -%C%# # -%C%# CHANNEL 1 PROPERTIES # -%C%# # -%C%############################################################# -%C% -%C%#CHANNEL 1 -%C% -%C%# -%C%# Specify MYCALL, MODEM, PTT, etc. configuration items for -%C%# CHANNEL 1. Repeat for any other channels. +%C%# If using a C-Media CM108/CM119 or similar USB Audio Adapter, +%C%# you can use a GPIO pin for PTT control. This is very convenient +%C%# because a single USB connection is used for both audio and PTT. +%C%# Example: %C% +%C%#PTT CM108 %C% -%C%############################################################# -%C%# # -%C%# TEXT TO SPEECH COMMAND FILE # -%C%# # -%C%############################################################# %C% -%W%#SPEECH dwespeak.bat -%L%#SPEECH dwespeak.sh +%C%# There are other possibilities such as serial port RTS, Raspberry Pi GPIO pins, +%C%# and hamlib for CAT control. For more details see: +%C%# https://github.com/wb2osz/direwolf-doc/blob/main/Radio-Interface-Guide.pdf %C% %C% %C%############################################################# @@ -361,38 +254,6 @@ %W%# - KISS TNC via serial port %L%# - KISS TNC via pseudo terminal (-p command line option) %C%# -%C% -%C%AGWPORT 8000 -%C%KISSPORT 8001 -%C% -%W%# -%W%# Some applications are designed to operate with only a physical -%W%# TNC attached to a serial port. For these, we provide a virtual serial -%W%# port that appears to be connected to a TNC. -%W%# -%W%# Take a look at the User Guide for instructions to set up -%W%# two virtual serial ports named COM3 and COM4 connected by -%W%# a null modem. -%W%# -%W%# Using the configuration described, Dire Wolf will connect to -%W%# COM3 and the client application will use COM4. -%W%# -%W%# Uncomment following line to use this feature. -%W% -%W%#NULLMODEM COM3 -%W% -%W% -%C%# -%C%# It is sometimes possible to recover frames with a bad FCS. -%C%# This is not a global setting. -%C%# It applies only the the most recent CHANNEL specified. -%C%# -%C%# 0 - Don't try to repair. (default) -%C%# 1 - Attempt to fix single bit error. -%C%# -%C% -%C%#FIX_BITS 0 -%C% %C%# %C%############################################################# %C%# # @@ -410,16 +271,10 @@ %C%# Each has a series of keywords and values for options. %C%# See User Guide for details. %C%# -%C%# Example: -%C%# -%C%# This results in a broadcast once every 10 minutes. -%C%# Every half hour, it can travel via one digipeater hop. -%C%# The others are kept local. +%C%# Example: PLEASE change the latitude and longitude. %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 -%C%#PBEACON delay=11 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" -%C%#PBEACON delay=21 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" +%C%#PBEACON overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" %C% %C%# %C%# Did you know that APRS comments and messages can contain UTF-8 characters, not only plain ASCII? @@ -428,29 +283,6 @@ %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 -%C% -%C% -%C%# -%C%# When the destination field is set to "SPEECH" the information part is -%C%# converted to speech rather than transmitted as a data frame. -%C%# -%C% -%C%#CBEACON dest="SPEECH" info="Club meeting tonight at 7 pm." -%C% -%C%# Similar for Morse code. If SSID is specified, it is multiplied -%C%# by 2 to get speed in words per minute (WPM). -%C% -%C%#CBEACON dest="MORSE-6" info="de MYCALL" -%C% -%C% -%C%# -%C%# Modify for your particular situation before removing -%C%# the # comment character from the beginning of appropriate lines above. -%C%# -%C% %C% %C%############################################################# %C%# # @@ -497,29 +329,12 @@ %C%# That's all you need for a receive only IGate which relays %C%# messages from the local radio channel to the global servers. %C% -%C%# Some might want to send an IGate client position directly to a server -%C%# without sending it over the air and relying on someone else to -%C%# forward it to an IGate server. This is done by using sendto=IG rather -%C%# than a radio channel number. Overlay R for receive only, T for two way. -%C%# 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 -%C% -%C% -%C%# To relay messages from the Internet to radio, you need to add +%C%# To relay APRS "messages" from the Internet to radio, you need to add %C%# one more option with the transmit channel number and a VIA path. %C% %C%#IGTXVIA 0 WIDE1-1,WIDE2-1 %C% -%C% -%C%# Finally, we don't want to flood the radio channel. -%C%# The IGate function will limit the number of packets transmitted -%C%# during 1 minute and 5 minute intervals. If a limit would -%C%# be exceeded, the packet is dropped and message is displayed in red. -%C%# This might be low for APRS Thursday when there is abnormally high activity. -%C% -%C%IGTXLIMIT 6 10 +%C%# For more information see Successful-IGate-Operation.pdf. %C% %C% %C%############################################################# diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index 7972cc23..11a82a43 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -16,7 +16,7 @@ # # The destination field is often used to identify the manufacturer/model. # These are not hardcoded into Dire Wolf. Instead they are read from -# a file called tocalls.txt at application start up time. +# a file called tocalls.yaml at application start up time. # # The original permanent symbols are built in but the "new" symbols, # using overlays, are often updated. These are also read from files. @@ -25,17 +25,17 @@ include(ExternalProject) -set(TOCALLS_TXT "tocalls.txt") +set(TOCALLS_YAML "tocalls.yaml") set(SYMBOLS-NEW_TXT "symbols-new.txt") set(SYMBOLSX_TXT "symbolsX.txt") set(CUSTOM_BINARY_DATA_DIR "${CMAKE_BINARY_DIR}/data") # we can also move to a separate cmake file and use file(download) # see conf/install_conf.cmake as example -file(COPY "${CUSTOM_DATA_DIR}/${TOCALLS_TXT}" DESTINATION "${CUSTOM_BINARY_DATA_DIR}") +file(COPY "${CUSTOM_DATA_DIR}/${TOCALLS_YAML}" DESTINATION "${CUSTOM_BINARY_DATA_DIR}") file(COPY "${CUSTOM_DATA_DIR}/${SYMBOLS-NEW_TXT}" DESTINATION "${CUSTOM_BINARY_DATA_DIR}") file(COPY "${CUSTOM_DATA_DIR}/${SYMBOLSX_TXT}" DESTINATION "${CUSTOM_BINARY_DATA_DIR}") -install(FILES "${CUSTOM_BINARY_DATA_DIR}/${TOCALLS_TXT}" DESTINATION ${INSTALL_DATA_DIR}) +install(FILES "${CUSTOM_BINARY_DATA_DIR}/${TOCALLS_YAML}" DESTINATION ${INSTALL_DATA_DIR}) install(FILES "${CUSTOM_BINARY_DATA_DIR}/${SYMBOLS-NEW_TXT}" DESTINATION ${INSTALL_DATA_DIR}) install(FILES "${CUSTOM_BINARY_DATA_DIR}/${SYMBOLSX_TXT}" DESTINATION ${INSTALL_DATA_DIR}) diff --git a/data/README.txt b/data/README.txt new file mode 100644 index 00000000..9d4da43c --- /dev/null +++ b/data/README.txt @@ -0,0 +1,18 @@ + +tocalls.yaml contains the encoding for the device/system/software +identifier which created the packet. +Knowing what generated the packet is very useful for troubleshooting. +TNCs, digipeaters, and IGates must not change this. + +For MIC-E format, well... it's complicated. +See Understanding-APRS-Packets.pdf. Too long to repeat here. + +For all other packet types, the AX.25 destination, or "tocall" field +contains a code for what generated the packet. +This is of the form AP????. For example, APDW18 for direwolf 1.8. + +The database of identifiers is currently maintained by Hessu, OH7LZB. + +You can update your local copy by running: + +wget https://raw.githubusercontent.com/aprsorg/aprs-deviceid/main/tocalls.yaml diff --git a/data/tocalls.txt b/data/tocalls.txt deleted file mode 100644 index 169c9868..00000000 --- a/data/tocalls.txt +++ /dev/null @@ -1,326 +0,0 @@ -<title> -APRS TO-CALL VERSION NUMBERS 14 Dec 2021 ---------------------------------------------------------------------- - WB4APR -</title> -<version_notes> -07 Jun 23 Added APK005 for Kenwood TH-D75 -14 Dec 21 Added APATAR ATA-R APRS Digipeater by TA7W/OH2UDS and TA6AEU -26 Sep 21 Added APRRDZ EPS32 https://github.com/dl9rdz/rdz_ttgo_sonde -18 Sep 21 Added APCSS for AMSAT Cubesat Simulator https://cubesatsim.org -16 Sep 21 Added APY05D for Yaesu FT5D series -04 Sep 21 APLOxx LoRa KISS TNC/Tracker https://github.com/SQ9MDD/TTGO-T-Beam-LoRa-APRS -24 Aug 21 Added APLSxx SARIMESH http://www.sarimesh.net -22 Aug 21 Added APE2Ax for VA3NNW's Email-2-APRS ap -30 Jun 21 Added APCNxx for carNET by DG5OAW -14 Jun 21 Added APN2xx for NOSaprs JNOS 2.0 - VE4KLM -24 Apr 21 Added APMPAD for DF1JSL's WXBot clone and extension -20 Apr 21 Added APLCxx for APRScube by DL3DCW -19 Apr 21 Added APVMxx for DRCC-DVM Voice (Digital Radio China Club) -13 Apr 21 Added APIxxx for all Dstar ICOMS (APRS via DPRS) -23 MAr 20 Added APW9xx For 9A9Y Weather Tracker -16 Feb 21 Added API970 for I com 9700 - -2020 Added APHBLx,APIZCI,APLGxx,APLTxx,APNVxx,APY300,APESPG,APESPW - APGDTx,APOSWx,APOSBx,APBT62,APCLUB,APMQxx -2019 Added APTPNx,APJ8xx,APBSDx,APNKMX,APAT51,APMGxx,APTCMA, - APATxx,APQTHx,APLIGx -2018 added APRARX,APELKx,APGBLN,APBKxx,APERSx,APTCHE -2017 Added APHWxx,APDVxx,APPICO,APBMxx,APP6xx,APTAxx,APOCSG,APCSMS, - APPMxx,APOFF,APDTMF,APRSON,APDIGI,APSAT,APTBxx,APIExx, - APSFxx -2016 added APYSxx,APINxx,APNICx,APTKPT,APK004,APFPRS,APCDS0,APDNOx -2015 Added APSTPO,APAND1,APDRxx,APZ247,APHTxx,APMTxx,APZMAJ - APB2MF,APR2MF,APAVT5 - -</version_notes> -<description> - -In APRS, the AX.25 Destination address is not used for packet -routing as is normally done in AX.25. So APRS uses it for two -things. The initial APxxxx is used as a group identifier to make -APRS packets instanantly recognizable on shared channels. Most -applicaitons ignore all non APRS packets. The remaining 4 xxxx -bytes of the field are available to indicate the software version -number or application. The following applications have requested -a TOCALL number series: - -Authors with similar alphabetic requirements are encouraged to share -their address space with other software. Work out agreements amongst -yourselves and keep me informed. - -</description> -<tocalls> - - APn 3rd digit is a number - AP1WWX TAPR T-238+ WX station - AP1MAJ Martyn M1MAJ DeLorme inReach Tracker - AP4Rxy APRS4R software interface - APnnnD Painter Engineering uSmartDigi D-Gate DSTAR Gateway - APnnnU Painter Engineering uSmartDigi Digipeater - APA APAFxx AFilter. - APAGxx AGATE - APAGWx SV2AGW's AGWtracker - APALxx Alinco DR-620/635 internal TNC digis. "Hachi" ,JF1AJE - APAXxx AFilterX. - APAHxx AHub - APAND1 APRSdroid (pre-release) http://aprsdroid.org/ - APAMxx Altus Metrum GPS trackers - APATAR ATA-R APRS Digipeater by TA7W/OH2UDS and TA6AEU - APAT8x for Anytone. 81 for 878 HT - APAT51 for Anytone AT-D578UV APRS mobile radio - APAVT5 SainSonic AP510 which is a 1watt tracker - APAWxx AGWPE - APB APBxxx Beacons or Rabbit TCPIP micros? - APB2MF DL2MF - MF2APRS Radiosonde for balloons - APBLxx BigRedBee BeeLine - APBLO MOdel Rocketry K7RKT - APBKxx PY5BK Bravo Tracker in Brazil - APBPQx John G8BPQ Digipeater/IGate - APBMxx BrandMeister DMR Server for R3ABM - APBSDx HamBSD https://hambsd.org/ - APBT62 BTech DMR 6x2 - APC APCxxx Cellular applications - APCBBx VE7UDP Blackberry Applications - APCDS0 Leon Lessing ZS6LMG's cell tracker - APCLEY EYTraker GPRS/GSM tracker by ZS6EY - APCLEZ Telit EZ10 GSM application ZS6CEY - APCLUB Brazil APRS network - APCLWX EYWeather GPRS/GSM WX station by ZS6EY - APCNxx for carNET by DG5OAW - APCSMS for Cosmos (used for sending commands @USNA) - APCSS for AMSAT cubesats https://cubesatsim.org - APCWP8 John GM7HHB, WinphoneAPRS - APCYxx Cybiko applications - APD APD4xx UP4DAR platform - APDDxx DV-RPTR Modem and Control Center Software - APDFxx Automatic DF units - APDGxx D-Star Gateways by G4KLX ircDDB - APDHxx WinDV (DUTCH*Star DV Node for Windows) - APDInn DIXPRS - Bela, HA5DI - APDIGI Used by PSAT2 to indicate the digi is ON - APDIGI digi ON for PSAT2 and QIKCOM-2 - APDKxx KI4LKF g2_ircddb Dstar gateway software - APDNOx APRSduino by DO3SWW - APDOxx ON8JL Standalone DStar Node - APDPRS D-Star originated posits - APDRxx APRSdroid Android App http://aprsdroid.org/ - APDSXX SP9UOB for dsDigi and ds-tracker - APDTxx APRStouch Tone (DTMF) - APDTMF digi off mode on QIKCOM2 and DTMF ON - APDUxx U2APRS by JA7UDE - APDVxx OE6PLD's SSTV with APRS status exchange - APDWxx DireWolf, WB2OSZ - APE APExxx Telemetry devices - APE2Ax VA3NNW's Email-2-APRS ap - APECAN Pecan Pico APRS Balloon Tracker - APELKx WB8ELK balloons - APERXQ Experimental tracker by PE1RXQ - APERSx Runner tracking by Jason,KG7YKZ - APESPG ESP SmartBeacon APRS-IS Client - APESPW ESP Weather Station APRS-IS Client - APF APFxxx Firenet - APFGxx Flood Gage (KP4DJT) - APFIxx for APRS.FI OH7LZB, Hessu - APFPRS for FreeDV by Jeroen PE1RXQ - APG APGxxx Gates, etc - APGOxx for AA3NJ PDA application - APGBLN for NW5W's GoBalloon - APGDTx for VK4FAST's Graphic Data Terminal - APH APHKxx for LA1BR tracker/digipeater - APHAXn SM2APRS by PY2UEP - APHBLx for DMR Gateway by Eric - KF7EEL - APHTxx HMTracker by IU0AAC - APHWxx for use in "HamWAN - API API282 for ICOM IC-2820 - API31 for ICOM ID-31 - API410 for ICOM ID-4100 - API51 for ICOM ID-51 - API510 for ICOM ID-5100 - API710 for ICOM IC-7100 - API80 for ICOM IC-80 - API880 for ICOM ID-880 - API910 for ICOM IC-9100 - API92 for ICOM IC-92 - API970 for ICOM 9700 - APICQx for ICQ - APICxx HA9MCQ's Pic IGate - APIExx W7KMV's PiAPRS system - APINxx PinPoint by AB0WV - APIZCI hymTR IZCI Tracker by TA7W/OH2UDS and TA6AEU - APJ APJ8xx Jordan / KN4CRD JS8Call application - APJAxx JavAPRS - APJExx JeAPRS - APJIxx jAPRSIgate - APJSxx javAPRSSrvr - APJYnn KA2DDO Yet another APRS system - APK APK0xx Kenwood TH-D7's - APK003 Kenwood TH-D72 - APK004 Kenwood TH-D74 - APK005 Kenwood TH-D75 - APK1xx Kenwood D700's - APK102 Kenwood D710 - APKRAM KRAMstuff.com - Mark. G7LEU - APL APLCxx APRScube by DL3DCW - APLGxx LoRa Gateway/Digipeater OE5BPA - APLIGx LightAPRS - TA2MUN and TA9OHC - APLOxx LoRa KISS TNC/Tracker - APLQRU Charlie - QRU Server - APLMxx WA0TQG transceiver controller - APLSxx SARIMESH ( http://www.sarimesh.net ) - APLTxx LoRa Tracker - OE5BPA - APM APMxxx MacAPRS, - APMGxx PiCrumbs and MiniGate - Alex, AB0TJ - APMIxx SQ3PLX http://microsat.com.pl/ - APMPAD DF1JSL's WXBot clone and extension - APMQxx Ham Radio of Things WB2OSZ - APMTxx LZ1PPL for tracker - APN APNxxx Network nodes, digis, etc - APN2xx NOSaprs for JNOS 2.0 - VE4KLM - APN3xx Kantronics KPC-3 rom versions - APN9xx Kantronics KPC-9612 Roms - APNAxx WB6ZSU's APRServe - APNDxx DIGI_NED - APNICx SQ5EKU http://sq5eku.blogspot.com/ - APNK01 Kenwood D700 (APK101) type - APNK80 KAM version 8.0 - APNKMP KAM+ - APNKMX KAM-XL - APNMxx MJF TNC roms - APNPxx Paccom TNC roms - APNTxx SV2AGW's TNT tnc as a digi - APNUxx UIdigi - APNVxx SQ8L's VP digi and Nodes - APNXxx TNC-X (K6DBG) - APNWxx SQ3FYK.com WX/Digi and SQ3PLX http://microsat.com.pl/ - APO APRSpoint - APOFF Used by PSAT and PSAT2 to indicate the digi is OFF - APOLUx for OSCAR satellites for AMSAT-LU by LU9DO - APOAxx OpenAPRS - Greg Carter - APOCSG For N0AGI's APRS to POCSAG project - APOD1w Open Track with 1 wire WX - APOSBx openSPOT3 by HA2NON at sharkrf.com - APOSWx openSPOT2 - APOTxx Open Track - APOU2k Open Track for Ultimeter - APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO - APP APP6xx for APRSlib - APPICx DB1NTO' PicoAPRS - APPMxx DL1MX's RTL-SDR pytohon Igate - APPTxx KetaiTracker by JF6LZE, Takeki (msg capable) - APQ APQxxx Earthquake data - APQTHx W8WJB's QTH.app - APR APR8xx APRSdos versions 800+ - APR2MF DL2MF - MF2APRS Radiosonde WX reporting - APRARX VK5QI's radiosonde tracking - APRDxx APRSdata, APRSdr - APRGxx aprsg igate software, OH2GVE - APRHH2 HamHud 2 - APRKxx APRStk - APRNOW W5GGW ipad application - APRRTx RPC electronics - APRS Generic, (obsolete. Digis should use APNxxx instead) - APRSON Used by PSAT to indicate the DIGI is ON - APRXxx >40 APRSmax - APRXxx <39 for OH2MQK's igate - APRTLM used in MIM's and Mic-lites, etc - APRtfc APRStraffic - APRSTx APRStt (Touch tone) - APS APSxxx APRS+SA, etc - APSARx ZL4FOX's SARTRACK - APSAT digi ON for QIKCOM-1 - APSCxx aprsc APRS-IS core server (OH7LZB, OH2MQK) - APSFxx F5OPV embedded devices - was APZ40 - APSK63 APRS Messenger -over-PSK63 - APSK25 APRS Messenger GMSK-250 - APSMSx Paul Dufresne's SMSGTE - SMS Gateway - APSTMx for W7QO's Balloon trackers - APSTPO for N0AGI Satellite Tracking and Operations - APT APT2xx Tiny Track II - APT3xx Tiny Track III - APTAxx K4ATM's tiny track - APTBxx TinyAPRS by BG5HHP Was APTAxx till Sep 2017 - APTCHE PU3IKE in Brazil TcheTracker/Tcheduino - APTCMA CAPI tracker - PU1CMA Brazil - APTIGR TigerTrack - APTKPT TrackPoint N0LP - APTPNx TARPN Packet Node Tracker by KN4ORB http://tarpn.net/ - APTTxx Tiny Track - APTWxx Byons WXTrac - APTVxx for ATV/APRN and SSTV applications - APU APU1xx UIview 16 bit applications - APU2xx UIview 32 bit apps - APU3xx UIview terminal program - APUDRx NW Digital Radio's UDR (APRS/Dstar) - APV APVxxx Voice over Internet applications - APVMxx DRCC-DVM Digital Voice (Digital Radio China Club) - APVRxx for IRLP - APVLxx for I-LINK - APVExx for ECHO link - APW APWxxx WinAPRS, etc - APW9xx 9A9Y Weather Tracker - APWAxx APRSISCE Android version - APWSxx DF4IAN's WS2300 WX station - APWMxx APRSISCE KJ4ERJ - APWWxx APRSISCE win32 version - APX APXnnn Xastir - APXRnn Xrouter - APY APYxxx Yaesu Radios - APY008 Yaesu VX-8 series - APY01D Yaesu FT1D series - APY02D Yaesu FT2D series - APY03D Yaesu FT3D series - APY05D Yaesu FT5D series - APY100 Yaesu FTM-100D series - APY300 Yaesu FTM-300D series - APY350 Yaesu FTM-350 series - APY400 Yaesu FTM-400D series - APZ APZxxx Experimental - APZ200 old versions of JNOS - APZ247 for UPRS NR0Q - APZ0xx Xastir (old versions. See APX) - APZMAJ Martyn M1MAJ DeLorme inReach Tracker - APZMDM github/codec2_talkie - product code not registered - APZMDR for HaMDR trackers - hessu * hes.iki.fi] - APZPAD Smart Palm - APZTKP TrackPoint, Nick N0LP (Balloon tracking)(depricated) - APZWIT MAP27 radio (Mountain Rescue) EI7IG - APZWKR GM1WKR NetSked application -</tocalls> -<notes> - -</notes> -<altnets> - -REGISTERED TOCALL ALTNETS: --------------------------- - -ALTNETS are uses of the AX-25 tocall to distinguish specialized -traffic that may be flowing on the APRS-IS, but that are not intended -to be part of normal APRS distribution to all normal APRS software -operating in normal (default) modes. Proper APRS software that -honors this design are supposed to IGNORE all ALTNETS unless the -particular operator has selected an ALTNET to monitor for. - -An example is when testing; an author may want to transmit objects -all over his map for on-air testing, but does not want these to -clutter everyone's maps or databases. He could use the ALTNET of -"TEST" and client APRS software that respects the ALTNET concept -should ignore these packets. - -An ALTNET is defined to be ANY AX.25 TOCALL that is NOT one of the -normal APRS TOCALL's. The normal TOCALL's that APRS is supposed to -process are: ALL, BEACON, CQ, QST, GPSxxx and of course APxxxx. - -The following is a list of ALTNETS that may be of interest to other -users. This list is by no means complete, since ANY combination of -characters other than APxxxx are considered an ALTNET. But this list -can give consisntecy to ALTNETS that may be using the global APRS-IS -and need some special recognition. Here are some ideas: -</altnets> -<altnet_list> - - SATERN - Salvation Army Altnet - AFMARS - Airforce Mars - AMARS - Army Mars -</altnet_list> \ No newline at end of file diff --git a/data/tocalls.yaml b/data/tocalls.yaml new file mode 100644 index 00000000..0df04785 --- /dev/null +++ b/data/tocalls.yaml @@ -0,0 +1,1637 @@ +# +# This is a machine-readable index of APRS device and software +# identification strings. For easy manual editing and validation, the +# master file is in YAML format. A conversion tool and pre-converted +# versions in XML and JSON are also provided for environments where those +# are more convenient to parse. +# +# This list is maintained by Hessu, OH7LZB, for the aprs.fi service. +# It is licensed under the CC BY-SA 2.0 license, so you're free to use +# it in any of your applications. For free. Just mention the source +# somewhere in the small print. +# http://creativecommons.org/licenses/by-sa/2.0/ +# + +--- + +# +# English shown names and descriptions for device classes +# +classes: + - class: wx + shown: Weather station + description: Dedicated weather station + + - class: tracker + shown: Tracker + description: Tracker device + + - class: rig + shown: Rig + description: Mobile or desktop radio + + - class: ht + shown: HT + description: Hand-held radio + + - class: app + shown: Mobile app + description: Mobile phone or tablet app + + - class: software + shown: Software + description: Desktop software + + - class: digi + shown: Digipeater + description: Digipeater software + + - class: igate + shown: iGate + description: iGate software + + - class: dstar + shown: D-Star + description: D-Star radio + + - class: satellite + shown: Satellite + description: Satellite-based station + + - class: service + shown: Service + description: Software running as a web service + +# +# mic-e device identifier index for new-style 2-character device +# suffixes. The first prefix byte indicates messaging capability. +# +mice: + - suffix: "_ " + vendor: Yaesu + model: VX-8 + class: ht + + - suffix: "_\"" + vendor: Yaesu + model: FTM-350 + class: rig + + - suffix: "_#" + vendor: Yaesu + model: VX-8G + class: ht + + - suffix: "_$" + vendor: Yaesu + model: FT1D + class: ht + + - suffix: "_(" + vendor: Yaesu + model: FT2D + class: ht + + - suffix: "_0" + vendor: Yaesu + model: FT3D + class: ht + + - suffix: "_3" + vendor: Yaesu + model: FT5D + class: ht + + - suffix: "_1" + vendor: Yaesu + model: FTM-300D + class: rig + + - suffix: "_2" + vendor: Yaesu + model: FTM-200D + class: rig + + - suffix: "_4" + vendor: Yaesu + model: FTM-500D + class: rig + + - suffix: "_)" + vendor: Yaesu + model: FTM-100D + class: rig + + - suffix: "_%" + vendor: Yaesu + model: FTM-400DR + class: rig + + - suffix: "(5" + vendor: Anytone + model: D578UV + class: ht + + - suffix: "(8" + vendor: Anytone + model: D878UV + class: ht + + - suffix: "|3" + vendor: Byonics + model: TinyTrak3 + class: tracker + + - suffix: "|4" + vendor: Byonics + model: TinyTrak4 + class: tracker + + - suffix: "^v" + vendor: HinzTec + model: anyfrog + + - suffix: "*v" + vendor: KissOZ + model: Tracker + class: tracker + + - suffix: ":2" + vendor: SQ8L + model: VP-Tracker + class: tracker + +# +# mic-e legacy devices, with an unique comment suffix and prefix +# + +micelegacy: + - prefix: ">" + vendor: Kenwood + model: TH-D7A + class: ht + features: + - messaging + + - prefix: ">" + suffix: "=" + vendor: Kenwood + model: TH-D72 + class: ht + features: + - messaging + + - prefix: ">" + suffix: "^" + vendor: Kenwood + model: TH-D74 + class: ht + features: + - messaging + + - prefix: ">" + suffix: "&" + vendor: Kenwood + model: TH-D75 + class: ht + features: + - messaging + + - prefix: "]" + vendor: Kenwood + model: TM-D700 + class: rig + features: + - messaging + + - prefix: "]" + suffix: "=" + vendor: Kenwood + model: TM-D710 + class: rig + features: + - messaging + +# +# TOCALL index +# +tocalls: + - tocall: AP1WWX + vendor: TAPR + model: T-238+ + class: wx + + - tocall: AP4R?? + vendor: Open Source + model: APRS4R + class: software + + - tocall: APAEP1 + vendor: Paraguay Space Agency (AEP) + model: "EIRUAPRSDIGIS&FV1" + class: satellite + + - tocall: APAF?? + model: AFilter + + - tocall: APAG?? + model: AGate + + - tocall: APAGW + vendor: SV2AGW + model: AGWtracker + class: software + os: Windows + + - tocall: APAGW? + vendor: SV2AGW + model: AGWtracker + class: software + os: Windows + + - tocall: APAH?? + model: AHub + + - tocall: APAIOR + vendor: J. Angelo Racoma DU2XXR/N2RAC + model: APRSPH net bot based on Ioreth + class: service + os: linux + contact: info@aprsph.net + features: + - messaging + + - tocall: APAM?? + vendor: Altus Metrum + model: AltOS + class: tracker + + - tocall: APAND? + vendor: Open Source + model: APRSdroid + os: Android + class: app + + - tocall: APAR?? + vendor: Ãyvind, LA7ECA + model: Arctic Tracker + class: tracker + os: embedded + + - tocall: APAT51 + vendor: Anytone + model: AT-D578 + class: rig + + - tocall: APAT81 + vendor: Anytone + model: AT-D878 + class: ht + + - tocall: APAT?? + vendor: Anytone + + - tocall: APATAR + vendor: TA7W/OH2UDS Baris Dinc and TA6AEU + model: ATA-R APRS Digipeater + class: digi + + - tocall: APAVT5 + vendor: SainSonic + model: AP510 + class: tracker + + - tocall: APAW?? + vendor: SV2AGW + model: AGWPE + class: software + os: Windows + + - tocall: APAX?? + model: AFilterX + + - tocall: APB2MF + vendor: Mike, DL2MF + model: MF2APRS Radiosonde tracking tool + class: software + os: Windows + + - tocall: APBK?? + vendor: PY5BK + model: Bravo Tracker + class: tracker + + - tocall: APBL?? + vendor: BigRedBee + model: BeeLine GPS + class: tracker + + - tocall: APBM?? + vendor: R3ABM + model: BrandMeister DMR + + - tocall: APBPQ? + vendor: John Wiseman, G8BPQ + model: BPQ32 + class: software + os: Windows + + - tocall: APBSD? + vendor: hambsd.org + model: HamBSD + + - tocall: APBT62 + vendor: BTech + model: DMR 6x2 + + - tocall: APC??? + vendor: Rob Wittner, KZ5RW + model: APRS/CE + class: app + + - tocall: APCDS0 + vendor: ZS6LMG + model: cell tracker + class: tracker + + - tocall: APCLEY + vendor: ZS6EY + model: EYTraker + class: tracker + + - tocall: APCLEZ + vendor: ZS6EY + model: Telit EZ10 GSM application + class: tracker + + - tocall: APCLUB + model: Brazil APRS network + + - tocall: APCLWX + vendor: ZS6EY + model: EYWeather + class: wx + + - tocall: APCN?? + vendor: DG5OAW + model: carNET + + - tocall: APCSMS + vendor: USNA + model: Cosmos + + - tocall: APCSS + vendor: AMSAT + model: CubeSatSim CubeSat Simulator + + - tocall: APCTLK + vendor: Open Source + model: Codec2Talkie + class: app + + - tocall: APCWP8 + vendor: GM7HHB + model: WinphoneAPRS + class: app + + - tocall: APD5T? + vendor: Geoffrey, F4FXL + model: Open Source DStarGateway + class: dstar + contact: f4fxl@dstargateway.digital + + - tocall: APDF?? + model: Automatic DF units + + - tocall: APDG?? + vendor: Jonathan, G4KLX + model: ircDDB Gateway + class: dstar + + - tocall: APDI?? + vendor: Bela, HA5DI + model: DIXPRS + class: software + + - tocall: APDNO? + vendor: DO3SWW + model: APRSduino + class: tracker + os: embedded + + - tocall: APDPRS + vendor: unknown + model: D-Star APDPRS + class: dstar + + - tocall: APDR?? + vendor: Open Source + model: APRSdroid + os: Android + class: app + + - tocall: APDS?? + vendor: SP9UOB + model: dsDIGI + os: embedded + + - tocall: APDST? + vendor: SP9UOB + model: dsTracker + os: embedded + + - tocall: APDT?? + vendor: unknown + model: APRStouch Tone (DTMF) + + - tocall: APDU?? + vendor: JA7UDE + model: U2APRS + class: app + os: Android + + - tocall: APDV?? + vendor: OE6PLD + model: SSTV with APRS + class: software + + - tocall: APDW?? + vendor: WB2OSZ + model: DireWolf + + - tocall: APDnnn + vendor: Open Source + model: aprsd + class: software + os: Linux/Unix + + - tocall: APE2A? + vendor: NoseyNick, VA3NNW + model: Email-2-APRS gateway + class: software + os: Linux/Unix + + - tocall: APE??? + model: Telemetry devices + + - tocall: APECAN + vendor: KT5TK/DL7AD + model: Pecan Pico APRS Balloon Tracker + class: tracker + + - tocall: APELK? + vendor: WB8ELK + model: Balloon tracker + class: tracker + + - tocall: APEML? + vendor: Leszek, SP9MLI + model: SP9MLI for WX, Telemetry + class: software + contact: sp9mli@gmail.com + + - tocall: APEP?? + vendor: Patrick EGLOFF, TK5EP + model: LoRa WX station + class: wx + os: embedded + contact: pegloff@gmail.com + + - tocall: APERS? + vendor: Jason, KG7YKZ + model: Runner tracking + class: tracker + + - tocall: APERXQ + vendor: PE1RXQ + model: PE1RXQ APRS Tracker + class: tracker + + - tocall: APESP? + vendor: LY3PH + model: APRS-ESP + os: embedded + + - tocall: APFG?? + vendor: KP4DJT + model: Flood Gage + class: software + + - tocall: APFI?? + vendor: aprs.fi + class: app + + - tocall: APFII? + model: iPhone/iPad app + vendor: aprs.fi + os: ios + class: app + + - tocall: APGBLN + vendor: NW5W + model: GoBalloon + class: tracker + + - tocall: APGO?? + vendor: AA3NJ + model: APRS-Go + class: app + + - tocall: APHAX? + vendor: PY2UEP + model: SM2APRS SondeMonitor + class: software + os: Windows + + - tocall: APHBL? + vendor: KF7EEL + model: HBLink D-APRS Gateway + class: software + + - tocall: APHH? + vendor: Steven D. Bragg, KA9MVA + model: HamHud + class: tracker + + - tocall: APHK?? + vendor: LA1BR + model: Digipeater/tracker + + - tocall: APHMEY + vendor: Tapio Heiskanen, OH2TH + model: APRS-IS Client for Athom Homey + contact: oh2th@iki.fi + + - tocall: APHPIA + vendor: HP3ICC + model: Arduino APRS + + - tocall: APHPIB + vendor: HP3ICC + model: Python APRS Beacon + + - tocall: APHPIW + vendor: HP3ICC + model: Python APRS WX + + - tocall: APHRM? + vendor: Giovanni, IW1CGW + model: Meteo + class: wx + contact: iw1cgw@libero.it + + - tocall: APHRT? + vendor: Giovanni, IW1CGW + model: Telemetry + contact: iw1cgw@libero.it + + - tocall: APHT?? + vendor: IU0AAC + model: HMTracker + class: tracker + + - tocall: APHW?? + vendor: HamWAN + + - tocall: API282 + vendor: Icom + model: IC-2820 + class: dstar + + - tocall: API31 + vendor: Icom + model: IC-31 + class: dstar + + - tocall: API410 + vendor: Icom + model: IC-4100 + class: dstar + + - tocall: API51 + vendor: Icom + model: IC-51 + class: dstar + + - tocall: API510 + vendor: Icom + model: IC-5100 + class: dstar + + - tocall: API710 + vendor: Icom + model: IC-7100 + class: dstar + + - tocall: API80 + vendor: Icom + model: IC-80 + class: dstar + + - tocall: API880 + vendor: Icom + model: IC-880 + class: dstar + + - tocall: API910 + vendor: Icom + model: IC-9100 + class: dstar + + - tocall: API92 + vendor: Icom + model: IC-92 + class: dstar + + - tocall: API970 + vendor: Icom + model: IC-9700 + class: dstar + + - tocall: API??? + vendor: Icom + model: unknown + class: dstar + + - tocall: APIC?? + vendor: HA9MCQ + model: PICiGATE + + - tocall: APIE?? + vendor: W7KMV + model: PiAPRS + + - tocall: APIN?? + vendor: AB0WV + model: PinPoint + + - tocall: APIZCI + vendor: TA7W/OH2UDS and TA6AEU + model: hymTR IZCI Tracker + class: tracker + os: embedded + + - tocall: APJ8?? + vendor: KN4CRD + model: JS8Call + class: software + + - tocall: APJA?? + vendor: K4HG & AE5PL + model: JavAPRS + + - tocall: APJE?? + vendor: Gregg Wonderly, W5GGW + model: JeAPRS + + - tocall: APJI?? + vendor: Peter Loveall, AE5PL + model: jAPRSIgate + class: software + + - tocall: APJID2 + vendor: Peter Loveall, AE5PL + model: D-Star APJID2 + class: dstar + + - tocall: APJS?? + vendor: Peter Loveall, AE5PL + model: javAPRSSrvr + + - tocall: APJY?? + vendor: KA2DDO + model: YAAC + class: software + + - tocall: APK003 + vendor: Kenwood + model: TH-D72 + class: ht + + - tocall: APK004 + vendor: Kenwood + model: TH-D74 + class: ht + + - tocall: APK005 + vendor: Kenwood + model: TH-D75 + class: ht + + - tocall: APK0?? + vendor: Kenwood + model: TH-D7 + class: ht + + - tocall: APK1?? + vendor: Kenwood + model: TM-D700 + class: rig + + - tocall: APKHTW + vendor: Kip, W3SN + model: Tempest Weather Bridge + class: wx + os: embedded + contact: w3sn@moxracing.33mail.com + + - tocall: APKRAM + vendor: kramstuff.com + model: Ham Tracker + class: app + os: ios + + - tocall: APLC?? + vendor: DL3DCW + model: APRScube + + - tocall: APLDG? + vendor: Eddie, 9W2LWK + model: LoRAIGate + class: igate + os: embedded + contact: 9w2lwk@gmail.com + + - tocall: APLDH? + vendor: Eddie, 9W2LWK + model: LoraTracker + class: tracker + os: embedded + contact: 9w2lwk@gmail.com + + - tocall: APLDI? + vendor: David, OK2DDS + model: LoRa IGate/Digipeater + class: digi + + - tocall: APLDM? + vendor: David, OK2DDS + model: LoRa Meteostation + class: wx + + - tocall: APLETK + vendor: DL5TKL + model: T-Echo + class: tracker + os: embedded + contact: cfr34k-git@tkolb.de + + - tocall: APLFG? + vendor: Gabor, HG3FUG + model: LoRa WX station + class: wx + os: embedded + contact: hg3fug@fazi.hu + + - tocall: APLFM? + vendor: DO1MA + model: FemtoAPRS + class: tracker + os: embedded + + - tocall: APLG?? + vendor: OE5BPA + model: LoRa Gateway/Digipeater + class: digi + + - tocall: APLHI? + vendor: Giovanni, IW1CGW + model: LoRa IGate/Digipeater/Telemetry + class: digi + contact: iw1cgw@libero.it + + - tocall: APLHM? + vendor: Giovanni, IW1CGW + model: LoRa Meteostation + class: wx + contact: iw1cgw@libero.it + + - tocall: APLIG? + vendor: TA2MUN/TA9OHC + model: LightAPRS Tracker + class: tracker + + - tocall: APLM?? + vendor: WA0TQG + class: software + + - tocall: APLO?? + vendor: SQ9MDD + model: LoRa KISS TNC/Tracker + class: tracker + + - tocall: APLP0? + vendor: SQ9P + model: fajne digi + class: digi + os: embedded + contact: sq9p.peter@gmail.com + + - tocall: APLP1? + vendor: SQ9P + model: LORA/FSK/AFSK fajny tracker + class: tracker + os: embedded + contact: sq9p.peter@gmail.com + + - tocall: APLRG? + vendor: Ricardo, CA2RXU + model: ESP32 LoRa iGate + class: igate + os: embedded + contact: richonguzman@gmail.com + + - tocall: APLRT? + vendor: Ricardo, CA2RXU + model: ESP32 LoRa Tracker + class: tracker + os: embedded + contact: richonguzman@gmail.com + + - tocall: APLS?? + vendor: SARIMESH + model: SARIMESH + class: software + + - tocall: APLT?? + vendor: OE5BPA + model: LoRa Tracker + class: tracker + + - tocall: APLU0? + vendor: SP9UP + model: ESP32/SX12xx LoRa iGate / Digi + class: digi + os: embedded + contact: wajdzik.m@gmail.com + + - tocall: APLU1? + vendor: SP9UP + model: ESP32/SX12xx LoRa Tracker + class: tracker + os: embedded + contact: wajdzik.m@gmail.com + + - tocall: APMAIL + vendor: Mike, NA7Q + model: APRS Mailbox + class: service + contact: mike.ph4@gmail.com + + - tocall: APMG?? + vendor: Alex, AB0TJ + model: PiCrumbs and MiniGate + class: software + + - tocall: APMI01 + vendor: Microsat + os: embedded + model: WX3in1 + + - tocall: APMI02 + vendor: Microsat + os: embedded + model: WXEth + + - tocall: APMI03 + vendor: Microsat + os: embedded + model: PLXDigi + + - tocall: APMI04 + vendor: Microsat + os: embedded + model: WX3in1 Mini + + - tocall: APMI05 + vendor: Microsat + os: embedded + model: PLXTracker + + - tocall: APMI06 + vendor: Microsat + os: embedded + model: WX3in1 Plus 2.0 + + - tocall: APMI?? + vendor: Microsat + os: embedded + + - tocall: APMON? + vendor: Amon Schumann, DL9AS + model: APRS Balloon Tracker + class: tracker + os: embedded + + - tocall: APMPAD + vendor: DF1JSL + model: Multi-Purpose APRS Daemon + class: service + contact: joerg.schultze.lutter@gmail.com + features: + - messaging + + - tocall: APMQ?? + vendor: WB2OSZ + model: Ham Radio of Things + + - tocall: APMT?? + vendor: LZ1PPL + model: Micro APRS Tracker + class: tracker + + - tocall: APN102 + vendor: Gregg Wonderly, W5GGW + model: APRSNow + class: app + os: ipad + + - tocall: APN2?? + vendor: VE4KLM + model: NOSaprs for JNOS 2.0 + + - tocall: APN3?? + vendor: Kantronics + model: KPC-3 + + - tocall: APN9?? + vendor: Kantronics + model: KPC-9612 + + - tocall: APNCM + vendor: Keith Kaiser, WA0TJT + model: Net Control Manager + class: software + os: browser + contact: wa0tjt@gmail.com + + - tocall: APND?? + vendor: PE1MEW + model: DIGI_NED + + - tocall: APNIC4 + vendor: SQ5EKU + model: BidaTrak + class: tracker + os: embedded + + - tocall: APNJS? + vendor: Julien Sansonnens, HB9HRD + model: Web messaging service + class: service + contact: julien.owls@gmail.com + features: + - messaging + + - tocall: APNK01 + vendor: Kenwood + model: TM-D700 + class: rig + features: + - messaging + + - tocall: APNK80 + vendor: Kantronics + model: KAM + + - tocall: APNKMP + vendor: Kantronics + model: KAM+ + + - tocall: APNKMX + vendor: Kantronics + model: KAM-XL + + - tocall: APNM?? + vendor: MFJ + model: TNC + + - tocall: APNP?? + vendor: PacComm + model: TNC + + - tocall: APNT?? + vendor: SV2AGW + model: TNT TNC as a digipeater + class: digi + + - tocall: APNU?? + vendor: IW3FQG + model: UIdigi + class: digi + + - tocall: APNV0? + vendor: SQ8L + model: VP-Digi + os: embedded + class: digi + + - tocall: APNV1? + vendor: SQ8L + model: VP-Node + os: embedded + + - tocall: APNV2? + vendor: SQ8L + model: VP-Tracker + class: tracker + + - tocall: APNV?? + vendor: SQ8L + + - tocall: APNW?? + vendor: SQ3FYK + model: WX3in1 + os: embedded + + - tocall: APNX?? + vendor: K6DBG + model: TNC-X + + - tocall: APOA?? + vendor: OpenAPRS + model: app + class: app + os: ios + + - tocall: APOCSG + vendor: N0AGI + model: POCSAG + + - tocall: APODOT + vendor: Mike, NA7Q + model: Oregon Department of Transportion Traffic Alerts + class: service + + - tocall: APOG7? + vendor: OpenGD77 + model: OpenGD77 + os: embedded + contact: Roger VK3KYY/G4KYF + + - tocall: APOLU? + vendor: AMSAT-LU + model: Oscar + class: satellite + + - tocall: APOPYT + vendor: Mike, NA7Q + model: NA7Q Messenger + class: software + contact: mike.ph4@gmail.com + + - tocall: APOSAT + vendor: Mike, NA7Q + model: Open Source Satellite Gateway + class: service + contact: mike.ph4@gmail.com + + - tocall: APOSMS + vendor: Mike, NA7Q + model: Open Source SMS Gateway + class: service + contact: mike.ph4@gmail.com + features: + - messaging + + - tocall: APOT?? + vendor: Argent Data Systems + model: OpenTracker + class: tracker + + - tocall: APOVU? + vendor: K J Somaiya Institute + model: BeliefSat + + - tocall: APOZ?? + vendor: OZ1EKD, OZ7HVO + model: KissOZ + class: tracker + + - tocall: APP6?? + model: APRSlib + + - tocall: APPCO? + vendor: RadCommSoft, LLC + model: PicoAPRSTracker + class: tracker + os: embedded + contact: ab4mw@radcommsoft.com + + - tocall: APPIC? + vendor: DB1NTO + model: PicoAPRS + class: tracker + + - tocall: APPM?? + vendor: DL1MX + model: rtl-sdr Python iGate + class: software + + - tocall: APPRIS + vendor: DF1JSL + model: Apprise APRS plugin + class: service + contact: joerg.schultze.lutter@gmail.com + features: + - messaging + + - tocall: APPS?? + vendor: Ãyvind, LA7ECA (for the Norwegian Radio Relay League) + model: Polaric Server + class: software + os: Linux + + - tocall: APPT?? + vendor: JF6LZE + model: KetaiTracker + class: tracker + + - tocall: APQTH? + vendor: Weston Bustraan, W8WJB + model: QTH.app + class: software + os: macOS + features: + - messaging + + - tocall: APR2MF + vendor: Mike, DL2MF + model: MF2wxAPRS Tinkerforge gateway + class: wx + os: Windows + + - tocall: APR8?? + vendor: Bob Bruninga, WB4APR + model: APRSdos + class: software + + - tocall: APRARX + vendor: Open Source + model: radiosonde_auto_rx + class: software + os: Linux/Unix + + - tocall: APRFG? + vendor: RF.Guru + contact: info@rf.guru + + - tocall: APRFGB + vendor: RF.Guru + model: APRS LoRa Pager + os: embedded + contact: info@rf.guru + + - tocall: APRFGD + vendor: RF.Guru + model: APRS Digipeater + class: digi + os: embedded + contact: info@rf.guru + + - tocall: APRFGH + vendor: RF.Guru + model: Hotspot + class: rig + os: embedded + contact: info@rf.guru + + - tocall: APRFGI + vendor: RF.Guru + model: LoRa APRS iGate + class: igate + os: embedded + contact: info@rf.guru + + - tocall: APRFGL + vendor: RF.Guru + model: Lora APRS Digipeater + class: digi + os: embedded + contact: info@rf.guru + + - tocall: APRFGM + vendor: RF.Guru + model: Mobile Radio + class: rig + os: embedded + contact: info@rf.guru + + - tocall: APRFGP + vendor: RF.Guru + model: Portable Radio + class: ht + os: embedded + contact: info@rf.guru + + - tocall: APRFGR + vendor: RF.Guru + model: Repeater + class: rig + os: embedded + contact: info@rf.guru + + - tocall: APRFGT + vendor: RF.Guru + model: LoRa APRS Tracker + class: tracker + os: embedded + contact: info@rf.guru + + - tocall: APRFGW + vendor: RF.Guru + model: LoRa APRS Weather Station + class: wx + os: embedded + contact: info@rf.guru + + - tocall: APRG?? + vendor: OH2GVE + model: aprsg + class: software + os: Linux/Unix + + - tocall: APRHH? + vendor: Steven D. Bragg, KA9MVA + model: HamHud + class: tracker + + - tocall: APRNOW + vendor: Gregg Wonderly, W5GGW + model: APRSNow + class: app + os: ipad + + - tocall: APRPR? + vendor: Robert DM4RW, Peter DL6MAA + model: Teensy RPR TNC + class: tracker + os: embedded + contact: dm4rw@skywaves.de + + - tocall: APRRDZ + model: rdzTTGOsonde + vendor: DL9RDZ + class: tracker + + - tocall: APRRF? + vendor: Jean-Francois Huet F1EVM + model: Tracker for RRF + class: tracker + os: embedded + contact: f1evm@f1evm.fr + features: + - messaging + + - tocall: APRRT? + vendor: RPC Electronics + model: RTrak + class: tracker + + - tocall: APRS + vendor: Unknown + model: Unknown + + - tocall: APRX?? + vendor: Kenneth W. Finnegan, W6KWF + model: Aprx + class: igate + os: Linux/Unix + + - tocall: APS??? + vendor: Brent Hildebrand, KH2Z + model: APRS+SA + class: software + + - tocall: APSAR + vendor: ZL4FOX + model: SARTrack + class: software + os: Windows + + - tocall: APSC?? + vendor: OH2MQK, OH7LZB + model: aprsc + class: software + + - tocall: APSF?? + vendor: F5OPV, SFCP_LABS + model: embedded APRS devices + os: embedded + + - tocall: APSFLG + vendor: F5OPV, SFCP_LABS + model: LoRa/APRS Gateway + class: digi + os: embedded + + - tocall: APSFRP + vendor: F5OPV, SFCP_LABS + model: VHF/UHF Repeater + os: embedded + + - tocall: APSFTL + vendor: F5OPV, SFCP_LABS + model: LoRa/APRS Telemetry Reporter + os: embedded + + - tocall: APSFWX + vendor: F5OPV, SFCP_LABS + model: embedded Weather Station + class: wx + os: embedded + + - tocall: APSK63 + vendor: Chris Moulding, G4HYG + model: APRS Messenger + class: software + os: Windows + + - tocall: APSMS? + vendor: Paul Dufresne + model: SMS gateway + class: software + + - tocall: APSRF? + vendor: SoftRF + model: Ham Edition + class: tracker + os: embedded + + - tocall: APSTM? + vendor: W7QO + model: Balloon tracker + class: tracker + + - tocall: APSTPO + vendor: N0AGI + model: Satellite Tracking and Operations + class: software + + - tocall: APT2?? + vendor: Byonics + model: TinyTrak2 + class: tracker + + - tocall: APT3?? + vendor: Byonics + model: TinyTrak3 + class: tracker + + - tocall: APT4?? + vendor: Byonics + model: TinyTrak4 + class: tracker + + - tocall: APTB?? + vendor: BG5HHP + model: TinyAPRS + + - tocall: APTCHE + vendor: PU3IKE + model: TcheTracker, Tcheduino + class: tracker + + - tocall: APTCMA + vendor: Cleber, PU1CMA + model: CAPI Tracker + class: tracker + + - tocall: APTEMP + vendor: KL7AF + model: APRS-Tempest Weather Gateway + class: wx + os: Linux/Unix + contact: kl7af@foghaven.net + + - tocall: APTHUR + model: APRSThursday weekly event mapbot daemon + contact: harihend1973@gmail.com + vendor: YD0BCX + class: service + os: linux/unix + features: + - messaging + + - tocall: APTKJ? + vendor: W9JAJ + model: ATTiny APRS Tracker + os: embedded + + - tocall: APTLVC + vendor: TA5LVC + model: TR80 APRS Tracker + class: tracker + + - tocall: APTNG? + vendor: Filip YU1TTN + model: Tango Tracker + class: tracker + + - tocall: APTPN? + vendor: KN4ORB + model: TARPN Packet Node Tracker + class: tracker + + - tocall: APTR?? + vendor: Motorola + model: MotoTRBO + + - tocall: APTT* + vendor: Byonics + model: TinyTrak + class: tracker + + - tocall: APTW?? + vendor: Byonics + model: WXTrak + class: wx + + - tocall: APU1?? + vendor: Roger Barker, G4IDE + model: UI-View16 + class: software + os: Windows + + - tocall: APU2* + vendor: Roger Barker, G4IDE + model: UI-View32 + class: software + os: Windows + + - tocall: APUDR? + vendor: NW Digital Radio + model: UDR + + - tocall: APVE?? + vendor: unknown + model: EchoLink + + - tocall: APVM?? + vendor: Digital Radio China Club + model: DRCC-DVM + class: igate + + - tocall: APVR?? + vendor: unknown + model: IRLP + + - tocall: APW9?? + vendor: Mile Strk, 9A9Y + model: WX Katarina + class: wx + os: embedded + features: + - messaging + + - tocall: APWA?? + vendor: KJ4ERJ + model: APRSISCE + class: software + os: Android + + - tocall: APWEE? + vendor: Tom Keffer and Matthew Wall + model: WeeWX Weather Software + class: software + os: Linux/Unix + + - tocall: APWM?? + vendor: KJ4ERJ + model: APRSISCE + class: software + os: Windows Mobile + features: + - messaging + - item-in-msg + + - tocall: APWW?? + vendor: KJ4ERJ + model: APRSIS32 + class: software + os: Windows + features: + - messaging + - item-in-msg + + - tocall: APWnnn + vendor: Sproul Brothers + model: WinAPRS + class: software + os: Windows + + - tocall: APX??? + vendor: Open Source + model: Xastir + class: software + os: Linux/Unix + + - tocall: APXR?? + vendor: G8PZT + model: Xrouter + + - tocall: APY01D + vendor: Yaesu + model: FT1D + class: ht + + - tocall: APY02D + vendor: Yaesu + model: FT2D + class: ht + + - tocall: APY05D + vendor: Yaesu + model: FT5D + class: ht + + - tocall: APY200 + vendor: Yaesu + model: FTM-200D + class: rig + + - tocall: APY300 + vendor: Yaesu + model: FTM-300D + class: rig + + - tocall: APY400 + vendor: Yaesu + model: FTM-400 + class: rig + + - tocall: APY500 + vendor: Yaesu + model: FTM-500D + class: rig + + - tocall: APYS?? + vendor: W2GMD + model: Python APRS + class: software + + - tocall: "APZ*" + vendor: Unknown + model: Experimental + + - tocall: APZ18 + vendor: IW3FQG + model: UIdigi + class: digi + + - tocall: APZ186 + vendor: IW3FQG + model: UIdigi + class: digi + + - tocall: APZ19 + vendor: IW3FQG + model: UIdigi + class: digi + + - tocall: APZ247 + model: UPRS + vendor: NR0Q + + - tocall: APZG?? + vendor: OH2GVE + model: aprsg + class: software + os: Linux/Unix + + - tocall: APZMAJ + vendor: M1MAJ + model: DeLorme inReach Tracker + + - tocall: APZMDR + vendor: Open Source + model: HaMDR + class: tracker + os: embedded + + - tocall: APZTKP + vendor: Nick Hanks, N0LP + model: TrackPoint + class: tracker + os: embedded + + - tocall: APZWKR + vendor: GM1WKR + model: NetSked + class: software + + - tocall: APnnnD + vendor: Painter Engineering + model: uSmartDigi D-Gate + class: dstar + + - tocall: APnnnU + vendor: Painter Engineering + model: uSmartDigi Digipeater + class: digi + + - tocall: PSKAPR + vendor: Open Source + model: PSKmail + class: software + diff --git a/external/hidapi/hid.c b/external/hidapi/hid.c index e483cd4f..f5c9858a 100644 --- a/external/hidapi/hid.c +++ b/external/hidapi/hid.c @@ -20,6 +20,8 @@ https://github.com/libusb/hidapi . ********************************************************/ +#include "../../src/direwolf.h" // for strlcpy + #include <windows.h> #ifndef _NTDEF_ @@ -465,7 +467,8 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor if (str) { len = strlen(str); cur_dev->path = (char*) calloc(len+1, sizeof(char)); - strncpy(cur_dev->path, str, len+1); + //strncpy(cur_dev->path, str, len+1); // produces warning + strlcpy(cur_dev->path, str, len+1); cur_dev->path[len] = '\0'; } else diff --git a/man/direwolf.1 b/man/direwolf.1 index 93f786dc..c6c8fa8d 100644 --- a/man/direwolf.1 +++ b/man/direwolf.1 @@ -132,6 +132,8 @@ f = Packet filtering. x = FX.25 increase verbose level. .P d = APRStt (DTMF to APRS object conversion). +.P +2 = IL2P. .RE .RE .PD diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a2c3963d..33c8c690 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,15 +10,22 @@ include_directories( ${PORTAUDIO_INCLUDE_DIRS} ${SNDIO_INCLUDE_DIRS} ${CUSTOM_GEOTRANZ_DIR} - ${CUSTOM_HIDAPI_DIR} + ${GPIOD_INCLUDE_DIRS} ) if(WIN32 OR CYGWIN) include_directories( + ${CUSTOM_HIDAPI_DIR} ${CUSTOM_REGEX_DIR} ) endif() +if(APPLE) + include_directories( + ${HIDAPI_INCLUDE_DIRS} + ) +endif() + # direwolf list(APPEND direwolf_SOURCES @@ -32,6 +39,7 @@ list(APPEND direwolf_SOURCES beacon.c config.c decode_aprs.c + deviceid.c dedupe.c demod_9600.c demod_afsk.c @@ -77,6 +85,7 @@ list(APPEND direwolf_SOURCES morse.c multi_modem.c waypoint.c + nettnc.c serial_port.c pfilter.c ptt.c @@ -119,7 +128,7 @@ if(LINUX) # icon # require plain gcc binary or link - #${CMAKE_SOURCE_DIR}/cmake/cpack/direwolf.rc + ${CMAKE_SOURCE_DIR}/cmake/cpack/direwolf.rc ) list(REMOVE_ITEM direwolf_SOURCES dwgpsd.c @@ -131,6 +140,7 @@ if(LINUX) else() # macOS freebsd list(APPEND direwolf_SOURCES audio_portaudio.c + cm108.c ) if(USE_MACOS_DNSSD) list(APPEND direwolf_SOURCES @@ -155,6 +165,7 @@ target_link_libraries(direwolf ${ALSA_LIBRARIES} ${UDEV_LIBRARIES} ${PORTAUDIO_LIBRARIES} + ${GPIOD_LIBRARIES} ${SNDIO_LIBRARIES} ${AVAHI_LIBRARIES} ) @@ -169,6 +180,7 @@ endif() # decode_aprs list(APPEND decode_aprs_SOURCES decode_aprs.c + deviceid.c ais.c kiss_frame.c ax25_pad.c @@ -353,6 +365,7 @@ list(APPEND atest_SOURCES ax25_pad.c ax25_pad2.c decode_aprs.c + deviceid.c dwgpsnmea.c dwgps.c dwgpsd.c @@ -469,10 +482,11 @@ endif() # List USB audio adapters than can use GPIO for PTT. # Originally for Linux only (using udev). -# Version 1.7 adds it for Windows. Needs hidapi library. +# Version 1.7 adds it for Windows. Uses local hidapi code. +# Post-1.7 adds it for Mac. Uses hidapi library. # cm108 -if(UDEV_FOUND OR WIN32 OR CYGWIN) +if(UDEV_FOUND OR WIN32 OR CYGWIN OR HIDAPI_FOUND) list(APPEND cm108_SOURCES cm108.c textcolor.c @@ -496,6 +510,12 @@ if(UDEV_FOUND OR WIN32 OR CYGWIN) ) endif() + if (APPLE) + target_link_libraries(cm108 + ${HIDAPI_LIBRARIES} + ) + endif() + if (WIN32 OR CYGWIN) target_link_libraries(cm108 ${HIDAPI_LIBRARIES} @@ -568,6 +588,6 @@ install(TARGETS ttcalc DESTINATION ${INSTALL_BIN_DIR}) install(TARGETS kissutil DESTINATION ${INSTALL_BIN_DIR}) install(TARGETS tnctest DESTINATION ${INSTALL_BIN_DIR}) install(TARGETS appserver DESTINATION ${INSTALL_BIN_DIR}) -if(UDEV_FOUND OR WIN32 OR CYGWIN) +if(UDEV_FOUND OR WIN32 OR CYGWIN OR HIDAPI_FOUND) install(TARGETS cm108 DESTINATION ${INSTALL_BIN_DIR}) endif() diff --git a/src/agwlib.c b/src/agwlib.c index 2c03adaa..cee4f992 100644 --- a/src/agwlib.c +++ b/src/agwlib.c @@ -357,7 +357,7 @@ static void * tnc_listen_thread (void *arg) /* * Take some precautions to guard against bad data which could cause problems later. */ - if (cmd.hdr.portx < 0 || cmd.hdr.portx >= MAX_CHANS) { + if (cmd.hdr.portx < 0 || cmd.hdr.portx >= MAX_TOTAL_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid channel number, %d, in command '%c', from network TNC.\n", cmd.hdr.portx, cmd.hdr.datakind); diff --git a/src/appserver.c b/src/appserver.c index b0ef7d87..bc2e2818 100644 --- a/src/appserver.c +++ b/src/appserver.c @@ -597,7 +597,7 @@ void agw_cb_G_port_information (int num_chan_avail, char *chan_descriptions[]) if (strncasecmp(p, "Port", 4) == 0 && isdigit(p[4])) { int chan = atoi(p+4) - 1; // "Port1" is our channel 0. - if (chan >= 0 && chan < MAX_CHANS) { + if (chan >= 0 && chan < MAX_TOTAL_CHANS) { char *desc = p + 4; while (*desc != '\0' && (*desc == ' ' || isdigit(*desc))) { diff --git a/src/aprs_tt.c b/src/aprs_tt.c index 7b125759..a2d35ec6 100644 --- a/src/aprs_tt.c +++ b/src/aprs_tt.c @@ -95,8 +95,8 @@ #define MAX_MSG_LEN 100 -static char msg_str[MAX_CHANS][MAX_MSG_LEN+1]; -static int msg_len[MAX_CHANS]; +static char msg_str[MAX_RADIO_CHANS][MAX_MSG_LEN+1]; +static int msg_len[MAX_RADIO_CHANS]; static int parse_fields (char *msg); static int parse_callsign (char *e); @@ -185,7 +185,7 @@ void aprs_tt_init (struct tt_config_s *p, int debug) // TODO: Keep ptr instead of making a copy. memcpy (&tt_config, p, sizeof(struct tt_config_s)); #endif - for (c=0; c<MAX_CHANS; c++) { + for (c=0; c<MAX_RADIO_CHANS; c++) { msg_len[c] = 0; msg_str[c][0] = '\0'; } @@ -226,7 +226,7 @@ void aprs_tt_button (int chan, char button) { static int poll_period = 0; - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); //if (button != '.') { diff --git a/src/atest.c b/src/atest.c index c5f4ec50..a24ed727 100644 --- a/src/atest.c +++ b/src/atest.c @@ -231,7 +231,7 @@ int main (int argc, char *argv[]) my_audio_config.adev[0].bits_per_sample = DEFAULT_BITS_PER_SAMPLE; - for (channel=0; channel<MAX_CHANS; channel++) { + for (channel=0; channel<MAX_RADIO_CHANS; channel++) { my_audio_config.achan[channel].modem_type = MODEM_AFSK; @@ -628,9 +628,10 @@ int main (int argc, char *argv[]) dw_printf ("%d samples per second. %d bits per sample. %d audio channels.\n", my_audio_config.adev[0].samples_per_sec, my_audio_config.adev[0].bits_per_sample, - my_audio_config.adev[0].num_channels); + (int)(my_audio_config.adev[0].num_channels)); + // nnum_channels is known to be 1 or 2. one_filetime = (double) wav_data.datasize / - ((my_audio_config.adev[0].bits_per_sample / 8) * my_audio_config.adev[0].num_channels * my_audio_config.adev[0].samples_per_sec); + ((my_audio_config.adev[0].bits_per_sample / 8) * (int)(my_audio_config.adev[0].num_channels) * my_audio_config.adev[0].samples_per_sec); total_filetime += one_filetime; dw_printf ("%d audio bytes in file. Duration = %.1f seconds.\n", @@ -654,7 +655,7 @@ int main (int argc, char *argv[]) int audio_sample; int c; - for (c=0; c<my_audio_config.adev[0].num_channels; c++) + for (c=0; c<(int)(my_audio_config.adev[0].num_channels); c++) { /* This reads either 1 or 2 bytes depending on */ @@ -921,7 +922,7 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev void ptt_set (int ot, int chan, int ptt_signal) { // Should only get here for DCD output control. - static double dcd_start_time[MAX_CHANS]; + static double dcd_start_time[MAX_RADIO_CHANS]; if (d_o_opt) { double t = (double)sample_number / my_audio_config.adev[0].samples_per_sec; diff --git a/src/audio.c b/src/audio.c index 82dec22a..53e2ae38 100644 --- a/src/audio.c +++ b/src/audio.c @@ -257,7 +257,7 @@ int audio_open (struct audio_s *pa) if (pa->adev[a].bits_per_sample == 0) pa->adev[a].bits_per_sample = DEFAULT_BITS_PER_SAMPLE; - for (chan=0; chan<MAX_CHANS; chan++) { + for (chan=0; chan<MAX_RADIO_CHANS; chan++) { if (pa->achan[chan].mark_freq == 0) pa->achan[chan].mark_freq = DEFAULT_MARK_FREQ; @@ -1099,7 +1099,12 @@ int audio_get (int a) dw_printf ("Audio input device %d error code %d: %s\n", a, n, snd_strerror(n)); if (n == (-EPIPE)) { - dw_printf ("This is most likely caused by the CPU being too slow to keep up with the audio stream.\n"); + dw_printf ("If receiving is fine and strange things happen when transmitting, it is probably RF energy\n"); + dw_printf ("getting into your audio or digital wiring. This can cause USB to lock up or PTT to get stuck on.\n"); + dw_printf ("Move the radio, and especially the antenna, farther away from the computer.\n"); + dw_printf ("Use shieled cable and put ferrite beads on the cables to reduce RF going where it is not wanted.\n"); + dw_printf ("\n"); + dw_printf ("A less likely cause is the CPU being too slow to keep up with the audio stream.\n"); dw_printf ("Use the \"top\" command, in another command window, to look at CPU usage.\n"); dw_printf ("This might be a temporary condition so we will attempt to recover a few times before giving up.\n"); dw_printf ("If using a very slow CPU, try reducing the CPU load by using -P- command\n"); diff --git a/src/audio.h b/src/audio.h index cb5ca94e..f69fc1d7 100644 --- a/src/audio.h +++ b/src/audio.h @@ -16,7 +16,7 @@ #include <hamlib/rig.h> #endif -#include "direwolf.h" /* for MAX_CHANS used throughout the application. */ +#include "direwolf.h" /* for MAX_RADIO_CHANS and MAX_TOTAL_CHANS used throughout the application. */ #include "ax25_pad.h" /* for AX25_MAX_ADDR_LEN */ #include "version.h" @@ -28,7 +28,8 @@ enum ptt_method_e { PTT_METHOD_NONE, /* VOX or no transmit. */ PTT_METHOD_SERIAL, /* Serial port RTS or DTR. */ - PTT_METHOD_GPIO, /* General purpose I/O, Linux only. */ + PTT_METHOD_GPIO, /* General purpose I/O using sysfs, deprecated after 2020, Linux only. */ + PTT_METHOD_GPIOD, /* General purpose I/O, using libgpiod, Linux only. */ PTT_METHOD_LPT, /* Parallel printer port, Linux only. */ PTT_METHOD_HAMLIB, /* HAMLib, Linux only. */ PTT_METHOD_CM108 }; /* GPIO pin of CM108/CM119/etc. Linux only. */ @@ -58,7 +59,7 @@ typedef enum retry_e { enum medium_e { MEDIUM_NONE = 0, // Channel is not valid for use. MEDIUM_RADIO, // Internal modem for radio. MEDIUM_IGATE, // Access IGate as ordinary channel. - MEDIUM_NETTNC }; // Remote network TNC. (possible future) + MEDIUM_NETTNC }; // Remote network TNC. (new in 1.8) typedef enum sanity_e { SANITY_APRS, SANITY_AX25, SANITY_NONE } sanity_t; @@ -74,16 +75,23 @@ struct audio_s { /* Properties of the sound device. */ - int defined; /* Was device defined? */ - /* First one defaults to yes. */ + int defined; /* Was device defined? 0=no. >0 for yes. */ + /* First channel defaults to 2 for yes with default config. */ + /* 1 means it was defined by user. */ + + int copy_from; /* >=0 means copy contents from another audio device. */ + /* In this case we don't have device names, below. */ + /* Num channels, samples/sec, and bit/sample are copied from */ + /* original device and can't be changed. */ + /* -1 for normal case. */ char adevice_in[80]; /* Name of the audio input device (or file?). */ - /* TODO: Can be "-" to read from stdin. */ + /* Can be udp:nnn for UDP or "-" to read from stdin. */ char adevice_out[80]; /* Name of the audio output device (or file?). */ int num_channels; /* Should be 1 for mono or 2 for stereo. */ - int samples_per_sec; /* Audio sampling rate. Typically 11025, 22050, or 44100. */ + int samples_per_sec; /* Audio sampling rate. Typically 11025, 22050, 44100, or 48000. */ int bits_per_sample; /* 8 (unsigned char) or 16 (signed short). */ } adev[MAX_ADEVS]; @@ -107,12 +115,6 @@ struct audio_s { float recv_ber; /* Receive Bit Error Rate (BER). */ /* Probability of inverting a bit coming out of the modem. */ - //int fx25_xmit_enable; /* Enable transmission of FX.25. */ - /* See fx25_init.c for explanation of values. */ - /* Initially this applies to all channels. */ - /* This should probably be per channel. One step at a time. */ - /* v1.7 - replaced by layer2_xmit==LAYER2_FX25 */ - int fx25_auto_enable; /* Turn on FX.25 for current connected mode session */ /* under poor conditions. */ /* Set to 0 to disable feature. */ @@ -131,10 +133,19 @@ struct audio_s { /* originally a "channel" was always connected to an internal modem. */ /* In version 1.6, this is generalized so that a channel (as seen by client application) */ /* can be connected to something else. Initially, this will allow application */ - /* access to the IGate. Later we might have network TNCs or other internal functions. */ + /* access to the IGate. In version 1.8 we add network KISS TNC. */ + + // Watch out for maximum number of channels. + // MAX_CHANS - Originally, this was 6 for internal modem adio channels. Has been phased out. + // After adding virtual channels (IGate, network TNC), this is split into two different numbers: + // MAX_RADIO_CHANNELS - For internal modems. + // MAX_TOTAL_CHANNELS - limited by KISS channels/ports. Needed for digipeating, filtering, etc. // Properties for all channels. + char mycall[MAX_TOTAL_CHANS][AX25_MAX_ADDR_LEN]; /* Call associated with this radio channel. */ + /* Could all be the same or different. */ + enum medium_e chan_medium[MAX_TOTAL_CHANS]; // MEDIUM_NONE for invalid. // MEDIUM_RADIO for internal modem. (only possibility earlier) @@ -146,6 +157,14 @@ struct audio_s { /* Redundant but it makes things quicker and simpler */ /* than always searching thru above. */ + // Applies only to network TNC type channels. + + char nettnc_addr[MAX_TOTAL_CHANS][80]; // Network TNC address: hostname or IP addr. + + int nettnc_port[MAX_TOTAL_CHANS]; // Network TNC TCP port. + + + /* Properties for each radio channel, common to receive and transmit. */ /* Can be different for each radio channel. */ @@ -163,8 +182,6 @@ struct audio_s { // int audio_source; // Default would be [0,1,2,3,4,5] // What else should be moved out of structure and enlarged when NETTNC is implemented. ??? - char mycall[AX25_MAX_ADDR_LEN]; /* Call associated with this radio channel. */ - /* Could all be the same or different. */ enum modem_t { MODEM_AFSK, MODEM_BASEBAND, MODEM_SCRAMBLE, MODEM_QPSK, MODEM_8PSK, MODEM_OFF, MODEM_16_QAM, MODEM_64_QAM, MODEM_AIS, MODEM_EAS } modem_type; @@ -175,7 +192,7 @@ struct audio_s { /* Might try MFJ-2400 / CCITT v.26 / Bell 201 someday. */ /* No modem. Might want this for DTMF only channel. */ - enum layer2_t { LAYER2_AX25 = 0, LAYER2_FX25, LAYER2_IL2P } layer2_xmit; + enum layer2_t { LAYER2_AX25 = 0, LAYER2_FX25, LAYER2_IL2P } layer2_xmit; // Must keep in sync with layer2_tx, below. // IL2P - New for version 1.7. // New layer 2 with FEC. Much less overhead than FX.25 but no longer backward compatible. @@ -304,6 +321,7 @@ struct audio_s { /* the case for CubieBoard where it was longer. */ /* This is filled in by ptt_init so we don't have to */ /* recalculate it each time we access it. */ + /* Also GPIO chip name for GPIOD method. Looks like 'gpiochip4' */ /* This could probably be collapsed into ptt_device instead of being separate. */ @@ -372,7 +390,7 @@ struct audio_s { int fulldup; /* Full Duplex. */ - } achan[MAX_CHANS]; + } achan[MAX_RADIO_CHANS]; #ifdef USE_HAMLIB int rigs; /* Total number of configured rigs */ @@ -381,6 +399,9 @@ struct audio_s { }; +#if DEMOD_C + const static char *layer2_tx[3] = {"AX.25", "FX.25", "IL2P"}; // Must keep in sync with enum layer2_t above. +#endif #if __WIN32__ #define DEFAULT_ADEVICE "" /* Windows: Empty string = default audio device. */ diff --git a/src/audio_portaudio.c b/src/audio_portaudio.c index cb6ccf10..92ba2cb3 100644 --- a/src/audio_portaudio.c +++ b/src/audio_portaudio.c @@ -578,7 +578,7 @@ int audio_open (struct audio_s *pa) if (pa->adev[a].bits_per_sample == 0) pa->adev[a].bits_per_sample = DEFAULT_BITS_PER_SAMPLE; - for (chan = 0; chan < MAX_CHANS; chan++) { + for (chan = 0; chan < MAX_RADIO_CHANS; chan++) { if (pa->achan[chan].mark_freq == 0) pa->achan[chan].mark_freq = DEFAULT_MARK_FREQ; diff --git a/src/audio_win.c b/src/audio_win.c index 85a1548b..21c09ce7 100644 --- a/src/audio_win.c +++ b/src/audio_win.c @@ -1,4 +1,4 @@ - +// FIXME: Add longer input timeout and more retries // // This file is part of Dire Wolf, an amateur radio packet TNC. // @@ -270,7 +270,7 @@ int audio_open (struct audio_s *pa) A->g_audio_in_type = AUDIO_IN_TYPE_SOUNDCARD; - for (chan=0; chan<MAX_CHANS; chan++) { + for (chan=0; chan<MAX_RADIO_CHANS; chan++) { if (pa -> achan[chan].mark_freq == 0) pa -> achan[chan].mark_freq = DEFAULT_MARK_FREQ; @@ -660,7 +660,13 @@ int audio_open (struct audio_s *pa) */ case AUDIO_IN_TYPE_STDIN: - setmode (STDIN_FILENO, _O_BINARY); + // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/setmode?view=msvc-170 + + int err = _setmode (_fileno(stdin), _O_BINARY); + if (err == -1) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Could not set stdin to binary mode. Unlikely to get desired result.\n"); + } A->stream_next= 0; A->stream_len = 0; @@ -786,7 +792,8 @@ int audio_get (int a) * Wait if nothing available. * Could use an event to wake up but this is adequate. */ - int timeout = 25; + // Issue 544: change from 25 to 200. That's 2 seconds total with current buff time. + int timeout = 200; while (A->in_headp == NULL) { //SLEEP_MS (ONE_BUF_TIME / 5); @@ -888,7 +895,7 @@ int audio_get (int a) while (A->stream_next >= A->stream_len) { int res; - res = read(STDIN_FILENO, A->stream_data, 1024); + res = read(STDIN_FILENO, A->stream_data, sizeof(A->stream_data)); if (res <= 0) { text_color_set(DW_COLOR_INFO); dw_printf ("\nEnd of file on stdin. Exiting.\n"); @@ -903,9 +910,13 @@ int audio_get (int a) A->stream_len = res; A->stream_next = 0; } - return (A->stream_data[A->stream_next++] & 0xff); + sample = A->stream_data[A->stream_next] & 0xff; + A->stream_next++; + return (sample); + break; - } + + } // end switch audio in type return (-1); diff --git a/src/ax25_link.c b/src/ax25_link.c index 3043a046..50495cd8 100644 --- a/src/ax25_link.c +++ b/src/ax25_link.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2016, 2017, 2018, 2023 John Langner, WB2OSZ +// Copyright (C) 2016, 2017, 2018, 2023, 2024 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 @@ -679,11 +679,13 @@ static struct misc_config_s *g_misc_config_p; * Inputs: pconfig - misc. configuration from config file or command line. * Beacon stuff ended up here. * + * debug - debug level. + * * Outputs: Remember required information for future use. That's all. * *--------------------------------------------------------------------*/ -void ax25_link_init (struct misc_config_s *pconfig) +void ax25_link_init (struct misc_config_s *pconfig, int debug) { /* @@ -691,6 +693,31 @@ void ax25_link_init (struct misc_config_s *pconfig) */ g_misc_config_p = pconfig; + if (debug >= 1) { // Only single level so far. + + s_debug_protocol_errors = 1; // Less serious Protocol errors. + + s_debug_client_app = 1; // Interaction with client application. + // dl_connect_request, dl_data_request, dl_data_indication, etc. + + s_debug_radio = 1; // Received frames and channel busy status. + // lm_data_indication, lm_channel_busy + + s_debug_variables = 1; // Variables, state changes. + + s_debug_retry = 1; // Related to lost I frames, REJ, SREJ, timeout, resending. + + s_debug_link_handle = 1; // Create data link state machine or pick existing one, + // based on my address, peer address, client app index, and radio channel. + + s_debug_stats = 1; // Statistics when connection is closed. + + s_debug_misc = 1; // Anything left over that might be interesting. + + s_debug_timers = 1; // Timer details. + } + + } /* end ax25_link_init */ @@ -2013,14 +2040,14 @@ static void dl_data_indication (ax25_dlsm_t *S, int pid, char *data, int len) * *------------------------------------------------------------------------------*/ -static int dcd_status[MAX_CHANS]; -static int ptt_status[MAX_CHANS]; +static int dcd_status[MAX_RADIO_CHANS]; +static int ptt_status[MAX_RADIO_CHANS]; void lm_channel_busy (dlq_item_t *E) { int busy; - assert (E->chan >= 0 && E->chan < MAX_CHANS); + assert (E->chan >= 0 && E->chan < MAX_RADIO_CHANS); assert (E->activity == OCTYPE_PTT || E->activity == OCTYPE_DCD); assert (E->status == 1 || E->status == 0); @@ -2104,7 +2131,7 @@ void lm_channel_busy (dlq_item_t *E) void lm_seize_confirm (dlq_item_t *E) { - assert (E->chan >= 0 && E->chan < MAX_CHANS); + assert (E->chan >= 0 && E->chan < MAX_RADIO_CHANS); ax25_dlsm_t *S; @@ -4642,6 +4669,8 @@ static void dm_frame (ax25_dlsm_t *S, int f) if (f == 1) { text_color_set(DW_COLOR_INFO); dw_printf ("%s doesn't understand AX.25 v2.2. Trying v2.0 ...\n", S->addrs[PEERCALL]); + dw_printf ("You can avoid this failed attempt and speed up the\n"); + dw_printf ("process by putting \"V20 %s\" in the configuration file.\n", S->addrs[PEERCALL]); INIT_T1V_SRT; @@ -4930,6 +4959,8 @@ static void frmr_frame (ax25_dlsm_t *S) text_color_set(DW_COLOR_INFO); dw_printf ("%s doesn't understand AX.25 v2.2. Trying v2.0 ...\n", S->addrs[PEERCALL]); + dw_printf ("You can avoid this failed attempt and speed up the\n"); + dw_printf ("process by putting \"V20 %s\" in the configuration file.\n", S->addrs[PEERCALL]); INIT_T1V_SRT; diff --git a/src/ax25_link.h b/src/ax25_link.h index 40fa401b..52caceed 100644 --- a/src/ax25_link.h +++ b/src/ax25_link.h @@ -43,7 +43,7 @@ // Call once at startup time. -void ax25_link_init (struct misc_config_s *pconfig); +void ax25_link_init (struct misc_config_s *pconfig, int debug); diff --git a/src/ax25_pad.c b/src/ax25_pad.c index 0f075808..2fce2df9 100644 --- a/src/ax25_pad.c +++ b/src/ax25_pad.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011 , 2013, 2014, 2015, 2019 John Langner, WB2OSZ +// Copyright (C) 2011 , 2013, 2014, 2015, 2019, 2024 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 @@ -174,6 +174,7 @@ #include "regex.h" #if __WIN32__ +// TODO: Why is this here, rather than in direwolf.h? char *strtok_r(char *str, const char *delim, char **saveptr); #endif @@ -194,6 +195,7 @@ static volatile int last_seq_num = 0; #if AX25MEMDEBUG +// TODO: Make static and use function for any extern references. int ax25memdebug = 0; @@ -355,12 +357,26 @@ void ax25_delete (packet_t this_p) * The SSID can be 2 alphanumeric characters, not just 1 to 15. * * We can just truncate the name because we will only - * end up discarding it. TODO: check on this. + * end up discarding it. TODO: check on this. WRONG! FIXME * * Returns: Pointer to new packet object in the current implementation. * * Outputs: Use the "get" functions to retrieve information in different ways. * + * Evolution: Originally this was written to handle only valid RF packets. + * There are other places where the rules are not as strict. + * Using decode_aprs with raw data seen on aprs.fi. e.g. + * EL-CA2JOT>RXTLM-1,TCPIP,qAR,CA2JOT::EL-CA2JOT:UNIT.... + * EA4YR>APBM1S,TCPIP*,qAS,BM2142POS:@162124z... + * * Source addr might not comply to RF format. + * * The q-construct has lower case. + * * Tier-2 server name might not comply to RF format. + * We have the same issue with the encapsulated part of a third-party packet. + * WB2OSZ-5>APDW17,WIDE1-1,WIDE2-1:}WHO-IS>APJIW4,TCPIP,WB2OSZ-5*::WB2OSZ-7 :ack0 + * + * We need a way to keep and retrieve the original name. + * This gets a little messy because the packet object is in the on air frame format. + * *------------------------------------------------------------------------------*/ #if AX25MEMDEBUG @@ -2579,6 +2595,59 @@ int ax25_get_c2 (packet_t this_p) } +/*------------------------------------------------------------------ + * + * Function: ax25_set_pid + * + * Purpose: Set protocol ID in packet. + * + * Inputs: this_p - pointer to packet object. + * + * pid - usually 0xF0 for APRS or 0xCF for NET/ROM. + * + * AX.25: "The Protocol Identifier (PID) field appears in information + * frames (I and UI) only. It identifies which kind of + * Layer 3 protocol, if any, is in use." + * + *------------------------------------------------------------------*/ + +void ax25_set_pid (packet_t this_p, int pid) +{ + assert (this_p->magic1 == MAGIC); + assert (this_p->magic2 == MAGIC); + + // Some applications set this to 0 which is an error. + // Change 0 to 0xF0 meaning no layer 3 protocol. + + if (pid == 0) { + pid = AX25_PID_NO_LAYER_3; + } + + // Sanity check: is it I or UI frame? + + if (this_p->frame_len == 0) return; + + ax25_frame_type_t frame_type; + cmdres_t cr; // command or response. + char description[64]; + int pf; // Poll/Final. + int nr, ns; // Sequence numbers. + + frame_type = ax25_frame_type (this_p, &cr, description, &pf, &nr, &ns); + + if (frame_type != frame_type_I && frame_type != frame_type_U_UI) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("ax25_set_pid(0x%2x): Packet type is not I or UI.\n", pid); + return; + } + + // TODO: handle 2 control byte case. + if (this_p->num_addr >= 2) { + this_p->frame_data[ax25_get_pid_offset(this_p)] = pid; + } +} + + /*------------------------------------------------------------------ * * Function: ax25_get_pid diff --git a/src/ax25_pad.h b/src/ax25_pad.h index cdb84c65..6d3d5cb2 100644 --- a/src/ax25_pad.h +++ b/src/ax25_pad.h @@ -66,6 +66,7 @@ #define AX25_UI_FRAME 3 /* Control field value. */ #define AX25_PID_NO_LAYER_3 0xf0 /* protocol ID used for APRS */ +#define AX25_PID_NETROM 0xcf /* protocol ID used for NET/ROM */ #define AX25_PID_SEGMENTATION_FRAGMENT 0x08 #define AX25_PID_ESCAPE_CHARACTER 0xff @@ -427,6 +428,7 @@ extern int ax25_is_null_frame (packet_t this_p); extern int ax25_get_control (packet_t this_p); extern int ax25_get_c2 (packet_t this_p); +extern void ax25_set_pid (packet_t this_p, int pid); extern int ax25_get_pid (packet_t this_p); extern int ax25_get_frame_len (packet_t this_p); diff --git a/src/beacon.c b/src/beacon.c index 69a72701..b868f228 100644 --- a/src/beacon.c +++ b/src/beacon.c @@ -162,14 +162,14 @@ 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 (chan >= MAX_TOTAL_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) { - if (strlen(g_modem_config_p->achan[chan].mycall) > 0 && - strcasecmp(g_modem_config_p->achan[chan].mycall, "N0CALL") != 0 && - strcasecmp(g_modem_config_p->achan[chan].mycall, "NOCALL") != 0) { + if (strlen(g_modem_config_p->mycall[chan]) > 0 && + strcasecmp(g_modem_config_p->mycall[chan], "N0CALL") != 0 && + strcasecmp(g_modem_config_p->mycall[chan], "NOCALL") != 0) { switch (g_misc_config_p->beacon[j].btype) { @@ -809,10 +809,10 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo) 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)); + strlcpy (mycall, g_modem_config_p->mycall[0], sizeof(mycall)); } else { - strlcpy (mycall, g_modem_config_p->achan[bp->sendto_chan].mycall, sizeof(mycall)); + strlcpy (mycall, g_modem_config_p->mycall[bp->sendto_chan], sizeof(mycall)); } if (strlen(mycall) == 0 || strcmp(mycall, "NOCALL") == 0) { @@ -900,7 +900,7 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo) case BEACON_OBJECT: - encode_object (bp->objname, bp->compress, 0, bp->lat, bp->lon, bp->ambiguity, + encode_object (bp->objname, bp->compress, 1, bp->lat, bp->lon, bp->ambiguity, bp->symtab, bp->symbol, bp->power, bp->height, bp->gain, bp->dir, G_UNKNOWN, G_UNKNOWN, /* course, speed */ diff --git a/src/cdigipeater.c b/src/cdigipeater.c index 06128b20..844af470 100644 --- a/src/cdigipeater.c +++ b/src/cdigipeater.c @@ -76,7 +76,7 @@ static struct cdigi_config_s *save_cdigi_config_p; * Maintain count of packets digipeated for each combination of from/to channel. */ -static int cdigi_count[MAX_CHANS][MAX_CHANS]; +static int cdigi_count[MAX_RADIO_CHANS][MAX_RADIO_CHANS]; int cdigipeater_get_count (int from_chan, int to_chan) { return (cdigi_count[from_chan][to_chan]); @@ -132,7 +132,9 @@ void cdigipeater (int from_chan, packet_t pp) // Connected mode is allowed only for channels with internal modem. // It probably wouldn't matter for digipeating but let's keep that rule simple and consistent. - if ( from_chan < 0 || from_chan >= MAX_CHANS || save_audio_config_p->chan_medium[from_chan] != MEDIUM_RADIO) { + if ( from_chan < 0 || from_chan >= MAX_RADIO_CHANS || + (save_audio_config_p->chan_medium[from_chan] != MEDIUM_RADIO && + save_audio_config_p->chan_medium[from_chan] != MEDIUM_NETTNC) ) { text_color_set(DW_COLOR_ERROR); dw_printf ("cdigipeater: Did not expect to receive on invalid channel %d.\n", from_chan); return; @@ -145,13 +147,13 @@ void cdigipeater (int from_chan, packet_t pp) * Might not have a benefit here. */ - for (to_chan=0; to_chan<MAX_CHANS; to_chan++) { + for (to_chan=0; to_chan<MAX_RADIO_CHANS; to_chan++) { if (save_cdigi_config_p->enabled[from_chan][to_chan]) { if (to_chan == from_chan) { packet_t result; - result = cdigipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall, - save_audio_config_p->achan[to_chan].mycall, + result = cdigipeat_match (from_chan, pp, save_audio_config_p->mycall[from_chan], + save_audio_config_p->mycall[to_chan], save_cdigi_config_p->has_alias[from_chan][to_chan], &(save_cdigi_config_p->alias[from_chan][to_chan]), to_chan, save_cdigi_config_p->cfilter_str[from_chan][to_chan]); @@ -168,13 +170,13 @@ void cdigipeater (int from_chan, packet_t pp) * Second pass: Look at packets being digipeated to different channel. */ - for (to_chan=0; to_chan<MAX_CHANS; to_chan++) { + for (to_chan=0; to_chan<MAX_RADIO_CHANS; to_chan++) { if (save_cdigi_config_p->enabled[from_chan][to_chan]) { if (to_chan != from_chan) { packet_t result; - result = cdigipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall, - save_audio_config_p->achan[to_chan].mycall, + result = cdigipeat_match (from_chan, pp, save_audio_config_p->mycall[from_chan], + save_audio_config_p->mycall[to_chan], save_cdigi_config_p->has_alias[from_chan][to_chan], &(save_cdigi_config_p->alias[from_chan][to_chan]), to_chan, save_cdigi_config_p->cfilter_str[from_chan][to_chan]); diff --git a/src/cdigipeater.h b/src/cdigipeater.h index 69a4b8c7..89b0302a 100644 --- a/src/cdigipeater.h +++ b/src/cdigipeater.h @@ -5,7 +5,7 @@ #include "regex.h" -#include "direwolf.h" /* for MAX_CHANS */ +#include "direwolf.h" /* for MAX_RADIO_CHANS */ #include "ax25_pad.h" /* for packet_t */ #include "audio.h" /* for radio channel properties */ @@ -23,17 +23,21 @@ struct cdigi_config_s { /* * Rules for each of the [from_chan][to_chan] combinations. */ - int enabled[MAX_CHANS][MAX_CHANS]; // Is it enabled for from/to pair? - int has_alias[MAX_CHANS][MAX_CHANS]; // If there was no alias in the config file, +// For APRS digipeater, we use MAX_TOTAL_CHANS because we use external TNCs. +// Connected mode packet must use internal modems we we use MAX_RADIO_CHANS. + + int enabled[MAX_RADIO_CHANS][MAX_RADIO_CHANS]; // Is it enabled for from/to pair? + + int has_alias[MAX_RADIO_CHANS][MAX_RADIO_CHANS]; // If there was no alias in the config file, // the structure below will not be set up // properly and an attempt to use it could // result in a crash. (fixed v1.5) // Not needed for [APRS] DIGIPEAT because // the alias is mandatory there. - regex_t alias[MAX_CHANS][MAX_CHANS]; + regex_t alias[MAX_RADIO_CHANS][MAX_RADIO_CHANS]; - char *cfilter_str[MAX_CHANS][MAX_CHANS]; + char *cfilter_str[MAX_RADIO_CHANS][MAX_RADIO_CHANS]; // NULL or optional Packet Filter strings such as "t/m". }; diff --git a/src/cm108.c b/src/cm108.c index ff3ff792..7bff3792 100644 --- a/src/cm108.c +++ b/src/cm108.c @@ -138,6 +138,8 @@ int main (void) #if __WIN32__ #include <wchar.h> #include "hidapi.h" +#elif __APPLE__ +#include "hidapi.h" #else #include <libudev.h> #include <sys/types.h> @@ -240,7 +242,7 @@ static int cm108_write (char *name, int iomask, int iodata); // Used to process regular expression matching results. -#ifndef __WIN32__ +#if !defined(__WIN32__) && !defined(__APPLE__) static void substr_se (char *dest, const char *src, int start, int endp1) { @@ -260,8 +262,9 @@ static void substr_se (char *dest, const char *src, int start, int endp1) // Maximum length of name for PTT HID. // For Linux, this was originally 17 to handle names like /dev/hidraw3. // Windows has more complicated names. The longest I saw was 95 but longer have been reported. +// Then we have this https://groups.io/g/direwolf/message/9622 where 127 is not enough. -#define MAXX_HIDRAW_NAME_LEN 128 +#define MAXX_HIDRAW_NAME_LEN 150 /* * Result of taking inventory of USB soundcards and USB HIDs. @@ -317,7 +320,7 @@ static void usage(void) dw_printf ("Usage: cm108 [ device-path [ gpio-num ] ]\n"); dw_printf ("\n"); dw_printf ("With no command line arguments, this will produce a list of\n"); -#if __WIN32__ +#if __WIN32__ || __APPLE__ dw_printf ("Human Interface Devices (HID) and indicate which ones can be\n"); dw_printf ("used for GPIO PTT.\n"); #else @@ -375,11 +378,11 @@ int main (int argc, char **argv) num_things = cm108_inventory (things, MAXX_THINGS); -#if __WIN32__ +#if __WIN32__ || __APPLE__ -///////////////////////////////////////////////////// -// Windows - Remove the sound related columns for now. -///////////////////////////////////////////////////// +//////////////////////////////////////////////////////////// +// Windows & Mac - Remove the sound related columns for now. +//////////////////////////////////////////////////////////// dw_printf (" VID PID %-*s %-*s" "\n", (int)sizeof(things[0].product), "Product", @@ -539,7 +542,7 @@ int cm108_inventory (struct thing_s *things, int max_things) int num_things = 0; memset (things, 0, sizeof(struct thing_s) * max_things); -#if __WIN32__ +#if __WIN32__ || __APPLE__ struct hid_device_info *devs, *cur_dev; @@ -779,7 +782,7 @@ void cm108_find_ptt (char *output_audio_device, char *ptt_device, int ptt_devic // Possible improvement: Skip if inventory already taken. num_things = cm108_inventory (things, MAXX_THINGS); -#if __WIN32__ +#if __WIN32__ || __APPLE__ // FIXME - This is just a half baked implementation. // I have not been able to figure out how to find the connection // between the audio device and HID in the same package. @@ -934,7 +937,7 @@ int cm108_set_gpio_pin (char *name, int num, int state) static int cm108_write (char *name, int iomask, int iodata) { -#if __WIN32__ +#if __WIN32__ || __APPLE__ //text_color_set(DW_COLOR_DEBUG); //dw_printf ("TEMP DEBUG cm108_write: %s %d %d\n", name, iomask, iodata); @@ -1060,8 +1063,12 @@ static int cm108_write (char *name, int iomask, int iodata) dw_printf (" crw-rw---- 1 root audio 247, 0 Oct 6 19:24 %s\n", name); dw_printf ("rather than root-only access like this:\n"); dw_printf (" crw------- 1 root root 247, 0 Sep 24 09:40 %s\n", name); + dw_printf ("This permission should be set by one of:\n"); + dw_printf ("/etc/udev/rules.d/99-direwolf-cmedia.rules\n"); + dw_printf ("/usr/lib/udev/rules.d/99-direwolf-cmedia.rules\n"); + dw_printf ("which should be created by the installation process.\n"); + dw_printf ("Your account must be in the 'audio' group.\n"); } - close (fd); return (-1); } diff --git a/src/config.c b/src/config.c index 1ad6c081..097c908f 100644 --- a/src/config.c +++ b/src/config.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2021 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2021, 2023 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -20,7 +20,7 @@ #define CONFIG_C 1 // influences behavior of aprs_tt.h -// #define DEBUG 1 +//#define DEBUG 1 /*------------------------------------------------------------------ * @@ -716,6 +716,7 @@ static void rtfm() dw_printf (" stable release: https://github.com/wb2osz/direwolf/tree/master/doc\n"); dw_printf (" development version: https://github.com/wb2osz/direwolf/tree/dev/doc\n"); dw_printf (" additional topics: https://github.com/wb2osz/direwolf-doc\n"); + dw_printf (" general APRS info: https://how.aprs.works\n"); } void config_init (char *fname, struct audio_s *p_audio_config, @@ -755,19 +756,26 @@ void config_init (char *fname, struct audio_s *p_audio_config, strlcpy (p_audio_config->adev[adevice].adevice_out, DEFAULT_ADEVICE, sizeof(p_audio_config->adev[adevice].adevice_out)); p_audio_config->adev[adevice].defined = 0; + p_audio_config->adev[adevice].copy_from = -1; p_audio_config->adev[adevice].num_channels = DEFAULT_NUM_CHANNELS; /* -2 stereo */ p_audio_config->adev[adevice].samples_per_sec = DEFAULT_SAMPLES_PER_SEC; /* -r option */ p_audio_config->adev[adevice].bits_per_sample = DEFAULT_BITS_PER_SAMPLE; /* -8 option for 8 instead of 16 bits */ } - p_audio_config->adev[0].defined = 1; - - for (channel=0; channel<MAX_CHANS; channel++) { - int ot, it; + p_audio_config->adev[0].defined = 2; // 2 means it was done by default and not the user's config file. +// MAX_TOTAL_CHANS + for (channel=0; channel<MAX_TOTAL_CHANS; channel++) { p_audio_config->chan_medium[channel] = MEDIUM_NONE; /* One or both channels will be */ /* set to radio when corresponding */ /* audio device is defined. */ + } + +// MAX_RADIO_CHANS for achan[] +// Maybe achan should be renamed to radiochan to make it clearer. + for (channel=0; channel<MAX_RADIO_CHANS; channel++) { + int ot, it; + p_audio_config->achan[channel].modem_type = MODEM_AFSK; p_audio_config->achan[channel].v26_alternative = V26_UNSPECIFIED; p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ; /* -m option */ @@ -925,10 +933,13 @@ void config_init (char *fname, struct audio_s *p_audio_config, p_misc_config->maxframe_extended = AX25_K_MAXFRAME_EXTENDED_DEFAULT; /* Max frames to send before ACK. mod 128 "Window" size. */ - p_misc_config->maxv22 = AX25_N2_RETRY_DEFAULT / 3; /* Max SABME before falling back to SABM. */ - p_misc_config->v20_addrs = NULL; /* Go directly to v2.0 for stations listed. */ + p_misc_config->maxv22 = AX25_N2_RETRY_DEFAULT / 3; /* Send SABME this many times before falling back to SABM. */ + p_misc_config->v20_addrs = NULL; /* Go directly to v2.0 for stations listed */ + /* without trying v2.2 first. */ p_misc_config->v20_count = 0; p_misc_config->noxid_addrs = NULL; /* Don't send XID to these stations. */ + /* Might work with a partial v2.2 implementation */ + /* on the other end. */ p_misc_config->noxid_count = 0; /* @@ -976,8 +987,13 @@ void config_init (char *fname, struct audio_s *p_audio_config, if (fp == NULL) { // TODO: not exactly right for all situations. text_color_set(DW_COLOR_ERROR); - dw_printf ("ERROR - Could not open config file %s\n", filepath); + dw_printf ("ERROR - Could not open configuration file %s\n", filepath); dw_printf ("Try using -c command line option for alternate location.\n"); +#ifndef __WIN32__ + dw_printf ("A sample direwolf.conf file should be found in one of:\n"); + dw_printf (" /usr/local/share/doc/direwolf/conf/\n"); + dw_printf (" /usr/share/doc/direwolf/conf/\n"); +#endif rtfm(); exit(EXIT_FAILURE); } @@ -1012,7 +1028,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, * ADEVICE plughw:1,0 -- same for in and out. * ADEVICE plughw:2,0 plughw:3,0 -- different in/out for a channel or channel pair. * ADEVICE1 udp:7355 default -- from Software defined radio (SDR) via UDP. - * + * + * New in 1.8: Ability to map to another audio device. + * This allows multiple modems (i.e. data speeds) on the same audio interface. + * + * ADEVICEn = n -- Copy from different already defined channel. */ /* Note that ALSA name can contain comma such as hw:1,0 */ @@ -1040,17 +1060,42 @@ void config_init (char *fname, struct audio_s *p_audio_config, exit(EXIT_FAILURE); } + // Do not allow same adevice to be defined more than once. + // Overriding the default for adevice 0 is ok. + // In that case definded was 2. That's why we check for 1, not just non-zero. + + if (p_audio_config->adev[adevice].defined == 1) { // 1 means defined by user. + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: ADEVICE%d can't be defined more than once. Line %d.\n", adevice, line); + continue; + } + p_audio_config->adev[adevice].defined = 1; - - /* First channel of device is valid. */ - p_audio_config->chan_medium[ADEVFIRSTCHAN(adevice)] = MEDIUM_RADIO; - strlcpy (p_audio_config->adev[adevice].adevice_in, t, sizeof(p_audio_config->adev[adevice].adevice_in)); - strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out)); + // New case for release 1.8. - t = split(NULL,0); - if (t != NULL) { + if (strcmp(t, "=") == 0) { + t = split(NULL,0); + if (t != NULL) { + + } + +///////// to be continued.... FIXME + + } + else { + /* First channel of device is valid. */ + // This might be changed to UDP or STDIN when the device name is examined. + p_audio_config->chan_medium[ADEVFIRSTCHAN(adevice)] = MEDIUM_RADIO; + + strlcpy (p_audio_config->adev[adevice].adevice_in, t, sizeof(p_audio_config->adev[adevice].adevice_in)); strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out)); + + t = split(NULL,0); + if (t != NULL) { + // Different audio devices for receive and transmit. + strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out)); + } } } @@ -1188,9 +1233,12 @@ void config_init (char *fname, struct audio_s *p_audio_config, */ /* - * CHANNEL n - Set channel for channel-specific commands. + * CHANNEL n - Set channel for channel-specific commands. Only for modem/radio channels. */ +// TODO: allow full range so mycall can be set for network channels. +// Watch out for achan[] out of bounds. + else if (strcasecmp(t, "CHANNEL") == 0) { int n; t = split(NULL,0); @@ -1200,7 +1248,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } n = atoi(t); - if (n >= 0 && n < MAX_CHANS) { + if (n >= 0 && n < MAX_RADIO_CHANS) { channel = n; @@ -1220,7 +1268,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, } else { text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Channel number must in range of 0 to %d.\n", line, MAX_CHANS-1); + dw_printf ("Line %d: Channel number must in range of 0 to %d.\n", line, MAX_RADIO_CHANS-1); } } @@ -1241,7 +1289,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } int ichan = atoi(t); - if (ichan >= MAX_CHANS && ichan < MAX_TOTAL_CHANS) { + if (ichan >= MAX_RADIO_CHANS && ichan < MAX_TOTAL_CHANS) { if (p_audio_config->chan_medium[ichan] == MEDIUM_NONE) { @@ -1253,15 +1301,73 @@ void config_init (char *fname, struct audio_s *p_audio_config, } else { text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: ICHANNEL can't use %d because it is already in use.\n", line, ichan); + dw_printf ("Line %d: ICHANNEL can't use channel %d because it is already in use.\n", line, ichan); } } else { text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: ICHANNEL number must in range of %d to %d.\n", line, MAX_CHANS, MAX_TOTAL_CHANS-1); + dw_printf ("Line %d: ICHANNEL number must in range of %d to %d.\n", line, MAX_RADIO_CHANS, MAX_TOTAL_CHANS-1); } } +/* + * NCHANNEL chan addr port - Define Network TNC virtual channel. + * + * This allows a client application to talk to to an external TNC over TCP KISS + * by using a channel number outside the normal range for modems. + * This does not change the current channel number used by MODEM, PTT, etc. + * + * chan = direwolf channel. + * addr = hostname or IP address of network TNC. + * port = KISS TCP port on network TNC. + * + * Future: Might allow selection of channel on the network TNC. + * For now, ignore incoming and set to 0 for outgoing. + * + * FIXME: Can't set mycall for nchannel. + */ + + else if (strcasecmp(t, "NCHANNEL") == 0) { + t = split(NULL,0); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing virtual channel number for NCHANNEL command.\n", line); + continue; + } + int nchan = atoi(t); + if (nchan >= MAX_RADIO_CHANS && nchan < MAX_TOTAL_CHANS) { + + if (p_audio_config->chan_medium[nchan] == MEDIUM_NONE) { + + p_audio_config->chan_medium[nchan] = MEDIUM_NETTNC; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: NCHANNEL can't use channel %d because it is already in use.\n", line, nchan); + } + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: NCHANNEL number must in range of %d to %d.\n", line, MAX_RADIO_CHANS, MAX_TOTAL_CHANS-1); + } + + t = split(NULL,0); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing network TNC address for NCHANNEL command.\n", line); + continue; + } + strlcpy (p_audio_config->nettnc_addr[nchan], t, sizeof(p_audio_config->nettnc_addr[nchan])); + + t = split(NULL,0); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Missing network TNC TCP port for NCHANNEL command.\n", line); + continue; + } + p_audio_config->nettnc_port[nchan] = atoi(t); + } + /* * MYCALL station */ @@ -1297,14 +1403,14 @@ void config_init (char *fname, struct audio_s *p_audio_config, int c; - for (c = 0; c < MAX_CHANS; c++) { + for (c = 0; c < MAX_TOTAL_CHANS; c++) { if (c == channel || - strlen(p_audio_config->achan[c].mycall) == 0 || - strcasecmp(p_audio_config->achan[c].mycall, "NOCALL") == 0 || - strcasecmp(p_audio_config->achan[c].mycall, "N0CALL") == 0) { + strlen(p_audio_config->mycall[c]) == 0 || + strcasecmp(p_audio_config->mycall[c], "NOCALL") == 0 || + strcasecmp(p_audio_config->mycall[c], "N0CALL") == 0) { - strlcpy (p_audio_config->achan[c].mycall, t, sizeof(p_audio_config->achan[c].mycall)); + strlcpy (p_audio_config->mycall[c], t, sizeof(p_audio_config->mycall[c])); } } } @@ -1332,6 +1438,12 @@ void config_init (char *fname, struct audio_s *p_audio_config, */ else if (strcasecmp(t, "MODEM") == 0) { + + if (channel < 0 || channel >= MAX_RADIO_CHANS) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: MODEM can only be used with radio channel 0 - %d.\n", line, MAX_RADIO_CHANS-1); + continue; + } int n; t = split(NULL,0); if (t == NULL) { @@ -1667,6 +1779,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "DTMF") == 0) { + if (channel < 0 || channel >= MAX_RADIO_CHANS) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: DTMF can only be used with radio channel 0 - %d.\n", line, MAX_RADIO_CHANS-1); + continue; + } p_audio_config->achan[channel].dtmf_decode = DTMF_DECODE_ON; @@ -1682,6 +1799,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, */ else if (strcasecmp(t, "FIX_BITS") == 0) { + if (channel < 0 || channel >= MAX_RADIO_CHANS) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: FIX_BITS can only be used with radio channel 0 - %d.\n", line, MAX_RADIO_CHANS-1); + continue; + } int n; t = split(NULL,0); if (t == NULL) { @@ -1760,6 +1882,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, */ else if (strcasecmp(t, "PTT") == 0 || strcasecmp(t, "DCD") == 0 || strcasecmp(t, "CON") == 0) { + if (channel < 0 || channel >= MAX_RADIO_CHANS) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: PTT can only be used with radio channel 0 - %d.\n", line, MAX_RADIO_CHANS-1); + continue; + } int ot; char otname[8]; @@ -1809,6 +1936,45 @@ void config_init (char *fname, struct audio_s *p_audio_config, } p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_GPIO; #endif + } + else if (strcasecmp(t, "GPIOD") == 0) { +#if __WIN32__ + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file line %d: %s with GPIOD is only available on Linux.\n", line, otname); +#else +#if defined(USE_GPIOD) + t = split(NULL,0); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file line %d: Missing GPIO chip name for %s.\n", line, otname); + dw_printf ("Use the \"gpioinfo\" command to get a list of gpio chip names and corresponding I/O lines.\n"); + continue; + } + strlcpy(p_audio_config->achan[channel].octrl[ot].out_gpio_name, t, + sizeof(p_audio_config->achan[channel].octrl[ot].out_gpio_name)); + + t = split(NULL,0); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Config file line %d: Missing GPIO number for %s.\n", line, otname); + continue; + } + + if (*t == '-') { + p_audio_config->achan[channel].octrl[ot].out_gpio_num = atoi(t+1); + p_audio_config->achan[channel].octrl[ot].ptt_invert = 1; + } + else { + p_audio_config->achan[channel].octrl[ot].out_gpio_num = atoi(t); + p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; + } + p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_GPIOD; +#else + text_color_set(DW_COLOR_ERROR); + dw_printf ("Application was not built with optional support for GPIOD.\n"); + dw_printf ("Install packages gpiod and libgpiod-dev, remove 'build' subdirectory, then rebuild.\n"); +#endif /* USE_GPIOD*/ +#endif /* __WIN32__ */ } else if (strcasecmp(t, "LPT") == 0) { @@ -2092,6 +2258,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, */ else if (strcasecmp(t, "TXINH") == 0) { + if (channel < 0 || channel >= MAX_RADIO_CHANS) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: TXINH can only be used with radio channel 0 - %d.\n", line, MAX_RADIO_CHANS-1); + continue; + } char itname[8]; strlcpy (itname, "TXINH", sizeof(itname)); @@ -2132,9 +2303,17 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* * DWAIT n - Extra delay for receiver squelch. n = 10 mS units. + * + * Why did I do this? Just add more to TXDELAY. + * Now undocumented in User Guide. Might disappear someday. */ else if (strcasecmp(t, "DWAIT") == 0) { + if (channel < 0 || channel >= MAX_RADIO_CHANS) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: DWAIT can only be used with radio channel 0 - %d.\n", line, MAX_RADIO_CHANS-1); + continue; + } int n; t = split(NULL,0); if (t == NULL) { @@ -2159,6 +2338,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, */ else if (strcasecmp(t, "SLOTTIME") == 0) { + if (channel < 0 || channel >= MAX_RADIO_CHANS) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: SLOTTIME can only be used with radio channel 0 - %d.\n", line, MAX_RADIO_CHANS-1); + continue; + } int n; t = split(NULL,0); if (t == NULL) { @@ -2167,14 +2351,20 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } n = atoi(t); - if (n >= 0 && n <= 255) { + if (n >= 5 && n < 50) { + // 0 = User has no clue. This would be no delay. + // 10 = Default. + // 50 = Half second. User might think it is mSec and use 100. p_audio_config->achan[channel].slottime = n; } else { p_audio_config->achan[channel].slottime = DEFAULT_SLOTTIME; text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Invalid delay time for persist algorithm. Using %d.\n", + dw_printf ("Line %d: Invalid delay time for persist algorithm. Using default %d.\n", line, p_audio_config->achan[channel].slottime); + dw_printf ("Read the Dire Wolf User Guide, \"Radio Channel - Transmit Timing\"\n"); + dw_printf ("section, to understand what this means.\n"); + dw_printf ("Why don't you just use the default?\n"); } } @@ -2183,6 +2373,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, */ else if (strcasecmp(t, "PERSIST") == 0) { + if (channel < 0 || channel >= MAX_RADIO_CHANS) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: PERSIST can only be used with radio channel 0 - %d.\n", line, MAX_RADIO_CHANS-1); + continue; + } int n; t = split(NULL,0); if (t == NULL) { @@ -2191,14 +2386,17 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } n = atoi(t); - if (n >= 0 && n <= 255) { + if (n >= 5 && n <= 250) { p_audio_config->achan[channel].persist = n; } else { p_audio_config->achan[channel].persist = DEFAULT_PERSIST; text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Invalid probability for persist algorithm. Using %d.\n", + dw_printf ("Line %d: Invalid probability for persist algorithm. Using default %d.\n", line, p_audio_config->achan[channel].persist); + dw_printf ("Read the Dire Wolf User Guide, \"Radio Channel - Transmit Timing\"\n"); + dw_printf ("section, to understand what this means.\n"); + dw_printf ("Why don't you just use the default?\n"); } } @@ -2207,6 +2405,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, */ else if (strcasecmp(t, "TXDELAY") == 0) { + if (channel < 0 || channel >= MAX_RADIO_CHANS) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: TXDELAY can only be used with radio channel 0 - %d.\n", line, MAX_RADIO_CHANS-1); + continue; + } int n; t = split(NULL,0); if (t == NULL) { @@ -2216,6 +2419,23 @@ void config_init (char *fname, struct audio_s *p_audio_config, } n = atoi(t); if (n >= 0 && n <= 255) { + text_color_set(DW_COLOR_ERROR); + if (n < 10) { + dw_printf ("Line %d: Setting TXDELAY this small is a REALLY BAD idea if you want other stations to hear you.\n", + line); + dw_printf ("Read the Dire Wolf User Guide, \"Radio Channel - Transmit Timing\"\n"); + dw_printf ("section, to understand what this means.\n"); + dw_printf ("Why don't you just use the default rather than reducing reliability?\n"); + } + else if (n >= 100) { + dw_printf ("Line %d: Keeping with tradition, going back to the 1980s, TXDELAY is in 10 millisecond units.\n", + line); + dw_printf ("Line %d: The value %d would be %.3f seconds which seems rather excessive. Are you sure you want that?\n", + line, n, (double)n * 10. / 1000.); + dw_printf ("Read the Dire Wolf User Guide, \"Radio Channel - Transmit Timing\"\n"); + dw_printf ("section, to understand what this means.\n"); + dw_printf ("Why don't you just use the default?\n"); + } p_audio_config->achan[channel].txdelay = n; } else { @@ -2231,6 +2451,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, */ else if (strcasecmp(t, "TXTAIL") == 0) { + if (channel < 0 || channel >= MAX_RADIO_CHANS) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: TXTAIL can only be used with radio channel 0 - %d.\n", line, MAX_RADIO_CHANS-1); + continue; + } int n; t = split(NULL,0); if (t == NULL) { @@ -2240,12 +2465,28 @@ void config_init (char *fname, struct audio_s *p_audio_config, } n = atoi(t); if (n >= 0 && n <= 255) { + if (n < 5) { + dw_printf ("Line %d: Setting TXTAIL that small is a REALLY BAD idea if you want other stations to hear you.\n", + line); + dw_printf ("Read the Dire Wolf User Guide, \"Radio Channel - Transmit Timing\"\n"); + dw_printf ("section, to understand what this means.\n"); + dw_printf ("Why don't you just use the default rather than reducing reliability?\n"); + } + else if (n >= 50) { + dw_printf ("Line %d: Keeping with tradition, going back to the 1980s, TXTAIL is in 10 millisecond units.\n", + line); + dw_printf ("Line %d: The value %d would be %.3f seconds which seems rather excessive. Are you sure you want that?\n", + line, n, (double)n * 10. / 1000.); + dw_printf ("Read the Dire Wolf User Guide, \"Radio Channel - Transmit Timing\"\n"); + dw_printf ("section, to understand what this means.\n"); + dw_printf ("Why don't you just use the default?\n"); + } p_audio_config->achan[channel].txtail = n; } else { p_audio_config->achan[channel].txtail = DEFAULT_TXTAIL; text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Invalid time for transmit timing. Using %d.\n", + dw_printf ("Line %d: Invalid time for transmit timing. Using %d.\n", line, p_audio_config->achan[channel].txtail); } } @@ -2255,6 +2496,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, */ else if (strcasecmp(t, "FULLDUP") == 0) { + if (channel < 0 || channel >= MAX_RADIO_CHANS) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: FULLDUP can only be used with radio channel 0 - %d.\n", line, MAX_RADIO_CHANS-1); + continue; + } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); @@ -2282,6 +2528,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "SPEECH") == 0) { + if (channel < 0 || channel >= MAX_RADIO_CHANS) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: SPEECH can only be used with radio channel 0 - %d.\n", line, MAX_RADIO_CHANS-1); + continue; + } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); @@ -2313,6 +2564,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, */ else if (strcasecmp(t, "FX25TX") == 0) { + if (channel < 0 || channel >= MAX_RADIO_CHANS) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: FX25TX can only be used with radio channel 0 - %d.\n", line, MAX_RADIO_CHANS-1); + continue; + } int n; t = split(NULL,0); if (t == NULL) { @@ -2335,7 +2591,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, } /* - * FX25AUTO n - Enable Automatic use of FX.25 for connected mode. + * FX25AUTO n - Enable Automatic use of FX.25 for connected mode. *** Not Implemented *** * Automatically enable, for that session only, when an identical * frame is sent more than this number of times. * Default 5 based on half of default RETRY. @@ -2344,6 +2600,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, */ else if (strcasecmp(t, "FX25AUTO") == 0) { + if (channel < 0 || channel >= MAX_RADIO_CHANS) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: FX25AUTO can only be used with radio channel 0 - %d.\n", line, MAX_RADIO_CHANS-1); + continue; + } int n; t = split(NULL,0); if (t == NULL) { @@ -2375,6 +2636,11 @@ void config_init (char *fname, struct audio_s *p_audio_config, else if (strcasecmp(t, "IL2PTX") == 0) { + if (channel < 0 || channel >= MAX_RADIO_CHANS) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: IL2PTX can only be used with radio channel 0 - %d.\n", line, MAX_RADIO_CHANS-1); + continue; + } p_audio_config->achan[channel].layer2_xmit = LAYER2_IL2P; p_audio_config->achan[channel].il2p_max_fec = 1; p_audio_config->achan[channel].il2p_invert_polarity = 0; @@ -2435,10 +2701,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } from_chan = atoi(t); - if (from_chan < 0 || from_chan >= MAX_CHANS) { + if (from_chan < 0 || from_chan >= MAX_TOTAL_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: FROM-channel must be in range of 0 to %d on line %d.\n", - MAX_CHANS-1, line); + MAX_TOTAL_CHANS-1, line); continue; } @@ -2465,10 +2731,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } to_chan = atoi(t); - if (to_chan < 0 || to_chan >= MAX_CHANS) { + if (to_chan < 0 || to_chan >= MAX_TOTAL_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: TO-channel must be in range of 0 to %d on line %d.\n", - MAX_CHANS-1, line); + MAX_TOTAL_CHANS-1, line); continue; } @@ -2596,10 +2862,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } from_chan = atoi(t); - if (from_chan < 0 || from_chan >= MAX_CHANS) { + if (from_chan < 0 || from_chan >= MAX_RADIO_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: FROM-channel must be in range of 0 to %d on line %d.\n", - MAX_CHANS-1, line); + MAX_RADIO_CHANS-1, line); continue; } @@ -2625,10 +2891,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } to_chan = atoi(t); - if (to_chan < 0 || to_chan >= MAX_CHANS) { + if (to_chan < 0 || to_chan >= MAX_RADIO_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: TO-channel must be in range of 0 to %d on line %d.\n", - MAX_CHANS-1, line); + MAX_RADIO_CHANS-1, line); continue; } if (p_audio_config->chan_medium[to_chan] != MEDIUM_RADIO) { @@ -2670,10 +2936,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } from_chan = atoi(t); - if (from_chan < 0 || from_chan >= MAX_CHANS) { + if (from_chan < 0 || from_chan >= MAX_RADIO_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: FROM-channel must be in range of 0 to %d on line %d.\n", - MAX_CHANS-1, line); + MAX_RADIO_CHANS-1, line); continue; } @@ -2703,10 +2969,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } to_chan = atoi(t); - if (to_chan < 0 || to_chan >= MAX_CHANS) { + if (to_chan < 0 || to_chan >= MAX_RADIO_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: TO-channel must be in range of 0 to %d on line %d.\n", - MAX_CHANS-1, line); + MAX_RADIO_CHANS-1, line); continue; } if (p_audio_config->chan_medium[to_chan] != MEDIUM_RADIO) { @@ -2789,20 +3055,20 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } if (*t == 'i' || *t == 'I') { - from_chan = MAX_CHANS; + from_chan = MAX_TOTAL_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! 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; - if (from_chan < 0 || from_chan >= MAX_CHANS) { + if (from_chan < 0 || from_chan >= MAX_TOTAL_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Filter FROM-channel must be in range of 0 to %d or \"IG\" on line %d.\n", - MAX_CHANS-1, line); + MAX_TOTAL_CHANS-1, line); continue; } @@ -2828,20 +3094,20 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } if (*t == 'i' || *t == 'I') { - to_chan = MAX_CHANS; + to_chan = MAX_TOTAL_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"); + dw_printf ("Warning! Be sure to read carefully and understand \"Successful-APRS-Gateway-Operation.pdf\" .\n"); } else { to_chan = isdigit(*t) ? atoi(t) : -999; - if (to_chan < 0 || to_chan >= MAX_CHANS) { + if (to_chan < 0 || to_chan >= MAX_TOTAL_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Filter TO-channel must be in range of 0 to %d or \"IG\" on line %d.\n", - MAX_CHANS-1, line); + MAX_TOTAL_CHANS-1, line); continue; } if (p_audio_config->chan_medium[to_chan] != MEDIUM_RADIO && @@ -2903,10 +3169,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, } from_chan = isdigit(*t) ? atoi(t) : -999; - if (from_chan < 0 || from_chan >= MAX_CHANS) { + if (from_chan < 0 || from_chan >= MAX_RADIO_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Filter FROM-channel must be in range of 0 to %d on line %d.\n", - MAX_CHANS-1, line); + MAX_RADIO_CHANS-1, line); continue; } @@ -2928,10 +3194,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, } to_chan = isdigit(*t) ? atoi(t) : -999; - if (to_chan < 0 || to_chan >= MAX_CHANS) { + if (to_chan < 0 || to_chan >= MAX_RADIO_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Filter TO-channel must be in range of 0 to %d on line %d.\n", - MAX_CHANS-1, line); + MAX_RADIO_CHANS-1, line); continue; } if (p_audio_config->chan_medium[to_chan] != MEDIUM_RADIO) { @@ -4088,10 +4354,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, } r = atoi(t); - if (r < 0 || r > MAX_CHANS-1) { + if (r < 0 || r > MAX_RADIO_CHANS-1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: DTMF receive channel must be in range of 0 to %d on line %d.\n", - MAX_CHANS-1, line); + MAX_RADIO_CHANS-1, line); continue; } @@ -4119,9 +4385,9 @@ void config_init (char *fname, struct audio_s *p_audio_config, if (isdigit(*p)) { x = *p - '0'; - if (x < 0 || x > MAX_CHANS-1) { + if (x < 0 || x > MAX_TOTAL_CHANS-1) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Config file: Transmit channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); + dw_printf ("Config file: Transmit channel must be in range of 0 to %d on line %d.\n", MAX_TOTAL_CHANS-1, line); x = -1; } else if (p_audio_config->chan_medium[x] != MEDIUM_RADIO && @@ -4411,10 +4677,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, } n = atoi(t); - if (n < 0 || n > MAX_CHANS-1) { + if (n < 0 || n > MAX_TOTAL_CHANS-1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Transmit channel must be in range of 0 to %d on line %d.\n", - MAX_CHANS-1, line); + MAX_TOTAL_CHANS-1, line); continue; } p_igate_config->tx_chan = n; @@ -4470,6 +4736,13 @@ void config_init (char *fname, struct audio_s *p_audio_config, if (t != NULL && strlen(t) > 0) { p_igate_config->t2_filter = strdup (t); + + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Warning - IGFILTER is a rarely needed expert level feature.\n", line); + dw_printf ("If you don't have a special situation and a good understanding of\n"); + dw_printf ("how this works, you probably should not be messing with it.\n"); + dw_printf ("The default behavior is appropriate for most situations.\n"); + dw_printf ("Please read \"Successful-APRS-IGate-Operation.pdf\".\n"); } } @@ -4673,9 +4946,9 @@ void config_init (char *fname, struct audio_s *p_audio_config, t = split(NULL,0); if (t != NULL) { chan = atoi(t); - if (chan < 0 || chan >= MAX_CHANS) { + if (chan < 0 || chan >= MAX_TOTAL_CHANS) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Invalid channel %d for KISSPORT command. Must be in range 0 thru %d.\n", line, chan, MAX_CHANS-1); + dw_printf ("Line %d: Invalid channel %d for KISSPORT command. Must be in range 0 thru %d.\n", line, chan, MAX_TOTAL_CHANS-1); continue; } } @@ -5386,25 +5659,25 @@ void config_init (char *fname, struct audio_s *p_audio_config, */ int i, j, k, b; - for (i=0; i<MAX_CHANS; i++) { - for (j=0; j<MAX_CHANS; j++) { + for (i=0; i<MAX_TOTAL_CHANS; i++) { + for (j=0; j<MAX_TOTAL_CHANS; j++) { /* APRS digipeating. */ if (p_digi_config->enabled[i][j]) { - if ( strcmp(p_audio_config->achan[i].mycall, "") == 0 || - strcmp(p_audio_config->achan[i].mycall, "NOCALL") == 0 || - strcmp(p_audio_config->achan[i].mycall, "N0CALL") == 0) { + if ( strcmp(p_audio_config->mycall[i], "") == 0 || + strcmp(p_audio_config->mycall[i], "NOCALL") == 0 || + strcmp(p_audio_config->mycall[i], "N0CALL") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: MYCALL must be set for receive channel %d before digipeating is allowed.\n", i); p_digi_config->enabled[i][j] = 0; } - if ( strcmp(p_audio_config->achan[j].mycall, "") == 0 || - strcmp(p_audio_config->achan[j].mycall, "NOCALL") == 0 || - strcmp(p_audio_config->achan[j].mycall, "N0CALL") == 0) { + if ( strcmp(p_audio_config->mycall[j], "") == 0 || + strcmp(p_audio_config->mycall[j], "NOCALL") == 0 || + strcmp(p_audio_config->mycall[j], "N0CALL") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: MYCALL must be set for transmit channel %d before digipeating is allowed.\n", i); @@ -5426,20 +5699,20 @@ void config_init (char *fname, struct audio_s *p_audio_config, /* Connected mode digipeating. */ - if (p_cdigi_config->enabled[i][j]) { + if (i < MAX_RADIO_CHANS && j < MAX_RADIO_CHANS && p_cdigi_config->enabled[i][j]) { - if ( strcmp(p_audio_config->achan[i].mycall, "") == 0 || - strcmp(p_audio_config->achan[i].mycall, "NOCALL") == 0 || - strcmp(p_audio_config->achan[i].mycall, "N0CALL") == 0) { + if ( strcmp(p_audio_config->mycall[i], "") == 0 || + strcmp(p_audio_config->mycall[i], "NOCALL") == 0 || + strcmp(p_audio_config->mycall[i], "N0CALL") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: MYCALL must be set for receive channel %d before digipeating is allowed.\n", i); p_cdigi_config->enabled[i][j] = 0; } - if ( strcmp(p_audio_config->achan[j].mycall, "") == 0 || - strcmp(p_audio_config->achan[j].mycall, "NOCALL") == 0 || - strcmp(p_audio_config->achan[j].mycall, "N0CALL") == 0) { + if ( strcmp(p_audio_config->mycall[j], "") == 0 || + strcmp(p_audio_config->mycall[j], "NOCALL") == 0 || + strcmp(p_audio_config->mycall[j], "N0CALL") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: MYCALL must be set for transmit channel %d before digipeating is allowed.\n", i); @@ -5463,7 +5736,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, if (strlen(p_igate_config->t2_login) > 0 && (p_audio_config->chan_medium[i] == MEDIUM_RADIO || p_audio_config->chan_medium[i] == MEDIUM_NETTNC)) { - if (strcmp(p_audio_config->achan[i].mycall, "NOCALL") == 0 || strcmp(p_audio_config->achan[i].mycall, "N0CALL") == 0) { + if (strcmp(p_audio_config->mycall[i], "NOCALL") == 0 || strcmp(p_audio_config->mycall[i], "N0CALL") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: MYCALL must be set for receive channel %d before Rx IGate is allowed.\n", i); strlcpy (p_igate_config->t2_login, "", sizeof(p_igate_config->t2_login)); @@ -5471,9 +5744,9 @@ void config_init (char *fname, struct audio_s *p_audio_config, // Currently we can have only one transmit channel. // This might be generalized someday to allow more. if (p_igate_config->tx_chan >= 0 && - ( strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "") == 0 || - strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "NOCALL") == 0 || - strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "N0CALL") == 0)) { + ( strcmp(p_audio_config->mycall[p_igate_config->tx_chan], "") == 0 || + strcmp(p_audio_config->mycall[p_igate_config->tx_chan], "NOCALL") == 0 || + strcmp(p_audio_config->mycall[p_igate_config->tx_chan], "N0CALL") == 0)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: MYCALL must be set for transmit channel %d before Tx IGate is allowed.\n", i); @@ -5486,10 +5759,10 @@ void config_init (char *fname, struct audio_s *p_audio_config, // This will handle eventual case of multiple transmit channels. if (strlen(p_igate_config->t2_login) > 0) { - for (j=0; j<MAX_CHANS; j++) { + for (j=0; j<MAX_TOTAL_CHANS; j++) { if (p_audio_config->chan_medium[j] == MEDIUM_RADIO || p_audio_config->chan_medium[j] == MEDIUM_NETTNC) { - if (p_digi_config->filter_str[MAX_CHANS][j] == NULL) { - p_digi_config->filter_str[MAX_CHANS][j] = strdup("i/180"); + if (p_digi_config->filter_str[MAX_TOTAL_CHANS][j] == NULL) { + p_digi_config->filter_str[MAX_TOTAL_CHANS][j] = strdup("i/180"); } } } @@ -5622,7 +5895,7 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ } else if (value[0] == 'r' || value[0] == 'R') { int n = atoi(value+1); - if (( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) + if (( n < 0 || n >= MAX_TOTAL_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); @@ -5633,7 +5906,7 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ } else if (value[0] == 't' || value[0] == 'T' || value[0] == 'x' || value[0] == 'X') { int n = atoi(value+1); - if (( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) + if (( n < 0 || n >= MAX_TOTAL_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); @@ -5645,7 +5918,7 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ } else { int n = atoi(value); - if (( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) + if (( n < 0 || n >= MAX_TOTAL_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); @@ -5896,7 +6169,7 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ if (b->sendto_type == SENDTO_XMIT) { - if (( b->sendto_chan < 0 || b->sendto_chan >= MAX_CHANS || p_audio_config->chan_medium[b->sendto_chan] == MEDIUM_NONE) + if (( b->sendto_chan < 0 || b->sendto_chan >= MAX_TOTAL_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); @@ -5905,18 +6178,18 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ 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 ) { + if ( strcmp(p_audio_config->mycall[0], "") == 0 || + strcmp(p_audio_config->mycall[0], "NOCALL") == 0 || + strcmp(p_audio_config->mycall[0], "N0CALL") == 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 ) { + if ( strcmp(p_audio_config->mycall[b->sendto_chan], "") == 0 || + strcmp(p_audio_config->mycall[b->sendto_chan], "NOCALL") == 0 || + strcmp(p_audio_config->mycall[b->sendto_chan], "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); diff --git a/src/config.h b/src/config.h index 360ac492..e4675238 100644 --- a/src/config.h +++ b/src/config.h @@ -30,7 +30,7 @@ enum sendto_type_e { SENDTO_XMIT, SENDTO_IGATE, SENDTO_RECV }; #define MAX_BEACONS 30 -#define MAX_KISS_TCP_PORTS (MAX_CHANS+1) +#define MAX_KISS_TCP_PORTS (MAX_RADIO_CHANS+1) struct misc_config_s { diff --git a/src/decode_aprs.c b/src/decode_aprs.c index ab933278..acfed6b8 100644 --- a/src/decode_aprs.c +++ b/src/decode_aprs.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2017, 2022 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2017, 2022, 2023, 2024 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 @@ -56,7 +56,7 @@ #include "decode_aprs.h" #include "telemetry.h" #include "ais.h" - +#include "deviceid.h" #define TRUE 1 #define FALSE 0 @@ -124,7 +124,6 @@ static double get_longitude_9 (char *p, int quiet); static time_t get_timestamp (decode_aprs_t *A, char *p); static int get_maidenhead (decode_aprs_t *A, char *p); static int data_extension_comment (decode_aprs_t *A, char *pdext); -static void decode_tocall (decode_aprs_t *A, char *dest); //static void get_symbol (decode_aprs_t *A, char dti, char *src, char *dest); static void process_comment (decode_aprs_t *A, char *pstart, int clen); @@ -207,9 +206,36 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr A->g_footprint_lon = G_UNKNOWN; A->g_footprint_radius = G_UNKNOWN; -// TODO: Complain if obsolete WIDE or RELAY is found in via path. -// TODO: complain if unused WIDEn is see in path. +// Check for RFONLY or NOGATE in the destination field. +// Actual cases observed. +// W1KU-4>APDW15,W1IMD,WIDE1,KQ1L-8,N3LLO-3,WIDE2*:}EB1EBT-9>NOGATE,TCPIP,W1KU-4*::DF1AKR-9 :73{4 +// NE1CU-10>RFONLY,KB1AEV-15,N3LLO-3,WIDE2*:}W1HS-11>APMI06,TCPIP,NE1CU-10*:T#050,190,039,008,095,20403,00000000 + + char atemp[AX25_MAX_ADDR_LEN]; + ax25_get_addr_no_ssid (pp, AX25_DESTINATION, atemp); + if ( ! quiet) { + if (strcmp("RFONLY", atemp) == 0 || strcmp("NOGATE", atemp) == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf("RFONLY and NOGATE must not appear in the destination address field.\n"); + dw_printf("They should appear only at the end of the digi via path.\n"); + } + } + +// Complain if obsolete WIDE or RELAY is found in via path. + + for (int i = 0; i < ax25_get_num_repeaters(pp); i++) { + ax25_get_addr_no_ssid (pp, AX25_REPEATER_1 + i, atemp); + if ( ! quiet) { + if (strcmp("RELAY", atemp) == 0 || strcmp("WIDE", atemp) == 0 || strcmp("TRACE", atemp) == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf("RELAY, TRACE, and WIDE (not WIDEn) are obsolete.\n"); + dw_printf("Modern digipeaters will not recoginize these.\n"); + } + } + } + +// TODO: complain if unused WIDEn-0 is see in path. // There is a report of UIDIGI decrementing ssid 1 to 0 and not marking it used. // http://lists.tapr.org/pipermail/aprssig_lists.tapr.org/2022-May/049397.html @@ -292,7 +318,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr } /* - * Application might be in the destination field for most message types. + * Device/Application is in the destination field for most packet types. * MIC-E format has part of location in the destination field. */ @@ -303,7 +329,7 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet, char *third_party_sr break; default: - decode_tocall (A, A->g_dest); + deviceid_decode_dest (A->g_dest, A->g_mfr, sizeof(A->g_mfr)); break; } @@ -1392,10 +1418,18 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int int cust_msg = 0; const char *std_text[8] = {"Emergency", "Priority", "Special", "Committed", "Returning", "In Service", "En Route", "Off Duty" }; const char *cust_text[8] = {"Emergency", "Custom-6", "Custom-5", "Custom-4", "Custom-3", "Custom-2", "Custom-1", "Custom-0" }; - unsigned char *pfirst, *plast; strlcpy (A->g_data_type_desc, "MIC-E", sizeof(A->g_data_type_desc)); + if (ilen < sizeof(struct aprs_mic_e_s)) { + if ( ! A->g_quiet) { + text_color_set(DW_COLOR_ERROR); + dw_printf("MIC-E format must have at least %d characters in the information part.\n", (int)(sizeof(struct aprs_mic_e_s))); + } + return; + } + info[ilen] = '\0'; + p = (struct aprs_mic_e_s *)info; /* Destination is really latitude of form ddmmhh. */ @@ -1622,126 +1656,57 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int A->g_course = n; -/* Now try to pick out manufacturer and other optional items. */ -/* The telemetry field, in the original spec, is no longer used. */ - - strlcpy (A->g_mfr, "Unknown manufacturer", sizeof(A->g_mfr)); - - pfirst = info + sizeof(struct aprs_mic_e_s); - plast = info + ilen - 1; - -/* Carriage return character at the end is not mentioned in spec. */ -/* Remove if found because it messes up extraction of manufacturer. */ -/* Don't drop trailing space because that is used for Yaesu VX-8. */ -/* As I recall, the IGate function trims trailing spaces. */ -/* That would be bad for this particular model. Maybe I'm mistaken? */ - - - if (*plast == '\r') plast--; - -#define isT(c) ((c) == ' ' || (c) == '>' || (c) == ']' || (c) == '`' || (c) == '\'') - -// Last Updated Dec. 2021 - -// This does not change very often but I'm wondering if we could parse -// http://www.aprs.org/aprs12/mic-e-types.txt similar to how we use tocalls.txt. - -// TODO: Use https://github.com/aprsorg/aprs-deviceid rather than hardcoding. - - if (isT(*pfirst)) { - -// "legacy" formats. - - if (*pfirst == ' ' ) { strlcpy (A->g_mfr, "Original MIC-E", sizeof(A->g_mfr)); pfirst++; } +// The rest is a comment which can have other information cryptically embedded. +// Remove any trailing CR, which I would argue, violates the protocol spec. +// It is essential to keep trailing spaces. e.g. VX-8 device id suffix is "_ " - else if (*pfirst == '>' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TH-D72", sizeof(A->g_mfr)); pfirst++; plast--; } - else if (*pfirst == '>' && *plast == '^') { strlcpy (A->g_mfr, "Kenwood TH-D74", sizeof(A->g_mfr)); pfirst++; plast--; } - else if (*pfirst == '>' && *plast == '&') { strlcpy (A->g_mfr, "Kenwood TH-D75", sizeof(A->g_mfr)); pfirst++; plast--; } - else if (*pfirst == '>' ) { strlcpy (A->g_mfr, "Kenwood TH-D7A", sizeof(A->g_mfr)); pfirst++; } - - else if (*pfirst == ']' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TM-D710", sizeof(A->g_mfr)); pfirst++; plast--; } - else if (*pfirst == ']' ) { strlcpy (A->g_mfr, "Kenwood TM-D700", sizeof(A->g_mfr)); pfirst++; } + if (ilen <= sizeof(struct aprs_mic_e_s)) { + // Too short for a comment. We are finished. + strlcpy (A->g_mfr, "UNKNOWN vendor/model", sizeof(A->g_mfr)); + return; + } -// ` should be used for message capable devices. + char mcomment[256]; + strlcpy (mcomment, ((char*)info) + sizeof(struct aprs_mic_e_s), sizeof(mcomment)); - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ' ') { strlcpy (A->g_mfr, "Yaesu VX-8", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '"') { strlcpy (A->g_mfr, "Yaesu FTM-350", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '#') { strlcpy (A->g_mfr, "Yaesu VX-8G", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '$') { strlcpy (A->g_mfr, "Yaesu FT1D", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '%') { strlcpy (A->g_mfr, "Yaesu FTM-400DR", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ')') { strlcpy (A->g_mfr, "Yaesu FTM-100D", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '(') { strlcpy (A->g_mfr, "Yaesu FT2D", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '0') { strlcpy (A->g_mfr, "Yaesu FT3D", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '3') { strlcpy (A->g_mfr, "Yaesu FT5D", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '1') { strlcpy (A->g_mfr, "Yaesu FTM-300D", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '5') { strlcpy (A->g_mfr, "Yaesu FTM-500D", sizeof(A->g_mfr)); pfirst++; plast-=2; } + assert (strlen(mcomment) > 0); - else if (*pfirst == '`' && *(plast-1) == ' ' && *plast == 'X') { strlcpy (A->g_mfr, "AP510", sizeof(A->g_mfr)); pfirst++; plast-=2; } + if (mcomment[strlen(mcomment)-1] == '\r') { + mcomment[strlen(mcomment)-1] = '\0'; + if (strlen(mcomment) == 0) { + // Nothing left after removing trailing CR. + strlcpy (A->g_mfr, "UNKNOWN vendor/model", sizeof(A->g_mfr)); + return; + } + } - else if (*pfirst == '`' && *(plast-1) == '(' && *plast == '5') { strlcpy (A->g_mfr, "Anytone D578UV", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '`' ) { strlcpy (A->g_mfr, "Generic Mic-Emsg", sizeof(A->g_mfr)); pfirst++; } +/* Now try to pick out manufacturer and other optional items. */ +/* The telemetry field, in the original spec, is no longer used. */ -// ' should be used for trackers (not message capable). + char trimmed[256]; // Comment with vendor/model removed. + deviceid_decode_mice (mcomment, trimmed, sizeof(trimmed), A->g_mfr, sizeof(A->g_mfr)); - else if (*pfirst == '\'' && *(plast-1) == '(' && *plast == '5') { strlcpy (A->g_mfr, "Anytone D578UV", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '\'' && *(plast-1) == '(' && *plast == '8') { strlcpy (A->g_mfr, "Anytone D878UV", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '3') { strlcpy (A->g_mfr, "Byonics TinyTrack3", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '\'' && *(plast-1) == '|' && *plast == '4') { strlcpy (A->g_mfr, "Byonics TinyTrack4", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '\'' && *(plast-1) == ':' && *plast == '4') { strlcpy (A->g_mfr, "SCS GmbH & Co. P4dragon DR-7400 modems", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '\'' && *(plast-1) == ':' && *plast == '8') { strlcpy (A->g_mfr, "SCS GmbH & Co. P4dragon DR-7800 modems", sizeof(A->g_mfr)); pfirst++; plast-=2; } +// Possible altitude at beginning of remaining comment. +// Three base 91 characters followed by } - else if (*pfirst == '\'' ) { strlcpy (A->g_mfr, "Generic McTrackr", sizeof(A->g_mfr)); pfirst++; } - else if ( *(plast-1) == '\\' ) { strlcpy (A->g_mfr, "Hamhud ?", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if ( *(plast-1) == '/' ) { strlcpy (A->g_mfr, "Argent ?", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if ( *(plast-1) == '^' ) { strlcpy (A->g_mfr, "HinzTec anyfrog", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if ( *(plast-1) == '*' ) { strlcpy (A->g_mfr, "APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if ( *(plast-1) == '~' ) { strlcpy (A->g_mfr, "Unknown OTHER", sizeof(A->g_mfr)); pfirst++; plast-=2; } - } - -/* - * An optional altitude is next. - * It is three base-91 digits followed by "}". - * The TM-D710A might have encoding bug. This was observed: - * - * KJ4ETP-9>SUUP9Q,KE4OTZ-3,WIDE1*,WIDE2-1,qAR,KI4HDU-2:`oV$n6:>/]"7&}162.475MHz <Knox,TN> clintserman@gmail= - * N 35 50.9100, W 083 58.0800, 25 MPH, course 230, alt 945 ft, 162.475MHz - * - * KJ4ETP-9>SUUP6Y,GRNTOP-3*,WIDE2-1,qAR,KI4HDU-2:`oU~nT >/]<0x9a>xt}162.475MHz <Knox,TN> clintserman@gmail= - * Invalid character in MIC-E altitude. Must be in range of '!' to '{'. - * N 35 50.6900, W 083 57.9800, 29 MPH, course 204, alt 3280843 ft, 162.475MHz - * - * KJ4ETP-9>SUUP6Y,N4NEQ-3,K4EGA-1,WIDE2*,qAS,N5CWH-1:`oU~nT >/]?xt}162.475MHz <Knox,TN> clintserman@gmail= - * N 35 50.6900, W 083 57.9800, 29 MPH, course 204, alt 808497 ft, 162.475MHz - * - * KJ4ETP-9>SUUP2W,KE4OTZ-3,WIDE1*,WIDE2-1,qAR,KI4HDU-2:`oV2o"J>/]"7)}162.475MHz <Knox,TN> clintserman@gmail= - * N 35 50.2700, W 083 58.2200, 35 MPH, course 246, alt 955 ft, 162.475MHz - * - * Note the <0x9a> which is outside of the 7-bit ASCII range. Clearly very wrong. - */ + if (strlen(trimmed) >= 4 && + isdigit91(trimmed[0]) && + isdigit91(trimmed[1]) && + isdigit91(trimmed[2]) && + trimmed[3] == '}') { - if (plast > pfirst && pfirst[3] == '}') { + A->g_altitude_ft = DW_METERS_TO_FEET((trimmed[0]-33)*91*91 + (trimmed[1]-33)*91 + (trimmed[2]-33) - 10000); - A->g_altitude_ft = DW_METERS_TO_FEET((pfirst[0]-33)*91*91 + (pfirst[1]-33)*91 + (pfirst[2]-33) - 10000); - - if ( ! isdigit91(pfirst[0]) || ! isdigit91(pfirst[1]) || ! isdigit91(pfirst[2])) - { - if ( ! A->g_quiet) { - text_color_set(DW_COLOR_ERROR); - dw_printf("Invalid character in MIC-E altitude. Must be in range of '!' to '{'.\n"); - dw_printf("Bogus altitude of %.0f changed to unknown.\n", A->g_altitude_ft); - } - A->g_altitude_ft = G_UNKNOWN; - } - - pfirst += 4; + process_comment (A, trimmed+4, strlen(trimmed) - 4); + return; } - process_comment (A, (char*)pfirst, (int)(plast - pfirst) + 1); + process_comment (A, trimmed, strlen(trimmed)); -} +} // end aprs_mic_e /*------------------------------------------------------------------ @@ -3933,7 +3898,7 @@ double get_longitude_9 (char *p, int quiet) * * Inputs: p - Pointer to first byte. * - * Returns: time_t data type. (UTC) + * Returns: time_t data type. (UTC) Zero if error. * * Description: * @@ -4003,6 +3968,13 @@ time_t get_timestamp (decode_aprs_t *A, char *p) /* h = UTC. */ } *phms; + if ( ! (isdigit(p[0]) && isdigit(p[1]) && isdigit(p[2]) && isdigit(p[3]) && isdigit(p[4]) && isdigit(p[5]) && + (p[6] == 'z' || p[6] == '/' || p[6] == 'h'))) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Timestamp must be 6 digits followed by z, h, or /.\n"); + return ((time_t)0); + } + struct tm *ptm; time_t ts; @@ -4251,221 +4223,6 @@ static int data_extension_comment (decode_aprs_t *A, char *pdext) } -/*------------------------------------------------------------------ - * - * Function: decode_tocall - * - * Purpose: Extract application from the destination. - * - * Inputs: dest - Destination address. - * Don't care if SSID is present or not. - * - * Outputs: A->g_mfr - * - * Description: For maximum flexibility, we will read the - * data file at run time rather than compiling it in. - * - * For the most recent version, download from: - * - * http://www.aprs.org/aprs11/tocalls.txt - * - * Windows version: File must be in current working directory. - * - * Linux version: Search order is current working directory then - * /usr/local/share/direwolf - * /usr/share/direwolf/tocalls.txt - * - * Mac: Like Linux and then - * /opt/local/share/direwolf - * - *------------------------------------------------------------------*/ - -// If I was more ambitious, this would dynamically allocate enough -// storage based on the file contents. Just stick in a constant for -// now. This takes an insignificant amount of space and -// I don't anticipate tocalls.txt growing that quickly. -// Version 1.4 - add message if too small instead of silently ignoring the rest. - -// Dec. 2016 tocalls.txt has 153 destination addresses. - -#define MAX_TOCALLS 250 - -static struct tocalls_s { - unsigned char len; - char prefix[7]; - char *description; -} tocalls[MAX_TOCALLS]; - -static int num_tocalls = 0; - -// Make sure the array is null terminated. -// If search order is changed, do the same in symbols.c for consistency. - -static const char *search_locations[] = { - (const char *) "tocalls.txt", // CWD - (const char *) "data/tocalls.txt", // Windows with CMake - (const char *) "../data/tocalls.txt", // ? -#ifndef __WIN32__ - (const char *) "/usr/local/share/direwolf/tocalls.txt", - (const char *) "/usr/share/direwolf/tocalls.txt", -#endif -#if __APPLE__ - // https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/2458 - // Adding the /opt/local tree since macports typically installs there. Users might want their - // INSTALLDIR (see Makefile.macosx) to mirror that. If so, then we need to search the /opt/local - // path as well. - (const char *) "/opt/local/share/direwolf/tocalls.txt", -#endif - (const char *) NULL // Important - Indicates end of list. -}; - -static int tocall_cmp (const void *px, const void *py) -{ - const struct tocalls_s *x = (struct tocalls_s *)px; - const struct tocalls_s *y = (struct tocalls_s *)py; - - if (x->len != y->len) return (y->len - x->len); - return (strcmp(x->prefix, y->prefix)); -} - -static void decode_tocall (decode_aprs_t *A, char *dest) -{ - FILE *fp = 0; - int n = 0; - static int first_time = 1; - char stuff[100]; - char *p = NULL; - char *r = NULL; - - //dw_printf("debug: decode_tocall(\"%s\")\n", dest); - -/* - * Extract the calls and descriptions from the file. - * - * Use only lines with exactly these formats: - * - * APN Network nodes, digis, etc - * APWWxx APRSISCE win32 version - * | | | - * 00000000001111111111 - * 01234567890123456789... - * - * Matching will be with only leading upper case and digits. - */ - -// TODO: Look for this in multiple locations. -// For example, if application was installed in /usr/local/bin, -// we might want to put this in /usr/local/share/aprs - -// If search strategy changes, be sure to keep symbols_init in sync. - - if (first_time) { - - n = 0; - fp = NULL; - do { - if(search_locations[n] == NULL) break; - fp = fopen(search_locations[n++], "r"); - } while (fp == NULL); - - if (fp != NULL) { - - while (fgets(stuff, sizeof(stuff), fp) != NULL && num_tocalls < MAX_TOCALLS) { - - p = stuff + strlen(stuff) - 1; - while (p >= stuff && (*p == '\r' || *p == '\n')) { - *p-- = '\0'; - } - - // dw_printf("debug: %s\n", stuff); - - if (stuff[0] == ' ' && - stuff[4] == ' ' && - stuff[5] == ' ' && - stuff[6] == 'A' && - stuff[7] == 'P' && - stuff[12] == ' ' && - stuff[13] == ' ' ) { - - p = stuff + 6; - r = tocalls[num_tocalls].prefix; - while (isupper((int)(*p)) || isdigit((int)(*p))) { - *r++ = *p++; - } - *r = '\0'; - if (strlen(tocalls[num_tocalls].prefix) > 2) { - tocalls[num_tocalls].description = strdup(stuff+14); - tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix); - // dw_printf("debug %d: %d '%s' -> '%s'\n", num_tocalls, tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description); - - num_tocalls++; - } - } - else if (stuff[0] == ' ' && - stuff[1] == 'A' && - stuff[2] == 'P' && - isupper((int)(stuff[3])) && - stuff[4] == ' ' && - stuff[5] == ' ' && - stuff[6] == ' ' && - stuff[12] == ' ' && - stuff[13] == ' ' ) { - - p = stuff + 1; - r = tocalls[num_tocalls].prefix; - while (isupper((int)(*p)) || isdigit((int)(*p))) { - *r++ = *p++; - } - *r = '\0'; - if (strlen(tocalls[num_tocalls].prefix) > 2) { - tocalls[num_tocalls].description = strdup(stuff+14); - tocalls[num_tocalls].len = strlen(tocalls[num_tocalls].prefix); - // dw_printf("debug %d: %d '%s' -> '%s'\n", num_tocalls, tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description); - - num_tocalls++; - } - } - if (num_tocalls == MAX_TOCALLS) { // oops. might have discarded some. - text_color_set(DW_COLOR_ERROR); - dw_printf("MAX_TOCALLS needs to be larger than %d to handle contents of 'tocalls.txt'.\n", MAX_TOCALLS); - } - } - fclose(fp); - -/* - * Sort by decreasing length so the search will go - * from most specific to least specific. - * Example: APY350 or APY008 would match those specific - * models before getting to the more generic APY. - */ - - qsort (tocalls, num_tocalls, sizeof(struct tocalls_s), tocall_cmp); - - } - else { - if ( ! A->g_quiet) { - text_color_set(DW_COLOR_ERROR); - dw_printf("Warning: Could not open 'tocalls.txt'.\n"); - dw_printf("System types in the destination field will not be decoded.\n"); - } - } - - first_time = 0; - - //for (n=0; n<num_tocalls; n++) { - // dw_printf("sorted %d: %d '%s' -> '%s'\n", n, tocalls[n].len, tocalls[n].prefix, tocalls[n].description); - //} - } - - - for (n=0; n<num_tocalls; n++) { - if (strncmp(dest, tocalls[n].prefix, tocalls[n].len) == 0) { - strlcpy (A->g_mfr, tocalls[n].description, sizeof(A->g_mfr)); - return; - } - } - -} /* end decode_tocall */ @@ -4513,7 +4270,7 @@ static void substr_se (char *dest, const char *src, int start, int endp1) * clen - Length of comment or -1 to take it all. * * Outputs: A->g_telemetry - Base 91 telemetry |ss1122| - * A->g_altitude_ft - from /A=123456 + * A->g_altitude_ft - from /A=123456 or /A=-12345 * A->g_lat - Might be adjusted from !DAO! * A->g_lon - Might be adjusted from !DAO! * A->g_aprstt_loc - Private extension to !DAO! @@ -4543,6 +4300,10 @@ static void substr_se (char *dest, const char *src, int start, int endp1) * Protocol reference, end of chapter 6. * * /A=123456 Altitude + * /A=-12345 Enhancement - There are many places on the earth's + * surface but the APRS spec has no provision for negative + * numbers. I propose having 5 digits for a consistent + * field width. 6 would be excessive. * * What can appear in a comment? * @@ -4708,7 +4469,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg); } - e = regcomp (&alt_re, "/A=[0-9][0-9][0-9][0-9][0-9][0-9]", REG_EXTENDED); + e = regcomp (&alt_re, "/A=[0-9-][0-9][0-9][0-9][0-9][0-9]", REG_EXTENDED); if (e) { regerror (e, &alt_re, emsg, sizeof(emsg)); dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg); @@ -5068,7 +4829,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) } /* - * Altitude in feet. /A=123456 + * Altitude in feet. /A=123456 or /A=-12345 */ if (regexec (&alt_re, A->g_comment, MAXMATCH, match, 0) == 0) @@ -5186,7 +4947,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) * * Function: main * - * Purpose: Main program for standalone test program. + * Purpose: Main program for standalone application to parse and explain APRS packets. * * Inputs: stdin for raw data to decode. * This is in the usual display format either from @@ -5332,6 +5093,7 @@ int main (int argc, char *argv[]) // If you don't like the text colors, use 0 instead of 1 here. text_color_init(1); text_color_set(DW_COLOR_INFO); + deviceid_init(); while (fgets(stuff, sizeof(stuff), stdin) != NULL) { diff --git a/src/demod.c b/src/demod.c index cc522271..ebbcbed4 100644 --- a/src/demod.c +++ b/src/demod.c @@ -31,6 +31,8 @@ * *---------------------------------------------------------------*/ +#define DEMOD_C 1 + #include "direwolf.h" #include <stdlib.h> @@ -63,11 +65,11 @@ static struct audio_s *save_audio_config_p; // Current state of all the decoders. -static struct demodulator_state_s demodulator_state[MAX_CHANS][MAX_SUBCHANS]; +static struct demodulator_state_s demodulator_state[MAX_RADIO_CHANS][MAX_SUBCHANS]; -static int sample_sum[MAX_CHANS][MAX_SUBCHANS]; -static int sample_count[MAX_CHANS][MAX_SUBCHANS]; +static int sample_sum[MAX_RADIO_CHANS][MAX_SUBCHANS]; +static int sample_count[MAX_RADIO_CHANS][MAX_SUBCHANS]; /*------------------------------------------------------------------ @@ -100,7 +102,7 @@ int demod_init (struct audio_s *pa) save_audio_config_p = pa; - for (chan = 0; chan < MAX_CHANS; chan++) { + for (chan = 0; chan < MAX_RADIO_CHANS; chan++) { if (save_audio_config_p->chan_medium[chan] == MEDIUM_RADIO) { @@ -306,6 +308,7 @@ int demod_init (struct audio_s *pa) save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec); if (save_audio_config_p->achan[chan].decimate != 1) dw_printf (" / %d", save_audio_config_p->achan[chan].decimate); + dw_printf (", Tx %s", layer2_tx[(int)(save_audio_config_p->achan[chan].layer2_xmit)]); if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF) dw_printf (", DTMF decoder enabled"); dw_printf (".\n"); @@ -540,7 +543,7 @@ int demod_init (struct audio_s *pa) save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec); if (save_audio_config_p->achan[chan].decimate != 1) dw_printf (" / %d", save_audio_config_p->achan[chan].decimate); - + dw_printf (", Tx %s", layer2_tx[(int)(save_audio_config_p->achan[chan].layer2_xmit)]); if (save_audio_config_p->achan[chan].v26_alternative == V26_B) dw_printf (", compatible with MFJ-2400"); else @@ -601,6 +604,7 @@ int demod_init (struct audio_s *pa) save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec); if (save_audio_config_p->achan[chan].decimate != 1) dw_printf (" / %d", save_audio_config_p->achan[chan].decimate); + dw_printf (", Tx %s", layer2_tx[(int)(save_audio_config_p->achan[chan].layer2_xmit)]); if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF) dw_printf (", DTMF decoder enabled"); dw_printf (".\n"); @@ -736,6 +740,7 @@ int demod_init (struct audio_s *pa) save_audio_config_p->achan[chan].profiles, save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec, save_audio_config_p->achan[chan].upsample); + dw_printf (", Tx %s", layer2_tx[(int)(save_audio_config_p->achan[chan].layer2_xmit)]); if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF) dw_printf (", DTMF decoder enabled"); dw_printf (".\n"); @@ -812,7 +817,7 @@ int demod_init (struct audio_s *pa) // Now the virtual channels. FIXME: could be single loop. - for (chan = MAX_CHANS; chan < MAX_TOTAL_CHANS; chan++) { + for (chan = MAX_RADIO_CHANS; chan < MAX_TOTAL_CHANS; chan++) { // FIXME dw_printf ("-------- virtual channel loop %d \n", chan); @@ -927,7 +932,7 @@ int demod_get_sample (int a) * *--------------------------------------------------------------------*/ -static volatile int mute_input[MAX_CHANS]; +static volatile int mute_input[MAX_RADIO_CHANS]; // New in 1.7. // A few people have a really bad audio cross talk situation where they receive their own transmissions. @@ -939,7 +944,7 @@ static volatile int mute_input[MAX_CHANS]; void demod_mute_input (int chan, int mute_during_xmit) { - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); mute_input[chan] = mute_during_xmit; } @@ -952,7 +957,7 @@ void demod_process_sample (int chan, int subchan, int sam) struct demodulator_state_s *D; - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); if (mute_input[chan]) { @@ -1066,7 +1071,7 @@ alevel_t demod_get_audio_level (int chan, int subchan) struct demodulator_state_s *D; alevel_t alevel; - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); /* We have to consider two different cases here. */ diff --git a/src/demod_9600.c b/src/demod_9600.c index 705d1fa7..99432bfe 100644 --- a/src/demod_9600.c +++ b/src/demod_9600.c @@ -395,7 +395,7 @@ void demod_9600_process_sample (int chan, int sam, int upsample, struct demodula int subchan = 0; - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); /* Scale to nice number for convenience. */ @@ -611,7 +611,10 @@ inline static void nudge_pll (int chan, int subchan, int slice, float demod_out_ /* Overflow. Was large positive, wrapped around, now large negative. */ - hdlc_rec_bit (chan, subchan, slice, demod_out_f > 0, D->modem_type == MODEM_SCRAMBLE, D->slicer[slice].lfsr); + hdlc_rec_bit_new (chan, subchan, slice, demod_out_f > 0, D->modem_type == MODEM_SCRAMBLE, D->slicer[slice].lfsr, + &(D->slicer[slice].pll_nudge_total), &(D->slicer[slice].pll_symbol_count)); + D->slicer[slice].pll_symbol_count++; + pll_dcd_each_symbol2 (D, chan, subchan, slice); } @@ -627,12 +630,14 @@ inline static void nudge_pll (int chan, int subchan, int slice, float demod_out_ float target = D->pll_step_per_sample * demod_out_f / (demod_out_f - D->slicer[slice].prev_demod_out_f); + 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 + target * (1.0f - D->pll_locked_inertia) ); } else { D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_searching_inertia + target * (1.0f - D->pll_searching_inertia) ); } + D->slicer[slice].pll_nudge_total += (int64_t)((signed int)(D->slicer[slice].data_clock_pll)) - (int64_t)before; } diff --git a/src/demod_afsk.c b/src/demod_afsk.c index b4d6c295..3e5d03ec 100644 --- a/src/demod_afsk.c +++ b/src/demod_afsk.c @@ -609,7 +609,7 @@ void demod_afsk_process_sample (int chan, int subchan, int sam, struct demodulat static int seq = 0; /* for log file name */ #endif - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); /* diff --git a/src/demod_psk.c b/src/demod_psk.c index bc058185..3d06c915 100644 --- a/src/demod_psk.c +++ b/src/demod_psk.c @@ -72,7 +72,10 @@ * V.26 has two variations, A and B. Initially I implemented the A alternative. * It later turned out that the MFJ-2400 used the B alternative. In version 1.6 you have a * choice between compatibility with MFJ (and probably the others) or the original implementation. - * + * The B alternative works a little more reliably, perhaps because there is never a + * zero phase difference between adjacent symbols. + * Eventually the A alternative might disappear to reduce confusion. + * *---------------------------------------------------------------*/ #include "direwolf.h" @@ -94,7 +97,7 @@ #include "fsk_demod_state.h" // Values above override defaults. #include "audio.h" -#include "tune.h" +//#include "tune.h" // obsolete. eventually remove all references. #include "fsk_gen_filter.h" #include "hdlc_rec.h" #include "textcolor.h" @@ -102,7 +105,13 @@ #include "dsp.h" - +#define TUNE(envvar,param,name,fmt) { \ + char *e = getenv(envvar); \ + if (e != NULL) { \ + param = atof(e); \ + text_color_set (DW_COLOR_ERROR); \ + dw_printf ("TUNE: " name " = " fmt "\n", param); \ + } } static const int phase_to_gray_v26[4] = {0, 1, 3, 2}; @@ -202,9 +211,10 @@ void demod_psk_init (enum modem_t modem_type, enum v26_e v26_alt, int samples_pe D->num_slicers = 1; // Haven't thought about this yet. Is it even applicable? -#ifdef TUNE_PROFILE - profile = TUNE_PROFILE; -#endif +//#ifdef TUNE_PROFILE +// profile = TUNE_PROFILE; +//#endif + TUNE("TUNE_PROFILE", profile, "profile", "%c") if (modem_type == MODEM_QPSK) { @@ -290,9 +300,16 @@ void demod_psk_init (enum modem_t modem_type, enum v26_e v26_alt, int samples_pe D->u.psk.delay_line_width_sym = 1.25; // Delay line > 13/12 * symbol period +// JWL experiment 11-7. Should delay be based on audio freq rather than baud? +#if 0 // experiment made things much worse. 55 went down to 21. + D->u.psk.coffs = (int) round( (11.f / 12.f) * (float)samples_per_sec / (float)carrier_freq ); + D->u.psk.boffs = (int) round( (float)samples_per_sec / (float)carrier_freq ); + D->u.psk.soffs = (int) round( (13.f / 12.f) * (float)samples_per_sec / (float)carrier_freq ); +#else D->u.psk.coffs = (int) round( (11.f / 12.f) * (float)samples_per_sec / (float)correct_baud ); D->u.psk.boffs = (int) round( (float)samples_per_sec / (float)correct_baud ); D->u.psk.soffs = (int) round( (13.f / 12.f) * (float)samples_per_sec / (float)correct_baud ); +#endif } else { @@ -393,26 +410,40 @@ void demod_psk_init (enum modem_t modem_type, enum v26_e v26_alt, int samples_pe } } -#ifdef TUNE_PRE_BAUD - D->u.psk.prefilter_baud = TUNE_PRE_BAUD; -#endif -#ifdef TUNE_PRE_WINDOW - D->u.psk.pre_window = TUNE_PRE_WINDOW; -#endif +//#ifdef TUNE_PRE_BAUD +// D->u.psk.prefilter_baud = TUNE_PRE_BAUD; +//#endif + TUNE("TUNE_PRE_BAUD", D->u.psk.prefilter_baud, "prefilter_baud", "%.3f") -#ifdef TUNE_LPF_BAUD - D->u.psk.lpf_baud = TUNE_LPF_BAUD; -#endif -#ifdef TUNE_LP_WINDOW - D->u.psk.lp_window = TUNE_LP_WINDOW; -#endif +//#ifdef TUNE_PRE_WINDOW +// D->u.psk.pre_window = TUNE_PRE_WINDOW; +//#endif + TUNE("TUNE_PRE_WINDOW", D->u.psk.pre_window, "pre_window", "%d") + +//#ifdef TUNE_LPF_BAUD +// D->u.psk.lpf_baud = TUNE_LPF_BAUD; +//#endif +//#ifdef TUNE_LP_WINDOW +// D->u.psk.lp_window = TUNE_LP_WINDOW; +//#endif + TUNE("TUNE_LPF_BAUD", D->u.psk.lpf_baud, "lpf_baud", "%.3f") + TUNE("TUNE_LP_WINDOW", D->u.psk.lp_window, "lp_window", "%d") -#if defined(TUNE_PLL_SEARCHING) - D->pll_searching_inertia = TUNE_PLL_SEARCHING; -#endif -#if defined(TUNE_PLL_LOCKED) - D->pll_locked_inertia = TUNE_PLL_LOCKED; -#endif + + TUNE("TUNE_LP_FILTER_WIDTH_SYM", D->u.psk.lp_filter_width_sym, "lp_filter_width_sym", "%.3f") + + + + + +//#if defined(TUNE_PLL_SEARCHING) +// D->pll_searching_inertia = TUNE_PLL_SEARCHING; +//#endif +//#if defined(TUNE_PLL_LOCKED) +// D->pll_locked_inertia = TUNE_PLL_LOCKED; +//#endif + TUNE("TUNE_PLL_LOCKED", D->pll_locked_inertia, "pll_locked_inertia", "%.2f") + TUNE("TUNE_PLL_SEARCHING", D->pll_searching_inertia, "pll_searching_inertia", "%.2f") /* @@ -427,17 +458,24 @@ void demod_psk_init (enum modem_t modem_type, enum v26_e v26_alt, int samples_pe */ D->u.psk.pre_filter_taps = (int) round( D->u.psk.pre_filter_width_sym * (float)samples_per_sec / (float)correct_baud ); + +// JWL experiment 11/7 - Should delay line be based on audio frequency? + D->u.psk.delay_line_taps = (int) round( D->u.psk.delay_line_width_sym * (float)samples_per_sec / (float)correct_baud ); D->u.psk.delay_line_taps = (int) round( D->u.psk.delay_line_width_sym * (float)samples_per_sec / (float)correct_baud ); + + D->u.psk.lp_filter_taps = (int) round( D->u.psk.lp_filter_width_sym * (float)samples_per_sec / (float)correct_baud ); -#ifdef TUNE_PRE_FILTER_TAPS - D->u.psk.pre_filter_taps = TUNE_PRE_FILTER_TAPS; -#endif +//#ifdef TUNE_PRE_FILTER_TAPS +// D->u.psk.pre_filter_taps = TUNE_PRE_FILTER_TAPS; +//#endif + TUNE("TUNE_PRE_FILTER_TAPS", D->u.psk.pre_filter_taps, "pre_filter_taps", "%d") -#ifdef TUNE_lp_filter_taps - D->u.psk.lp_filter_taps = TUNE_lp_filter_taps; -#endif +//#ifdef TUNE_lp_filter_taps +// D->u.psk.lp_filter_taps = TUNE_lp_filter_taps; +//#endif + TUNE("TUNE_LP_FILTER_TAPS", D->u.psk.lp_filter_taps, "lp_filter_taps (FIR)", "%d") if (D->u.psk.pre_filter_taps > MAX_FILTER_SIZE) { @@ -665,7 +703,7 @@ void demod_psk_process_sample (int chan, int subchan, int sam, struct demodulato { int slice = 0; // Would it make sense to have more than one? - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); /* Scale to nice number for plotting during debug. */ @@ -800,16 +838,22 @@ static void nudge_pll (int chan, int subchan, int slice, int demod_bits, struct int gray = demod_bits; - hdlc_rec_bit (chan, subchan, slice, (gray >> 1) & 1, 0, bit_quality[1]); - hdlc_rec_bit (chan, subchan, slice, gray & 1, 0, bit_quality[0]); + hdlc_rec_bit_new (chan, subchan, slice, (gray >> 1) & 1, 0, bit_quality[1], + &(D->slicer[slice].pll_nudge_total), &(D->slicer[slice].pll_symbol_count)); + hdlc_rec_bit_new (chan, subchan, slice, gray & 1, 0, bit_quality[0], + &(D->slicer[slice].pll_nudge_total), &(D->slicer[slice].pll_symbol_count)); } else { int gray = demod_bits; - hdlc_rec_bit (chan, subchan, slice, (gray >> 2) & 1, 0, bit_quality[2]); - hdlc_rec_bit (chan, subchan, slice, (gray >> 1) & 1, 0, bit_quality[1]); - hdlc_rec_bit (chan, subchan, slice, gray & 1, 0, bit_quality[0]); + hdlc_rec_bit_new (chan, subchan, slice, (gray >> 2) & 1, 0, bit_quality[2], + &(D->slicer[slice].pll_nudge_total), &(D->slicer[slice].pll_symbol_count)); + hdlc_rec_bit_new (chan, subchan, slice, (gray >> 1) & 1, 0, bit_quality[1], + &(D->slicer[slice].pll_nudge_total), &(D->slicer[slice].pll_symbol_count)); + hdlc_rec_bit_new (chan, subchan, slice, gray & 1, 0, bit_quality[0], + &(D->slicer[slice].pll_nudge_total), &(D->slicer[slice].pll_symbol_count)); } + D->slicer[slice].pll_symbol_count++; pll_dcd_each_symbol2 (D, chan, subchan, slice); } @@ -826,12 +870,14 @@ static void nudge_pll (int chan, int subchan, int slice, int demod_bits, struct pll_dcd_signal_transition2 (D, slice, D->slicer[slice].data_clock_pll); + 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)floorf((float)(D->slicer[slice].data_clock_pll) * D->pll_locked_inertia); } else { D->slicer[slice].data_clock_pll = (int)floorf((float)(D->slicer[slice].data_clock_pll) * D->pll_searching_inertia); } + D->slicer[slice].pll_nudge_total += (int64_t)((signed int)(D->slicer[slice].data_clock_pll)) - (int64_t)before; } /* diff --git a/src/deviceid.c b/src/deviceid.c new file mode 100644 index 00000000..49b9b346 --- /dev/null +++ b/src/deviceid.c @@ -0,0 +1,682 @@ +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 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 +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + + +/*------------------------------------------------------------------ + * + * File: deviceid.c + * + * Purpose: Determine the device identifier from the destination field, + * or from prefix/suffix for MIC-E format. + * + * Description: Orginally this used the tocalls.txt file and was part of decode_aprs.c. + * For release 1.8, we use tocalls.yaml and this is split into a separate file. + * + *------------------------------------------------------------------*/ + +//#define TEST 1 // Standalone test. $ gcc -DTEST deviceid.c && ./a.out + + +#if TEST +#define HAVE_STRLCPY 1 // prevent defining in direwolf.h +#define HAVE_STRLCAT 1 +#endif + +#include "direwolf.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> + +#include "deviceid.h" +#include "textcolor.h" + + +static void unquote (int line, char *pin, char *pout); +static int tocall_cmp (const void *px, const void *py); +static int mice_cmp (const void *px, const void *py); + +/*------------------------------------------------------------------ + * + * Function: main + * + * Purpose: A little self-test used during development. + * + * Description: Read the yaml file. Decipher a few typical values. + * + *------------------------------------------------------------------*/ + +#if TEST +// So we don't need to link with any other files. +#define dw_printf printf +void text_color_set(dw_color_t) { return; } +void strlcpy(char *dst, char *src, size_t dlen) { + strcpy (dst, src); +} +void strlcat(char *dst, char *src, size_t dlen) { + strcat (dst, src); +} + + +int main (int argc, char *argv[]) +{ + char device[80]; + char comment_out[80]; + + deviceid_init (); + + dw_printf ("\n"); + dw_printf ("Testing ...\n"); + +// MIC-E Legacy (really Kenwood). + + deviceid_decode_mice (">Comment", comment_out, sizeof(comment_out), device, sizeof(device)); + dw_printf ("%s %s\n", comment_out, device); + assert (strcmp(comment_out, "Comment") == 0); + assert (strcmp(device, "Kenwood TH-D7A") == 0); + + deviceid_decode_mice (">Comment^", comment_out, sizeof(comment_out), device, sizeof(device)); + dw_printf ("%s %s\n", comment_out, device); + assert (strcmp(comment_out, "Comment") == 0); + assert (strcmp(device, "Kenwood TH-D74") == 0); + + deviceid_decode_mice ("]Comment", comment_out, sizeof(comment_out), device, sizeof(device)); + dw_printf ("%s %s\n", comment_out, device); + assert (strcmp(comment_out, "Comment") == 0); + assert (strcmp(device, "Kenwood TM-D700") == 0); + + deviceid_decode_mice ("]Comment=", comment_out, sizeof(comment_out), device, sizeof(device)); + dw_printf ("%s %s\n", comment_out, device); + assert (strcmp(comment_out, "Comment") == 0); + assert (strcmp(device, "Kenwood TM-D710") == 0); + + deviceid_decode_mice ("]\"4V}=", comment_out, sizeof(comment_out), device, sizeof(device)); + dw_printf ("%s %s\n", comment_out, device); + assert (strcmp(comment_out, "\"4V}") == 0); + assert (strcmp(device, "Kenwood TM-D710") == 0); + + +// Modern MIC-E. + + deviceid_decode_mice ("`Comment_\"", comment_out, sizeof(comment_out), device, sizeof(device)); + dw_printf ("%s %s\n", comment_out, device); + assert (strcmp(comment_out, "Comment") == 0); + assert (strcmp(device, "Yaesu FTM-350") == 0); + + deviceid_decode_mice ("`Comment_ ", comment_out, sizeof(comment_out), device, sizeof(device)); + dw_printf ("%s %s\n", comment_out, device); + assert (strcmp(comment_out, "Comment") == 0); + assert (strcmp(device, "Yaesu VX-8") == 0); + + deviceid_decode_mice ("'Comment|3", comment_out, sizeof(comment_out), device, sizeof(device)); + dw_printf ("%s %s\n", comment_out, device); + assert (strcmp(comment_out, "Comment") == 0); + assert (strcmp(device, "Byonics TinyTrak3") == 0); + + deviceid_decode_mice ("Comment", comment_out, sizeof(comment_out), device, sizeof(device)); + dw_printf ("%s %s\n", comment_out, device); + assert (strcmp(comment_out, "Comment") == 0); + assert (strcmp(device, "UNKNOWN vendor/model") == 0); + + deviceid_decode_mice ("", comment_out, sizeof(comment_out), device, sizeof(device)); + dw_printf ("%s %s\n", comment_out, device); + assert (strcmp(comment_out, "") == 0); + assert (strcmp(device, "UNKNOWN vendor/model") == 0); + +// Tocall + + deviceid_decode_dest ("APDW18", device, sizeof(device)); + dw_printf ("%s\n", device); + assert (strcmp(device, "WB2OSZ DireWolf") == 0); + + deviceid_decode_dest ("APD123", device, sizeof(device)); + dw_printf ("%s\n", device); + assert (strcmp(device, "Open Source aprsd") == 0); + + // null for Vendor. + deviceid_decode_dest ("APAX", device, sizeof(device)); + dw_printf ("%s\n", device); + assert (strcmp(device, "AFilterX") == 0); + + deviceid_decode_dest ("APA123", device, sizeof(device)); + dw_printf ("%s\n", device); + assert (strcmp(device, "UNKNOWN vendor/model") == 0); + + dw_printf ("\n"); + dw_printf ("Success!\n"); + + exit (EXIT_SUCCESS); +} + +#endif // TEST + + + +// Structures to hold mapping from encoded form to vendor and model. +// The .yaml file has two separate sections for MIC-E but they can +// both be handled as a single more general case. + +struct mice { + char prefix[4]; // The legacy form has 1 prefix character. + // The newer form has none. (more accurately ` or ') + char suffix[4]; // The legacy form has 0 or 1. + // The newer form has 2. + char *vendor; + char *model; +}; + +struct tocalls { + char tocall[8]; // Up to 6 characters. Some may have wildcards at the end. + // Most often they are trailing "??" or "?" or "???" in one case. + // Sometimes there is trailing "nnn". Does that imply digits only? + // Sometimes we see a trailing "*". Is "*" different than "?"? + // There are a couple bizzare cases like APnnnD which can + // create an ambigious situation. APMPAD, APRFGD, APY0[125]D. + // Screw them if they can't follow the rules. I'm not putting in a special case. + char *vendor; + char *model; +}; + + +static struct mice *pmice = NULL; // Pointer to array. +static int mice_count = 0; // Number of allocated elements. +static int mice_index = -1; // Current index for filling in. + +static struct tocalls *ptocalls = NULL; // Pointer to array. +static int tocalls_count = 0; // Number of allocated elements. +static int tocalls_index = -1; // Current index for filling in. + + + + +/*------------------------------------------------------------------ + * + * Function: deviceid_init + * + * Purpose: Called once at startup to read the tocalls.yaml file which was obtained from + * https://github.com/aprsorg/aprs-deviceid . + * + * Inputs: tocalls.yaml with OS specific directory search list. + * + * Outputs: static variables listed above. + * + * Description: For maximum flexibility, we will read the + * data file at run time rather than compiling it in. + * + *------------------------------------------------------------------*/ + +// Make sure the array is null terminated. +// If search order is changed, do the same in symbols.c for consistency. +// fopen is perfectly happy with / in file path when running on Windows. + +static const char *search_locations[] = { + (const char *) "tocalls.yaml", // Current working directory + (const char *) "data/tocalls.yaml", // Windows with CMake + (const char *) "../data/tocalls.yaml", // Source tree +#ifndef __WIN32__ + (const char *) "/usr/local/share/direwolf/tocalls.yaml", + (const char *) "/usr/share/direwolf/tocalls.yaml", +#endif +#if __APPLE__ + // https://groups.yahoo.com/neo/groups/direwolf_packet/conversations/messages/2458 + // Adding the /opt/local tree since macports typically installs there. Users might want their + // INSTALLDIR (see Makefile.macosx) to mirror that. If so, then we need to search the /opt/local + // path as well. + (const char *) "/opt/local/share/direwolf/tocalls.yaml", +#endif + (const char *) NULL // Important - Indicates end of list. +}; + + +void deviceid_init(void) +{ + FILE *fp = NULL; + for (int n = 0; search_locations[n] != NULL && fp == NULL; n++) { +#if TEST + text_color_set(DW_COLOR_INFO); + dw_printf ("Trying %s\n", search_locations[n]); +#endif + fp = fopen(search_locations[n], "r"); +#if TEST + if (fp != NULL) { + dw_printf ("Opened %s\n", search_locations[n]); + } +#endif + }; + + if (fp == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Could not open any of these file locations:\n"); + for (int n = 0; search_locations[n] != NULL; n++) { + dw_printf (" %s\n", search_locations[n]); + } + dw_printf("It won't be possible to extract device identifiers from packets.\n"); + return; + }; + +// Read file first time to get number of items. +// Allocate required space. +// Rewind. +// Read file second time to gather data. + + enum { no_section, mice_section, tocalls_section} section = no_section; + char stuff[80]; + + for (int pass = 1; pass <=2; pass++) { + int line = 0; // Line number within file. + + while (fgets(stuff, sizeof(stuff), fp)) { + line++; + + // Remove trailing CR/LF or spaces. + char *p = stuff + strlen(stuff) - 1; + while (p >= (char*)stuff && (*p == '\r' || *p == '\n' || *p == ' ')) { + *p-- = '\0'; + } + + // Ignore comment lines. + if (stuff[0] == '#') { + continue; + } + +#if TEST + //dw_printf ("%d: %s\n", line, stuff); +#endif + // This is not very robust; everything better be in exactly the right format. + + if (strncmp(stuff, "mice:", strlen("mice:")) == 0) { + section = mice_section; +#if TEST + dw_printf ("Pass %d, line %d, MIC-E section\n", pass, line); +#endif + } + else if (strncmp(stuff, "micelegacy:", strlen("micelegacy:")) == 0) { + section = mice_section; // treat both same. +#if TEST + dw_printf ("Pass %d, line %d, Legacy MIC-E section\n", pass, line); +#endif + } + else if (strncmp(stuff, "tocalls:", strlen("tocalls:")) == 0) { + section = tocalls_section; +#if TEST + dw_printf ("Pass %d, line %d, TOCALLS section\n", pass, line); +#endif + } + + // The first property of an item is preceded by " - ". + + if (pass == 1 && strncmp(stuff, " - ", 3) == 0) { + switch (section) { + case no_section: break; + case mice_section: mice_count++; break; + case tocalls_section: tocalls_count++; break; + } + } + + if (pass == 2) { + switch (section) { + case no_section: + break; + + case mice_section: + if (strncmp(stuff, " - ", 3) == 0) { + mice_index++; + assert (mice_index >= 0 && mice_index < mice_count); + } + if (strncmp(stuff+3, "prefix: ", strlen("prefix: ")) == 0) { + unquote (line, stuff+3+8, pmice[mice_index].prefix); + } + else if (strncmp(stuff+3, "suffix: ", strlen("suffix: ")) == 0) { + unquote (line, stuff+3+8, pmice[mice_index].suffix); + } + else if (strncmp(stuff+3, "vendor: ", strlen("vendor: ")) == 0) { + pmice[mice_index].vendor = strdup(stuff+3+8); + } + else if (strncmp(stuff+3, "model: ", strlen("model: ")) == 0) { + pmice[mice_index].model = strdup(stuff+3+7); + } + break; + + case tocalls_section: + if (strncmp(stuff, " - ", 3) == 0) { + tocalls_index++; + assert (tocalls_index >= 0 && tocalls_index < tocalls_count); + } + if (strncmp(stuff+3, "tocall: ", strlen("tocall: ")) == 0) { + // Remove trailing wildcard characters ? * n + char *r = stuff + strlen(stuff) - 1; + while (r >= (char*)stuff && (*r == '?' || *r == '*' || *r == 'n')) { + *r-- = '\0'; + } + + strlcpy (ptocalls[tocalls_index].tocall, stuff+3+8, sizeof(ptocalls[tocalls_index].tocall)); + + // Remove trailing CR/LF or spaces. + char *p = stuff + strlen(stuff) - 1; + while (p >= (char*)stuff && (*p == '\r' || *p == '\n' || *p == ' ')) { + *p-- = '\0'; + } + } + else if (strncmp(stuff+3, "vendor: ", strlen("vendor: ")) == 0) { + ptocalls[tocalls_index].vendor = strdup(stuff+3+8); + } + else if (strncmp(stuff+3, "model: ", strlen("model: ")) == 0) { + ptocalls[tocalls_index].model = strdup(stuff+3+7); + } + break; + } + } + } // while(fgets + + if (pass == 1) { +#if TEST + dw_printf ("deviceid sizes %d %d\n", mice_count, tocalls_count); +#endif + pmice = calloc(sizeof(struct mice), mice_count); + ptocalls = calloc(sizeof(struct tocalls), tocalls_count); + + rewind (fp); + section = no_section; + } + } // for pass = 1 or 2 + + fclose (fp); + + assert (mice_index == mice_count - 1); + assert (tocalls_index == tocalls_count - 1); + + +// MIC-E Legacy needs to be sorted so those with suffix come first. + + qsort (pmice, mice_count, sizeof(struct mice), mice_cmp); + + +// Sort tocalls by decreasing length so the search will go from most specific to least specific. +// Example: APY350 or APY008 would match those specific models before getting to the more generic APY. + + qsort (ptocalls, tocalls_count, sizeof(struct tocalls), tocall_cmp); + + +#if TEST + dw_printf ("MIC-E:\n"); + for (int i = 0; i < mice_count; i++) { + dw_printf ("%s %s %s\n", pmice[i].suffix, pmice[i].vendor, pmice[i].model); + } + dw_printf ("TOCALLS:\n"); + for (int i = 0; i < tocalls_count; i++) { + dw_printf ("%s %s %s\n", ptocalls[i].tocall, ptocalls[i].vendor, ptocalls[i].model); + } +#endif + + return; + +} // end deviceid_init + + +/*------------------------------------------------------------------ + * + * Function: unquote + * + * Purpose: Remove surrounding quotes and undo any escapes. + * + * Inputs: line - File line number for error message. + * + * in - String with quotes. Might contain \ escapes. + * + * Outputs: out - Quotes and escapes removed. + * Limited to 2 characters to avoid buffer overflow. + * + * Examples: in out + * "_#" _# + * "_\"" _" + * "=" = + * + *------------------------------------------------------------------*/ + +static void unquote (int line, char *pin, char *pout) +{ + int count = 0; + + *pout = '\0'; + if (*pin != '"') { + text_color_set(DW_COLOR_ERROR); + dw_printf("Missing leading \" for %s on line %d.\n", pin, line); + return; + } + + pin++; + while (*pin != '\0' && *pin != '\"' && count < 2) { + if (*pin == '\\') { + pin++; + } + *pout++ = *pin++; + count++; + } + *pout = '\0'; + + if (*pin != '"') { + text_color_set(DW_COLOR_ERROR); + dw_printf("Missing trailing \" or string too long on line %d.\n", line); + return; + } +} + +// Used to sort the tocalls by length. +// When length is equal, alphabetically. + +static int tocall_cmp (const void *px, const void *py) +{ + const struct tocalls *x = (struct tocalls *)px; + const struct tocalls *y = (struct tocalls *)py; + + if (strlen(x->tocall) != strlen(y->tocall)) { + return (strlen(y->tocall) - strlen(x->tocall)); + } + return (strcmp(x->tocall, y->tocall)); +} + +// Used to sort the suffixes by length. +// Longer at the top. +// Example check for >xxx^ before >xxx . + +static int mice_cmp (const void *px, const void *py) +{ + const struct mice *x = (struct mice *)px; + const struct mice *y = (struct mice *)py; + + return (strlen(y->suffix) - strlen(x->suffix)); +} + + + + + +/*------------------------------------------------------------------ + * + * Function: deviceid_decode_dest + * + * Purpose: Find vendor/model for destination address of form APxxxx. + * + * Inputs: dest - Destination address. No SSID. + * + * device_size - Amount of space available for result to avoid buffer overflow. + * + * Outputs: device - Vendor and model. + * + * Description: With the exception of MIC-E format, we expect to find the vendor/model in the + * AX.25 destination field. The form should be APxxxx. + * + * Search the list looking for the maximum length match. + * For example, + * APXR = Xrouter + * APX = Xastir + * + *------------------------------------------------------------------*/ + +void deviceid_decode_dest (char *dest, char *device, size_t device_size) +{ + strlcpy (device, "UNKNOWN vendor/model", device_size); + + if (ptocalls == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf("deviceid_decode_dest called without any deviceid data.\n"); + return; + } + + for (int n = 0; n < tocalls_count; n++) { + if (strncmp(dest, ptocalls[n].tocall, strlen(ptocalls[n].tocall)) == 0) { + + if (ptocalls[n].vendor != NULL) { + strlcpy (device, ptocalls[n].vendor, device_size); + } + + if (ptocalls[n].vendor != NULL && ptocalls[n].model != NULL) { + strlcat (device, " ", device_size); + } + + if (ptocalls[n].model != NULL) { + strlcat (device, ptocalls[n].model, device_size); + } + return; + } + } + +// Not found in table. + strlcpy (device, "UNKNOWN vendor/model", device_size); + +} // end deviceid_decode_dest + + +/*------------------------------------------------------------------ + * + * Function: deviceid_decode_mice + * + * Purpose: Find vendor/model for MIC-E comment. + * + * Inputs: comment - MIC-E comment that might have vendor/model encoded as + * a prefix and/or suffix. + * Any trailing CR has already been removed. + * + * trimmed_size - Amount of space available for result to avoid buffer overflow. + * + * device_size - Amount of space available for result to avoid buffer overflow. + * + * Outputs: trimmed - Final comment with device vendor/model removed. + * This would include any altitude. + * + * device - Vendor and model. + * + * Description: MIC-E device identification has a tortured history. + * + * The Kenwood TH-D7A put ">" at the beginning of the comment. + * The Kenwood TM-D700 put "]" at the beginning of the comment. + * Later Kenwood models also added a single suffix character + * using a character very unlikely to appear at the end of a comment. + * + * The later convention, used by everyone else, is to have a prefix of ` or ' + * and a suffix of two characters. The suffix characters need to be + * something very unlikely to be found at the end of a comment. + * + * A receiving device is expected to remove those extra characters + * before displaying the comment. + * + * References: http://www.aprs.org/aprs12/mic-e-types.txt + * http://www.aprs.org/aprs12/mic-e-examples.txt + * https://github.com/wb2osz/aprsspec containing: + * APRS Protocol Specification 1.2 + * Understanding APRS Packets + *------------------------------------------------------------------*/ + +// The strncmp documentation doesn't mention behavior if length is zero. +// Do our own just to be safe. + +static inline int strncmp_z (char *a, char *b, size_t len) +{ + int result = 0; + if (len > 0) { + result = strncmp(a, b, len); + } + //dw_printf ("Comparing '%s' and '%s' len %d result %d\n", a, b, len, result); + return result; +} + +void deviceid_decode_mice (char *comment, char *trimmed, size_t trimmed_size, char *device, size_t device_size) +{ + strlcpy (device, "UNKNOWN vendor/model", device_size); + strlcpy (trimmed, comment, trimmed_size); + if (strlen(comment) < 1) { + return; + } + + if (ptocalls == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf("deviceid_decode_mice called without any deviceid data.\n"); + return; + } + + +// The Legacy format has an explicit prefix in the table. +// For others, it must be ` or ' to indicate whether messaging capable. + + for (int n = 0; n < mice_count; n++) { + if ((strlen(pmice[n].prefix) != 0 && // Legacy + strncmp_z(comment, // prefix from table + pmice[n].prefix, + strlen(pmice[n].prefix)) == 0 && + strncmp_z(comment + strlen(comment) - strlen(pmice[n].suffix), // possible suffix + pmice[n].suffix, + strlen(pmice[n].suffix)) == 0) || + + (strlen(pmice[n].prefix) == 0 && // Later + (comment[0] == '`' || comment[0] == '\'') && // prefix ` or ' + strncmp_z(comment + strlen(comment) - strlen(pmice[n].suffix), // suffix + pmice[n].suffix, + strlen(pmice[n].suffix)) == 0) ) { + + if (pmice[n].vendor != NULL) { + strlcpy (device, pmice[n].vendor, device_size); + } + + if (pmice[n].vendor != NULL && pmice[n].model != NULL) { + strlcat (device, " ", device_size); + } + + if (pmice[n].model != NULL) { + strlcat (device, pmice[n].model, device_size); + } + + // Remove any prefix/suffix and return what remains. + + strlcpy (trimmed, comment + 1, trimmed_size); + trimmed[strlen(comment) - 1 - strlen(pmice[n].suffix)] = '\0'; + + return; + } + } + + +// Not found. + + strlcpy (device, "UNKNOWN vendor/model", device_size); + strlcpy (trimmed, comment, trimmed_size); + +} // end deviceid_decode_mice + +// end deviceid.c diff --git a/src/deviceid.h b/src/deviceid.h new file mode 100644 index 00000000..d7a1b30e --- /dev/null +++ b/src/deviceid.h @@ -0,0 +1,6 @@ + +// deviceid.h + +void deviceid_init(void); +void deviceid_decode_dest (char *dest, char *device, size_t device_size); +void deviceid_decode_mice (char *comment, char *trimmed, size_t trimmed_size, char *device, size_t device_size); diff --git a/src/digipeater.c b/src/digipeater.c index fbe89370..fcf59568 100644 --- a/src/digipeater.c +++ b/src/digipeater.c @@ -91,7 +91,7 @@ static struct digi_config_s *save_digi_config_p; * Maintain count of packets digipeated for each combination of from/to channel. */ -static int digi_count[MAX_CHANS][MAX_CHANS]; +static int digi_count[MAX_TOTAL_CHANS][MAX_TOTAL_CHANS]; int digipeater_get_count (int from_chan, int to_chan) { return (digi_count[from_chan][to_chan]); @@ -154,7 +154,7 @@ void digipeater (int from_chan, packet_t pp) // Network TNC is OK for UI frames where we don't care about timing. - if ( from_chan < 0 || from_chan >= MAX_CHANS || + if ( from_chan < 0 || from_chan >= MAX_TOTAL_CHANS || (save_audio_config_p->chan_medium[from_chan] != MEDIUM_RADIO && save_audio_config_p->chan_medium[from_chan] != MEDIUM_NETTNC)) { text_color_set(DW_COLOR_ERROR); @@ -195,14 +195,14 @@ void digipeater (int from_chan, packet_t pp) * */ - for (to_chan=0; to_chan<MAX_CHANS; to_chan++) { + for (to_chan=0; to_chan<MAX_TOTAL_CHANS; to_chan++) { if (save_digi_config_p->enabled[from_chan][to_chan]) { if (to_chan == from_chan) { packet_t result; - result = digipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall, - save_audio_config_p->achan[to_chan].mycall, - &save_digi_config_p->alias[from_chan][to_chan], &save_digi_config_p->wide[from_chan][to_chan], + result = digipeat_match (from_chan, pp, save_audio_config_p->mycall[from_chan], + save_audio_config_p->mycall[to_chan], + &save_digi_config_p->alias[from_chan][to_chan], &save_digi_config_p->wide[from_chan][to_chan], to_chan, save_digi_config_p->preempt[from_chan][to_chan], save_digi_config_p->atgp[from_chan][to_chan], save_digi_config_p->filter_str[from_chan][to_chan]); @@ -222,14 +222,14 @@ void digipeater (int from_chan, packet_t pp) * These are lower priority */ - for (to_chan=0; to_chan<MAX_CHANS; to_chan++) { + for (to_chan=0; to_chan<MAX_TOTAL_CHANS; to_chan++) { if (save_digi_config_p->enabled[from_chan][to_chan]) { if (to_chan != from_chan) { packet_t result; - result = digipeat_match (from_chan, pp, save_audio_config_p->achan[from_chan].mycall, - save_audio_config_p->achan[to_chan].mycall, - &save_digi_config_p->alias[from_chan][to_chan], &save_digi_config_p->wide[from_chan][to_chan], + result = digipeat_match (from_chan, pp, save_audio_config_p->mycall[from_chan], + save_audio_config_p->mycall[to_chan], + &save_digi_config_p->alias[from_chan][to_chan], &save_digi_config_p->wide[from_chan][to_chan], to_chan, save_digi_config_p->preempt[from_chan][to_chan], save_digi_config_p->atgp[from_chan][to_chan], save_digi_config_p->filter_str[from_chan][to_chan]); @@ -641,9 +641,9 @@ void digi_regen (int from_chan, packet_t pp) // dw_printf ("digi_regen()\n"); - assert (from_chan >= 0 && from_chan < MAX_CHANS); + assert (from_chan >= 0 && from_chan < MAX_TOTAL_CHANS); - for (to_chan=0; to_chan<MAX_CHANS; to_chan++) { + for (to_chan=0; to_chan<MAX_TOTAL_CHANS; to_chan++) { if (save_digi_config_p->regen[from_chan][to_chan]) { result = ax25_dup (pp); if (result != NULL) { diff --git a/src/digipeater.h b/src/digipeater.h index 5c849769..46d955da 100644 --- a/src/digipeater.h +++ b/src/digipeater.h @@ -4,7 +4,7 @@ #include "regex.h" -#include "direwolf.h" /* for MAX_CHANS */ +#include "direwolf.h" /* for MAX_TOTAL_CHANS */ #include "ax25_pad.h" /* for packet_t */ #include "audio.h" /* for radio channel properties */ @@ -29,25 +29,25 @@ struct digi_config_s { * Rules for each of the [from_chan][to_chan] combinations. */ - regex_t alias[MAX_CHANS][MAX_CHANS]; + regex_t alias[MAX_TOTAL_CHANS][MAX_TOTAL_CHANS]; - regex_t wide[MAX_CHANS][MAX_CHANS]; + regex_t wide[MAX_TOTAL_CHANS][MAX_TOTAL_CHANS]; - int enabled[MAX_CHANS][MAX_CHANS]; + int enabled[MAX_TOTAL_CHANS][MAX_TOTAL_CHANS]; - enum preempt_e { PREEMPT_OFF, PREEMPT_DROP, PREEMPT_MARK, PREEMPT_TRACE } preempt[MAX_CHANS][MAX_CHANS]; + enum preempt_e { PREEMPT_OFF, PREEMPT_DROP, PREEMPT_MARK, PREEMPT_TRACE } preempt[MAX_TOTAL_CHANS][MAX_TOTAL_CHANS]; // ATGP is an ugly hack for the specific need of ATGP which needs more that 8 digipeaters. // DO NOT put this in the User Guide. On a need to know basis. - char atgp[MAX_CHANS][MAX_CHANS][AX25_MAX_ADDR_LEN]; + char atgp[MAX_TOTAL_CHANS][MAX_TOTAL_CHANS][AX25_MAX_ADDR_LEN]; - char *filter_str[MAX_CHANS+1][MAX_CHANS+1]; + char *filter_str[MAX_TOTAL_CHANS+1][MAX_TOTAL_CHANS+1]; // NULL or optional Packet Filter strings such as "t/m". // Notice the size of arrays is one larger than normal. // That extra position is for the IGate. - int regen[MAX_CHANS][MAX_CHANS]; // Regenerate packet. + int regen[MAX_TOTAL_CHANS][MAX_TOTAL_CHANS]; // Regenerate packet. // Sort of like digipeating but passed along unchanged. }; diff --git a/src/direwolf.c b/src/direwolf.c index e23aecb4..d98cf04d 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, 2023 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019, 2020, 2021, 2023, 2024 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 @@ -129,6 +129,8 @@ #include "dwsock.h" #include "dns_sd_dw.h" #include "dlq.h" // for fec_type_t definition. +#include "deviceid.h" +#include "nettnc.h" //static int idx_decoded = 0; @@ -186,7 +188,7 @@ static int d_u_opt = 0; /* "-d u" command line option to print UTF-8 also in h static int d_p_opt = 0; /* "-d p" option for dumping packets over radio. */ static int q_h_opt = 0; /* "-q h" Quiet, suppress the "heard" line with audio level. */ -static int q_d_opt = 0; /* "-q d" Quiet, suppress the printing of decoded of APRS packets. */ +static int q_d_opt = 0; /* "-q d" Quiet, suppress the printing of description of APRS packets. */ static int A_opt_ais_to_obj = 0; /* "-A" Convert received AIS to APRS "Object Report." */ @@ -227,6 +229,7 @@ int main (int argc, char *argv[]) #endif int d_x_opt = 1; /* "-d x" option for FX.25. Default minimal. Repeat for more detail. -qx to silence. */ int d_2_opt = 0; /* "-d 2" option for IL2P. Default minimal. Repeat for more detail. */ + int d_c_opt = 0; /* "-d c" option for connected mode data link state machine. */ int aprstt_debug = 0; /* "-d d" option for APRStt (think Dtmf) debug. */ @@ -302,24 +305,27 @@ int main (int argc, char *argv[]) text_color_init(t_opt); text_color_set(DW_COLOR_INFO); //dw_printf ("Dire Wolf version %d.%d (%s) BETA TEST 7\n", MAJOR_VERSION, MINOR_VERSION, __DATE__); - //dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "G", __DATE__); - dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION); + dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "D", __DATE__); + //dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION); -#if defined(ENABLE_GPSD) || defined(USE_HAMLIB) || defined(USE_CM108) || USE_AVAHI_CLIENT || USE_MACOS_DNSSD +#if defined(ENABLE_GPSD) || defined(USE_HAMLIB) || defined(USE_CM108) || USE_AVAHI_CLIENT || USE_MACOS_DNSSD || USE_GPIOD dw_printf ("Includes optional support for: "); -#if defined(ENABLE_GPSD) + #if defined(ENABLE_GPSD) dw_printf (" gpsd"); -#endif -#if defined(USE_HAMLIB) + #endif + #if defined(USE_HAMLIB) dw_printf (" hamlib"); -#endif -#if defined(USE_CM108) + #endif + #if defined(USE_CM108) dw_printf (" cm108-ptt"); -#endif -#if (USE_AVAHI_CLIENT|USE_MACOS_DNSSD) + #endif + #if defined(USE_GPIOD) + dw_printf (" libgpiod"); + #endif + #if (USE_AVAHI_CLIENT|USE_MACOS_DNSSD) dw_printf (" dns-sd"); -#endif + #endif dw_printf ("\n"); #endif @@ -333,6 +339,13 @@ int main (int argc, char *argv[]) #endif +// TODO: Display hardware and OS version to help with troubleshooting. +// cat /proc/cpuinfo | grep ^Model +// BSD, Deb?: /etc/os-release +// /etc/issue + + + /* * Starting with version 0.9, the prebuilt Windows version * requires a minimum of a Pentium 3 or equivalent so we can @@ -386,6 +399,7 @@ int main (int argc, char *argv[]) text_color_set(DW_COLOR_ERROR); for (int n=0; n<15; n++) { dw_printf ("\n"); + dw_printf ("Why are you running this as root user?.\n"); dw_printf ("Dire Wolf requires only privileges available to ordinary users.\n"); dw_printf ("Running this as root is an unnecessary security risk.\n"); //SLEEP_SEC(1); @@ -554,7 +568,7 @@ int main (int argc, char *argv[]) break; } } - if (x_opt_chan < 0 || x_opt_chan >= MAX_CHANS) { + if (x_opt_chan < 0 || x_opt_chan >= MAX_RADIO_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid channel %d for -x. \n", x_opt_chan); text_color_set(DW_COLOR_INFO); @@ -633,6 +647,7 @@ int main (int argc, char *argv[]) #if USE_HAMLIB case 'h': d_h_opt++; break; // Hamlib verbose level. #endif + case 'c': d_c_opt++; break; // Connected mode data link state machine case 'x': d_x_opt++; break; // FX.25 case '2': d_2_opt++; break; // IL2P case 'd': aprstt_debug++; break; // APRStt (mnemonic Dtmf) @@ -982,6 +997,7 @@ int main (int argc, char *argv[]) * Files not supported at this time. * Can always "cat" the file and pipe it into stdin. */ + deviceid_init(); err = audio_open (&audio_config); if (err < 0) { @@ -999,6 +1015,13 @@ int main (int argc, char *argv[]) fx25_init (d_x_opt); il2p_init (d_2_opt); +/* + * New in 1.8 - Allow a channel to be mapped to a network TNC rather than + * an internal modem and radio. + * I put it here so channel properties would come out in right order. + */ + nettnc_init (&audio_config); + /* * Initialize the touch tone decoder & APRStt gateway. */ @@ -1103,7 +1126,7 @@ int main (int argc, char *argv[]) igate_init (&audio_config, &igate_config, &digi_config, d_i_opt); cdigipeater_init (&audio_config, &cdigi_config); pfilter_init (&igate_config, d_f_opt); - ax25_link_init (&misc_config); + ax25_link_init (&misc_config, d_c_opt); /* * Provide the AGW & KISS socket interfaces for use by a client application. @@ -1162,7 +1185,10 @@ int main (int argc, char *argv[]) * * Inputs: chan - Audio channel number, 0 or 1. * subchan - Which modem caught it. - * Special case -1 for DTMF decoder. + * Special cases: + * -1 for DTMF decoder. + * -2 for channel mapped to APRS-IS. + * -3 for channel mapped to network TNC. * slice - Slicer which caught it. * pp - Packet handle. * alevel - Audio level, range of 0 - 100. @@ -1193,7 +1219,7 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev // Can indicate FX.25/IL2P or fix_bits. assert (chan >= 0 && chan < MAX_TOTAL_CHANS); // TOTAL for virtual channels - assert (subchan >= -2 && subchan < MAX_SUBCHANS); + assert (subchan >= -3 && subchan < MAX_SUBCHANS); assert (slice >= 0 && slice < MAX_SLICERS); assert (pp != NULL); // 1.1J+ @@ -1274,7 +1300,13 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev ax25_get_addr_with_ssid(pp, h-1, probably_really); - dw_printf ("%s (probably %s) audio level = %s %s %s\n", heard, probably_really, alevel_text, display_retries, spectrum); + // audio level applies only for internal modem channels. + if (subchan >=0) { + dw_printf ("%s (probably %s) audio level = %s %s %s\n", heard, probably_really, alevel_text, display_retries, spectrum); + } + else { + dw_printf ("%s (probably %s)\n", heard, probably_really); + } } else if (strcmp(heard, "DTMF") == 0) { @@ -1283,7 +1315,13 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev } else { - dw_printf ("%s audio level = %s %s %s\n", heard, alevel_text, display_retries, spectrum); + // audio level applies only for internal modem channels. + if (subchan >= 0) { + dw_printf ("%s audio level = %s %s %s\n", heard, alevel_text, display_retries, spectrum); + } + else { + dw_printf ("%s\n", heard); + } } } } @@ -1297,10 +1335,12 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev if (alevel.rec > 110) { text_color_set(DW_COLOR_ERROR); - dw_printf ("Audio input level is too high. Reduce so most stations are around 50.\n"); + dw_printf ("Audio input level is too high. This may cause distortion and reduced decode performance.\n"); + dw_printf ("Solution is to decrease the audio input level.\n"); + dw_printf ("Setting audio input level so most stations are around 50 will provide good dyanmic range.\n"); } // FIXME: rather than checking for ichannel, how about checking medium==radio - else if (alevel.rec < 5 && chan != audio_config.igate_vchannel) { + else if (alevel.rec < 5 && chan != audio_config.igate_vchannel && subchan != -3) { text_color_set(DW_COLOR_ERROR); dw_printf ("Audio input level is too low. Increase so most stations are around 50.\n"); @@ -1325,14 +1365,18 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev strlcpy (ts, "", sizeof(ts)); } - if (subchan == -1) { + if (subchan == -1) { // dtmf text_color_set(DW_COLOR_REC); dw_printf ("[%d.dtmf%s] ", chan, ts); } - else if (subchan == -2) { + else if (subchan == -2) { // APRS-IS text_color_set(DW_COLOR_REC); dw_printf ("[%d.is%s] ", chan, ts); } + else if (subchan == -3) { // nettnc + text_color_set(DW_COLOR_REC); + dw_printf ("[%d%s] ", chan, ts); + } else { if (ax25_is_aprs(pp)) { text_color_set(DW_COLOR_REC); @@ -1493,7 +1537,7 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev 0, 0, 0, A.g_comment, // freq, tone, offset ais_obj_info, sizeof(ais_obj_info)); - snprintf (ais_obj_packet, sizeof(ais_obj_packet), "%s>%s%1d%1d:%s", A.g_src, APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, ais_obj_info); + snprintf (ais_obj_packet, sizeof(ais_obj_packet), "%s>%s%1d%1d,NOGATE:%s", A.g_src, APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, ais_obj_info); dw_printf ("[%d.AIS] %s\n", chan, ais_obj_packet); @@ -1609,9 +1653,10 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev * Use only those with correct CRC (or using FEC.) */ - if (retries == RETRY_NONE || fec_type == fec_type_fx25 || fec_type == fec_type_il2p) { - - cdigipeater (chan, pp); + if (chan < MAX_RADIO_CHANS) { + if (retries == RETRY_NONE || fec_type == fec_type_fx25 || fec_type == fec_type_il2p) { + cdigipeater (chan, pp); + } } } @@ -1703,12 +1748,13 @@ static void usage (char **argv) #if USE_HAMLIB dw_printf (" h h = hamlib increase verbose level.\n"); #endif + dw_printf (" c c = Connected mode data link state machine.\n"); dw_printf (" x x = FX.25 increase verbose level.\n"); dw_printf (" 2 2 = IL2P.\n"); dw_printf (" d d = APRStt (DTMF to APRS object translation).\n"); dw_printf (" -q Quiet (suppress output) options:\n"); dw_printf (" h h = Heard line with the audio level.\n"); - dw_printf (" d d = Decoding of APRS packets.\n"); + dw_printf (" d d = Description of APRS packets.\n"); dw_printf (" x x = Silence FX.25 information.\n"); dw_printf (" -t n Text colors. 0=disabled. 1=default. 2,3,4,... alternatives.\n"); dw_printf (" Use 9 to test compatibility with your terminal.\n"); diff --git a/src/direwolf.h b/src/direwolf.h index 69b09529..a6db3221 100644 --- a/src/direwolf.h +++ b/src/direwolf.h @@ -56,15 +56,10 @@ * * ADevice 0: channel 0 * ADevice 1: left = 2, right = 3 - * - * TODO1.2: Look for any places that have - * for (ch=0; ch<MAX_CHANS; ch++) ... - * and make sure they handle undefined channels correctly. */ #define MAX_RADIO_CHANS ((MAX_ADEVS) * 2) -#define MAX_CHANS MAX_RADIO_CHANS // TODO: Replace all former with latter to avoid confusion with following. #define MAX_TOTAL_CHANS 16 // v1.7 allows additional virtual channels which are connected // to something other than radio modems. @@ -77,7 +72,7 @@ */ #ifdef USE_HAMLIB -#define MAX_RIGS MAX_CHANS +#define MAX_RIGS MAX_RADIO_CHANS #endif /* diff --git a/src/dlq.c b/src/dlq.c index f56b8649..59d90d52 100644 --- a/src/dlq.c +++ b/src/dlq.c @@ -498,7 +498,7 @@ void dlq_connect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int num dw_printf ("dlq_connect_request (...)\n"); #endif - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); /* Allocate a new queue item. */ @@ -556,7 +556,7 @@ void dlq_disconnect_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int dw_printf ("dlq_disconnect_request (...)\n"); #endif - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); /* Allocate a new queue item. */ @@ -619,7 +619,7 @@ void dlq_outstanding_frames_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LE dw_printf ("dlq_outstanding_frames_request (...)\n"); #endif - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); /* Allocate a new queue item. */ @@ -691,7 +691,7 @@ void dlq_xmit_data_request (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN], int n dw_printf ("dlq_xmit_data_request (...)\n"); #endif - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); /* Allocate a new queue item. */ @@ -758,7 +758,7 @@ void dlq_register_callsign (char *addr, int chan, int client) dw_printf ("dlq_register_callsign (%s, chan=%d, client=%d)\n", addr, chan, client); #endif - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); /* Allocate a new queue item. */ @@ -793,7 +793,7 @@ void dlq_unregister_callsign (char *addr, int chan, int client) dw_printf ("dlq_unregister_callsign (%s, chan=%d, client=%d)\n", addr, chan, client); #endif - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); /* Allocate a new queue item. */ diff --git a/src/dtmf.c b/src/dtmf.c index 953b0f70..447366f9 100644 --- a/src/dtmf.c +++ b/src/dtmf.c @@ -80,7 +80,7 @@ static struct dd_s { /* Separate for each audio channel. */ char prev_debounced; int timeout; -} dd[MAX_CHANS]; +} dd[MAX_RADIO_CHANS]; static int s_amplitude = 100; // range of 0 .. 100 @@ -129,7 +129,7 @@ void dtmf_init (struct audio_s *p_audio_config, int amp) * Larger = narrower bandwidth, slower response. */ - for (c=0; c<MAX_CHANS; c++) { + for (c=0; c<MAX_RADIO_CHANS; c++) { struct dd_s *D = &(dd[c]); int a = ACHAN2ADEV(c); @@ -167,7 +167,7 @@ void dtmf_init (struct audio_s *p_audio_config, int amp) } } - for (c=0; c<MAX_CHANS; c++) { + for (c=0; c<MAX_RADIO_CHANS; c++) { struct dd_s *D = &(dd[c]); D->n = 0; for (j=0; j<NUM_TONES; j++) { @@ -214,6 +214,11 @@ char dtmf_sample (int c, float input) '7', '8', '9', 'C', '*', '0', '#', 'D' }; +// Only applies to radio channels. Should not be here. + if (c >= MAX_RADIO_CHANS) { + return ('$'); + } + D = &(dd[c]); for (i=0; i<NUM_TONES; i++) { diff --git a/src/dwgpsd.c b/src/dwgpsd.c index 1bc9d47a..623c6477 100644 --- a/src/dwgpsd.c +++ b/src/dwgpsd.c @@ -68,7 +68,7 @@ // 3.22 28 11 bullseye OK. // 3.23 29 12 OK. // 3.25 30 14 OK, Jan. 2023 - +// 3.25.1 30 14 bookworm TBD, Feb. 2025 // Previously the compilation would fail if the API version was later // than the last one tested. Now it is just a warning because it changes so @@ -201,7 +201,14 @@ static void * read_gpsd_thread (void *arg); * scons prefix=/usr libdir=lib/aarch64-linux-gnu * [ scons check ] * sudo scons udev-install - * + * + * Start and test + * + * sudo killall gpsd + * cat /dev/ttyACM0 + * + * sudo gpsd /dev/ttyACM0 -F /var/run/gpsd.sock + * cgps */ @@ -536,7 +543,8 @@ int main (int argc, char *argv[]) while (1) { dwfix_t fix; - fix = dwgps_read (&info) ; + fix = dwgps_read (&info) +; text_color_set (DW_COLOR_INFO); switch (fix) { case DWFIX_2D: diff --git a/src/dwsock.c b/src/dwsock.c index 6324a2fc..939253f4 100644 --- a/src/dwsock.c +++ b/src/dwsock.c @@ -125,7 +125,7 @@ int dwsock_init(void) /*------------------------------------------------------------------- * - * Name: sock_connect + * Name: dwsock_connect * * Purpose: Connect to given host / port. * diff --git a/src/encode_aprs.c b/src/encode_aprs.c index 20992bf7..a1823cd0 100644 --- a/src/encode_aprs.c +++ b/src/encode_aprs.c @@ -596,14 +596,22 @@ int encode_position (int messaging, int compressed, double lat, double lon, int presult[result_len] = '\0'; /* Altitude. Can be anywhere in comment. */ +// Officially, altitude must be six digits. +// What about all the places on the earth's surface that are below sea level? +// https://en.wikipedia.org/wiki/List_of_places_on_land_with_elevations_below_sea_level + +// The MIC-E format allows negative altitudes; not allowing it for /A=123456 seems to be an oversight. +// Most modern applications recognize the form /A=-12345 with minus and five digits. +// This maintains the same total field width and the range is more than adequate. if (alt_ft != G_UNKNOWN) { char salt[12]; /* Not clear if altitude can be negative. */ /* Be sure it will be converted to 6 digits. */ - if (alt_ft < 0) alt_ft = 0; + // if (alt_ft < 0) alt_ft = 0; + if (alt_ft < -99999) alt_ft = -99999; if (alt_ft > 999999) alt_ft = 999999; - snprintf (salt, sizeof(salt), "/A=%06d", alt_ft); + snprintf (salt, sizeof(salt), "/A=%06d", alt_ft); // /A=123456 ot /A=-12345 strlcat (presult, salt, result_size); result_len += strlen(salt); } diff --git a/src/fsk_demod_state.h b/src/fsk_demod_state.h index c9b26c23..e094bb41 100644 --- a/src/fsk_demod_state.h +++ b/src/fsk_demod_state.h @@ -469,7 +469,7 @@ struct demodulator_state_s * * Inputs: D Pointer to demodulator state. * - * chan Radio channel: 0 to MAX_CHANS - 1 + * chan Radio channel: 0 to MAX_RADIO_CHANS - 1 * * subchan Which of multiple demodulators: 0 to MAX_SUBCHANS - 1 * diff --git a/src/fx25_rec.c b/src/fx25_rec.c index 9cb5c4d9..8e6d4222 100644 --- a/src/fx25_rec.c +++ b/src/fx25_rec.c @@ -59,7 +59,7 @@ struct fx_context_s { unsigned char block[FX25_BLOCK_SIZE+1]; }; -static struct fx_context_s *fx_context[MAX_CHANS][MAX_SUBCHANS][MAX_SLICERS]; +static struct fx_context_s *fx_context[MAX_RADIO_CHANS][MAX_SUBCHANS][MAX_SLICERS]; static void process_rs_block (int chan, int subchan, int slice, struct fx_context_s *F); @@ -157,7 +157,7 @@ void fx25_rec_bit (int chan, int subchan, int slice, int dbit) struct fx_context_s *F = fx_context[chan][subchan][slice]; if (F == NULL) { - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); assert (slice >= 0 && slice < MAX_SLICERS); F = fx_context[chan][subchan][slice] = (struct fx_context_s *)malloc(sizeof (struct fx_context_s)); @@ -256,7 +256,7 @@ void fx25_rec_bit (int chan, int subchan, int slice, int dbit) int fx25_rec_busy (int chan) { - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); // This could be a little faster if we knew number of // subchannels and slicers but it is probably insignificant. diff --git a/src/fx25_send.c b/src/fx25_send.c index 7435be9f..0841a3fd 100644 --- a/src/fx25_send.c +++ b/src/fx25_send.c @@ -41,7 +41,7 @@ static void send_bit (int chan, int b); static int stuff_it (unsigned char *in, int ilen, unsigned char *out, int osize); -static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "fx25_send_frame" or "???" +static int number_of_bits_sent[MAX_RADIO_CHANS]; // Count number of bits sent by "fx25_send_frame" or "???" #if FXTEST @@ -249,7 +249,7 @@ static void send_bytes (int chan, unsigned char *b, int count) */ static void send_bit (int chan, int b) { - static int output[MAX_CHANS]; + static int output[MAX_RADIO_CHANS]; if (b == 0) { output[chan] = ! output[chan]; diff --git a/src/gen_packets.c b/src/gen_packets.c index 57b2741c..e98e774f 100644 --- a/src/gen_packets.c +++ b/src/gen_packets.c @@ -242,7 +242,7 @@ int main(int argc, char **argv) modem.adev[0].samples_per_sec = DEFAULT_SAMPLES_PER_SEC; /* -r option */ modem.adev[0].bits_per_sample = DEFAULT_BITS_PER_SAMPLE; /* -8 for 8 instead of 16 bits */ - for (chan = 0; chan < MAX_CHANS; chan++) { + for (chan = 0; chan < MAX_RADIO_CHANS; chan++) { modem.achan[chan].modem_type = MODEM_AFSK; /* change with -g */ modem.achan[chan].mark_freq = DEFAULT_MARK_FREQ; /* -m option */ modem.achan[chan].space_freq = DEFAULT_SPACE_FREQ; /* -s option */ diff --git a/src/gen_tone.c b/src/gen_tone.c index 6a816556..400c2920 100644 --- a/src/gen_tone.c +++ b/src/gen_tone.c @@ -63,14 +63,14 @@ static struct audio_s *save_audio_config_p = NULL; #define TICKS_PER_CYCLE ( 256.0 * 256.0 * 256.0 * 256.0 ) -static int ticks_per_sample[MAX_CHANS]; /* Same for both channels of same soundcard */ +static int ticks_per_sample[MAX_RADIO_CHANS]; /* Same for both channels of same soundcard */ /* because they have same sample rate */ /* but less confusing to have for each channel. */ -static int ticks_per_bit[MAX_CHANS]; -static int f1_change_per_sample[MAX_CHANS]; -static int f2_change_per_sample[MAX_CHANS]; -static float samples_per_symbol[MAX_CHANS]; +static int ticks_per_bit[MAX_RADIO_CHANS]; +static int f1_change_per_sample[MAX_RADIO_CHANS]; +static int f2_change_per_sample[MAX_RADIO_CHANS]; +static float samples_per_symbol[MAX_RADIO_CHANS]; static short sine_table[256]; @@ -78,7 +78,7 @@ static short sine_table[256]; /* Accumulators. */ -static unsigned int tone_phase[MAX_CHANS]; // Phase accumulator for tone generation. +static unsigned int tone_phase[MAX_RADIO_CHANS]; // Phase accumulator for tone generation. // Upper bits are used as index into sine table. #define PHASE_SHIFT_180 ( 128u << 24 ) @@ -86,11 +86,11 @@ static unsigned int tone_phase[MAX_CHANS]; // Phase accumulator for tone generat #define PHASE_SHIFT_45 ( 32u << 24 ) -static int bit_len_acc[MAX_CHANS]; // To accumulate fractional samples per bit. +static int bit_len_acc[MAX_RADIO_CHANS]; // To accumulate fractional samples per bit. -static int lfsr[MAX_CHANS]; // Shift register for scrambler. +static int lfsr[MAX_RADIO_CHANS]; // Shift register for scrambler. -static int bit_count[MAX_CHANS]; // Counter incremented for each bit transmitted +static int bit_count[MAX_RADIO_CHANS]; // Counter incremented for each bit transmitted // on the channel. This is only used for QPSK. // The LSB determines if we save the bit until // next time, or send this one with the previously saved. @@ -101,10 +101,10 @@ static int bit_count[MAX_CHANS]; // Counter incremented for each bit transmitted // For 8PSK, it has a different meaning. It is the // number of bits in 'save_bit' so we can accumulate // three for each symbol. -static int save_bit[MAX_CHANS]; +static int save_bit[MAX_RADIO_CHANS]; -static int prev_dat[MAX_CHANS]; // Previous data bit. Used for G3RUH style. +static int prev_dat[MAX_RADIO_CHANS]; // Previous data bit. Used for G3RUH style. @@ -163,7 +163,7 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets) amp16bit = (int)((32767 * amp) / 100); - for (chan = 0; chan < MAX_CHANS; chan++) { + for (chan = 0; chan < MAX_RADIO_CHANS; chan++) { if (audio_config_p->chan_medium[chan] == MEDIUM_RADIO) { @@ -352,8 +352,8 @@ static const int gray2phase_v27[8] = {1, 0, 2, 3, 6, 7, 5, 4}; // #define PSKIQ 1 // not ready for prime time yet. #if PSKIQ -static int xmit_octant[MAX_CHANS]; // absolute phase in 45 degree units. -static int xmit_prev_octant[MAX_CHANS]; // from previous symbol. +static int xmit_octant[MAX_RADIO_CHANS]; // absolute phase in 45 degree units. +static int xmit_prev_octant[MAX_RADIO_CHANS]; // from previous symbol. // For PSK, we generate the final signal by combining fixed frequency cosine and // sine by the following weights. diff --git a/src/hdlc_rec.c b/src/hdlc_rec.c index d87a1b50..cfae77a6 100644 --- a/src/hdlc_rec.c +++ b/src/hdlc_rec.c @@ -114,11 +114,11 @@ struct hdlc_state_s { int eas_fields_after_plus; /* Number of "-" characters after the "+". */ }; -static struct hdlc_state_s hdlc_state[MAX_CHANS][MAX_SUBCHANS][MAX_SLICERS]; +static struct hdlc_state_s hdlc_state[MAX_RADIO_CHANS][MAX_SUBCHANS][MAX_SLICERS]; -static int num_subchan[MAX_CHANS]; //TODO1.2 use ptr rather than copy. +static int num_subchan[MAX_RADIO_CHANS]; //TODO1.2 use ptr rather than copy. -static int composite_dcd[MAX_CHANS][MAX_SUBCHANS+1]; +static int composite_dcd[MAX_RADIO_CHANS][MAX_SUBCHANS+1]; /*********************************************************************************** @@ -149,7 +149,7 @@ void hdlc_rec_init (struct audio_s *pa) memset (composite_dcd, 0, sizeof(composite_dcd)); - for (ch = 0; ch < MAX_CHANS; ch++) + for (ch = 0; ch < MAX_RADIO_CHANS; ch++) { if (pa->chan_medium[ch] == MEDIUM_RADIO) { @@ -429,17 +429,24 @@ a good modem here and providing a result when it is received. ***********************************************************************************/ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled, int not_used_remove) +{ + static int64_t dummyll = 0; + static int dummy = 0; + hdlc_rec_bit_new (chan, subchan, slice, raw, is_scrambled, not_used_remove, + &dummyll, &dummy); +} + +void hdlc_rec_bit_new (int chan, int subchan, int slice, int raw, int is_scrambled, int not_used_remove, + int64_t *pll_nudge_total, int *pll_symbol_count) { int dbit; /* Data bit after undoing NRZI. */ /* Should be only 0 or 1. */ - struct hdlc_state_s *H; assert (was_init == 1); - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); - assert (slice >= 0 && slice < MAX_SLICERS); // -e option can be used to artificially introduce the desired @@ -467,7 +474,7 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled, /* * Different state information for each channel / subchannel / slice. */ - H = &hdlc_state[chan][subchan][slice]; + struct hdlc_state_s *H = &hdlc_state[chan][subchan][slice]; /* @@ -589,16 +596,44 @@ void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled, dw_printf ("\nfound flag, channel %d.%d, %d bits in frame\n", chan, subchan, rrbb_get_len(H->rrbb) - 1); #endif if (rrbb_get_len(H->rrbb) >= MIN_FRAME_LEN * 8) { - + +//JWL - end of frame + + float speed_error; // in percentage. + if (*pll_symbol_count > 0) { // avoid divde by 0. + + // TODO: + // Fudged to get +-2.0 with gen_packets -b 1224 & 1176. + // Also initialized the symbol counter to -1. + + speed_error = (float)((double)(*pll_nudge_total) * 100. / (256. * 256. * 256. * 256.) / (double)(*pll_symbol_count) + 0.02); + + text_color_set(DW_COLOR_DEBUG); + +// std dw_printf ("DEBUG: total %lld, count %d\n", *pll_nudge_total, *pll_symbol_count); +// mingw +// dw_printf ("DEBUG: total %I64d, count %d\n", *pll_nudge_total, *pll_symbol_count); +// dw_printf ("DEBUG: speed error %+0.2f%% -> %+0.1f%% \n", speed_error, speed_error); + } + else { + speed_error = 0; + } + rrbb_set_speed_error (H->rrbb, speed_error); + alevel_t alevel = demod_get_audio_level (chan, subchan); rrbb_set_audio_level (H->rrbb, alevel); hdlc_rec2_block (H->rrbb); /* Now owned by someone else who will free it. */ + H->rrbb = NULL; H->rrbb = rrbb_new (chan, subchan, slice, is_scrambled, H->lfsr, H->prev_descram); /* Allocate a new one. */ } else { + +//JWL - start of frame + *pll_nudge_total = 0; + *pll_symbol_count = -1; // comes out better than using 0. rrbb_clear (H->rrbb, is_scrambled, H->lfsr, H->prev_descram); } @@ -730,7 +765,7 @@ void dcd_change (int chan, int subchan, int slice, int state) { int old, new; - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); assert (subchan >= 0 && subchan <= MAX_SUBCHANS); assert (slice >= 0 && slice < MAX_SLICERS); assert (state == 0 || state == 1); @@ -791,7 +826,7 @@ int hdlc_rec_data_detect_any (int chan) { int sc; - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); for (sc = 0; sc < num_subchan[chan]; sc++) { if (composite_dcd[chan][sc] != 0) diff --git a/src/hdlc_rec.h b/src/hdlc_rec.h index 69b60a92..21cbf6c8 100644 --- a/src/hdlc_rec.h +++ b/src/hdlc_rec.h @@ -1,12 +1,22 @@ +/* hdlc_rec.h */ + + + + +#include <stdint.h> // int64_t #include "audio.h" void hdlc_rec_init (struct audio_s *pa); +// TODO: change all to _new. void hdlc_rec_bit (int chan, int subchan, int slice, int raw, int is_scrambled, int descram_state); +void hdlc_rec_bit_new (int chan, int subchan, int slice, int raw, int is_scrambled, int descram_state, + int64_t *pll_nudge_total, int *pll_nudge_count); + /* Provided elsewhere to process a complete frame. */ //void process_rec_frame (int chan, unsigned char *fbuf, int flen, int level); diff --git a/src/hdlc_rec2.c b/src/hdlc_rec2.c index b817018f..ebaac6c0 100644 --- a/src/hdlc_rec2.c +++ b/src/hdlc_rec2.c @@ -216,6 +216,8 @@ void hdlc_rec2_init (struct audio_s *p_audio_config) * Purpose: Extract HDLC frame from a stream of bits. * * Inputs: block - Handle for bit array. + * This will be deallocated so the caller + * must not hold on to the address. * * Description: The other (original) hdlc decoder took one bit at a time * right out of the demodulator. @@ -287,12 +289,10 @@ void hdlc_rec2_block (rrbb_t block) /* Let thru even with bad CRC. Of course, it still */ /* needs to be a minimum number of whole octets. */ ok = try_decode (block, chan, subchan, slice, alevel, retry_cfg, 1); - rrbb_delete (block); - } - else { - rrbb_delete (block); } + rrbb_delete (block); + } /* end hdlc_rec2_block */ @@ -438,7 +438,7 @@ static int try_to_fix_quick_now (rrbb_t block, int chan, int subchan, int slice, retry_cfg.u_bits.sep.bit_idx_c = -1; #ifdef DEBUG_LATER - tstart = dtime_now(); + tstart = dtime_monotonic(); dw_printf ("*** Try flipping TWO SEPARATED BITS %d bits\n", len); #endif len = rrbb_get_len(block); diff --git a/src/hdlc_send.c b/src/hdlc_send.c index 8a1cdc6d..5b2008d3 100644 --- a/src/hdlc_send.c +++ b/src/hdlc_send.c @@ -39,7 +39,7 @@ static void send_bit_nrzi (int, int); -static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "hdlc_send_frame" or "hdlc_send_flags" +static int number_of_bits_sent[MAX_RADIO_CHANS]; // Count number of bits sent by "hdlc_send_frame" or "hdlc_send_flags" @@ -240,7 +240,7 @@ static void send_byte_msb_first (int chan, int x, int polarity) // Data (non flags) use bit stuffing. -static int stuff[MAX_CHANS]; // Count number of "1" bits to keep track of when we +static int stuff[MAX_RADIO_CHANS]; // Count number of "1" bits to keep track of when we // need to break up a long run by "bit stuffing." // Needs to be array because we could be transmitting // on multiple channels at the same time. @@ -284,7 +284,7 @@ static void send_data_nrzi (int chan, int x) static void send_bit_nrzi (int chan, int b) { - static int output[MAX_CHANS]; + static int output[MAX_RADIO_CHANS]; if (b == 0) { output[chan] = ! output[chan]; diff --git a/src/igate.c b/src/igate.c index 7f84228a..4809e575 100644 --- a/src/igate.c +++ b/src/igate.c @@ -216,8 +216,8 @@ int main (int argc, char *argv[]) memset (&audio_config, 0, sizeof(audio_config)); audio_config.adev[0].num_channels = 2; - strlcpy (audio_config.achan[0].mycall, "WB2OSZ-1", sizeof(audio_config.achan[0].mycall)); - strlcpy (audio_config.achan[1].mycall, "WB2OSZ-2", sizeof(audio_config.achan[0].mycall)); + strlcpy (audio_config.mycall[0], "WB2OSZ-1", sizeof(audio_config.achan[0].mycall)); + strlcpy (audio_config.mycall[1], "WB2OSZ-2", sizeof(audio_config.achan[0].mycall)); memset (&igate_config, 0, sizeof(igate_config)); @@ -909,10 +909,10 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) // 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 (chan >= 0 && chan < MAX_TOTAL_CHANS && // in radio channel range + save_digi_config_p->filter_str[chan][MAX_TOTAL_CHANS] != NULL) { - if (pfilter(chan, MAX_CHANS, save_digi_config_p->filter_str[chan][MAX_CHANS], recv_pp, 1) != 1) { + if (pfilter(chan, MAX_TOTAL_CHANS, save_digi_config_p->filter_str[chan][MAX_TOTAL_CHANS], recv_pp, 1) != 1) { // Is this useful troubleshooting information or just distracting noise? // Originally this was always printed but there was a request to add a "quiet" option to suppress this. @@ -920,7 +920,7 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) if (s_debug >= 1) { text_color_set(DW_COLOR_INFO); - dw_printf ("Packet from channel %d to IGate was rejected by filter: %s\n", chan, save_digi_config_p->filter_str[chan][MAX_CHANS]); + dw_printf ("Packet from channel %d to IGate was rejected by filter: %s\n", chan, save_digi_config_p->filter_str[chan][MAX_TOTAL_CHANS]); } return; } @@ -998,6 +998,7 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) /* * Do not relay generic query. + * TODO: Should probably block in other direction too, in case rf>is gateway did not drop. */ if (ax25_get_dti(pp) == '?') { if (s_debug >= 1) { @@ -1071,6 +1072,8 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) * Inputs: pp - Packet object. * * chan - Radio channel where it was received. + * This will be -1 if from a beacon with sendto=ig + * so be careful if using as subscript. * * Description: Duplicate detection is handled here. * Suppress if same was sent recently. @@ -1141,7 +1144,7 @@ static void send_packet_to_server (packet_t pp, int chan) strlcat (msg, ",qAO,", sizeof(msg)); // new for version 1.4. } - strlcat (msg, save_audio_config_p->achan[chan].mycall, sizeof(msg)); + strlcat (msg, save_audio_config_p->mycall[chan>=0 ? chan : 0], sizeof(msg)); strlcat (msg, ":", sizeof(msg)); @@ -1781,7 +1784,7 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan) { int n; - assert (to_chan >= 0 && to_chan < MAX_CHANS); + assert (to_chan >= 0 && to_chan < MAX_TOTAL_CHANS); /* * Try to parse it into a packet object; we need this for the packet filtering. @@ -1856,7 +1859,7 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan) * filtering by stations along the way or the q construct. */ - assert (to_chan >= 0 && to_chan < MAX_CHANS); + assert (to_chan >= 0 && to_chan < MAX_TOTAL_CHANS); /* @@ -1906,9 +1909,9 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan) if ( ! msp_special_case) { - if (save_digi_config_p->filter_str[MAX_CHANS][to_chan] != NULL) { + if (save_digi_config_p->filter_str[MAX_TOTAL_CHANS][to_chan] != NULL) { - if (pfilter(MAX_CHANS, to_chan, save_digi_config_p->filter_str[MAX_CHANS][to_chan], pp3, 1) != 1) { + if (pfilter(MAX_TOTAL_CHANS, to_chan, save_digi_config_p->filter_str[MAX_TOTAL_CHANS][to_chan], pp3, 1) != 1) { // Previously there was a debug message here about the packet being dropped by filtering. // This is now handled better by the "-df" command line option for filtering details. @@ -1965,7 +1968,7 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan) char dest[AX25_MAX_ADDR_LEN]; /* Destination field. */ ax25_get_addr_with_ssid (pp3, AX25_DESTINATION, dest); snprintf (payload, sizeof(payload), "%s>%s,TCPIP,%s*:%s", - src, dest, save_audio_config_p->achan[to_chan].mycall, pinfo); + src, dest, save_audio_config_p->mycall[to_chan], pinfo); #if DEBUGx @@ -1991,7 +1994,7 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan) if (ig_to_tx_allow (pp3, to_chan)) { char radio [2400]; snprintf (radio, sizeof(radio), "%s>%s%d%d%s:}%s", - save_audio_config_p->achan[to_chan].mycall, + save_audio_config_p->mycall[to_chan], APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, save_igate_config_p->tx_via, payload); diff --git a/src/il2p_rec.c b/src/il2p_rec.c index 5ad457a0..a1e27263 100644 --- a/src/il2p_rec.c +++ b/src/il2p_rec.c @@ -69,7 +69,7 @@ struct il2p_context_s { int corrected; // Number of symbols corrected by RS FEC. }; -static struct il2p_context_s *il2p_context[MAX_CHANS][MAX_SUBCHANS][MAX_SLICERS]; +static struct il2p_context_s *il2p_context[MAX_RADIO_CHANS][MAX_SUBCHANS][MAX_SLICERS]; @@ -101,7 +101,7 @@ void il2p_rec_bit (int chan, int subchan, int slice, int dbit) struct il2p_context_s *F = il2p_context[chan][subchan][slice]; if (F == NULL) { - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); assert (slice >= 0 && slice < MAX_SLICERS); F = il2p_context[chan][subchan][slice] = (struct il2p_context_s *)malloc(sizeof (struct il2p_context_s)); @@ -251,12 +251,11 @@ void il2p_rec_bit (int chan, int subchan, int slice, int dbit) if (pp != NULL) { alevel_t alevel = demod_get_audio_level (chan, subchan); retry_t retries = F->corrected; - int is_fx25 = 1; // FIXME: distinguish fx.25 and IL2P. - // Currently this just means that a FEC mode was used. + fec_type_t fec_type = fec_type_il2p; // TODO: Could we put last 3 arguments in packet object rather than passing around separately? - multi_modem_process_rec_packet (chan, subchan, slice, pp, alevel, retries, is_fx25); + multi_modem_process_rec_packet (chan, subchan, slice, pp, alevel, retries, fec_type); } } // end block for local variables. diff --git a/src/il2p_send.c b/src/il2p_send.c index 3c4554e0..28948766 100644 --- a/src/il2p_send.c +++ b/src/il2p_send.c @@ -30,7 +30,7 @@ #include "gen_tone.h" -static int number_of_bits_sent[MAX_CHANS]; // Count number of bits sent by "il2p_send_frame" +static int number_of_bits_sent[MAX_RADIO_CHANS]; // Count number of bits sent by "il2p_send_frame" static void send_bytes (int chan, unsigned char *b, int count, int polarity); static void send_bit (int chan, int b, int polarity); diff --git a/src/kiss_frame.c b/src/kiss_frame.c index aa581dd2..65a09422 100644 --- a/src/kiss_frame.c +++ b/src/kiss_frame.c @@ -612,7 +612,7 @@ 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_TOTAL_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); @@ -663,10 +663,11 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, struct } text_color_set(DW_COLOR_INFO); dw_printf ("KISS protocol set TXDELAY = %d (*10mS units = %d mS), chan %d\n", kiss_msg[1], kiss_msg[1] * 10, chan); - if (kiss_msg[1] < 4 || kiss_msg[1] > 100) { + if (kiss_msg[1] < 10 || kiss_msg[1] >= 100) { text_color_set(DW_COLOR_ERROR); dw_printf ("Are you sure you want such an extreme value for TXDELAY?\n"); - dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n"); + dw_printf ("Read the Dire Wolf User Guide, \"Radio Channel - Transmit Timing\"\n"); + dw_printf ("section, to understand what this means.\n"); } xmit_set_txdelay (chan, kiss_msg[1]); break; @@ -683,7 +684,8 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, struct if (kiss_msg[1] < 5 || kiss_msg[1] > 250) { text_color_set(DW_COLOR_ERROR); dw_printf ("Are you sure you want such an extreme value for PERSIST?\n"); - dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n"); + dw_printf ("Read the Dire Wolf User Guide, \"Radio Channel - Transmit Timing\"\n"); + dw_printf ("section, to understand what this means.\n"); } xmit_set_persist (chan, kiss_msg[1]); break; @@ -700,7 +702,8 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, struct if (kiss_msg[1] < 2 || kiss_msg[1] > 50) { text_color_set(DW_COLOR_ERROR); dw_printf ("Are you sure you want such an extreme value for SLOTTIME?\n"); - dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n"); + dw_printf ("Read the Dire Wolf User Guide, \"Radio Channel - Transmit Timing\"\n"); + dw_printf ("section, to understand what this means.\n"); } xmit_set_slottime (chan, kiss_msg[1]); break; @@ -714,10 +717,11 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, struct } text_color_set(DW_COLOR_INFO); dw_printf ("KISS protocol set TXtail = %d (*10mS units = %d mS), chan %d\n", kiss_msg[1], kiss_msg[1] * 10, chan); - if (kiss_msg[1] < 2) { + if (kiss_msg[1] < 5) { text_color_set(DW_COLOR_ERROR); dw_printf ("Setting TXTAIL so low is asking for trouble. You probably don't want to do this.\n"); - dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n"); + dw_printf ("Read the Dire Wolf User Guide, \"Radio Channel - Transmit Timing\"\n"); + dw_printf ("section, to understand what this means.\n"); } xmit_set_txtail (chan, kiss_msg[1]); break; diff --git a/src/mheard.c b/src/mheard.c index f11c68f0..0e88b833 100644 --- a/src/mheard.c +++ b/src/mheard.c @@ -406,6 +406,7 @@ void mheard_save_rf (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, r mptr->chan = chan; mptr->num_digi_hops = hops; mptr->last_heard_rf = now; + // Why did I do this instead of saving the location for a position report? mptr->dlat = G_UNKNOWN; mptr->dlon = G_UNKNOWN; @@ -446,9 +447,16 @@ void mheard_save_rf (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, r } } - if (A->g_lat != G_UNKNOWN && A->g_lon != G_UNKNOWN) { - mptr->dlat = A->g_lat; - mptr->dlon = A->g_lon; + // Issue 545. This was not thought out well. + // There was a case where a station sent a position report and the location was stored. + // Later, the same station sent an object report and the stations's location was overwritten + // by the object location. Solution: Save location only if position report. + + if (A->g_packet_type == packet_type_position) { + if (A->g_lat != G_UNKNOWN && A->g_lon != G_UNKNOWN) { + mptr->dlat = A->g_lat; + mptr->dlon = A->g_lon; + } } if (mheard_debug >= 2) { diff --git a/src/multi_modem.c b/src/multi_modem.c index d2382f1a..7770a19a 100644 --- a/src/multi_modem.c +++ b/src/multi_modem.c @@ -126,7 +126,7 @@ static struct { int age; unsigned int crc; int score; -} candidate[MAX_CHANS][MAX_SUBCHANS][MAX_SLICERS]; +} candidate[MAX_RADIO_CHANS][MAX_SUBCHANS][MAX_SLICERS]; @@ -135,7 +135,7 @@ static struct { #define PROCESS_AFTER_BITS 3 -static int process_age[MAX_CHANS]; +static int process_age[MAX_RADIO_CHANS]; static void pick_best_candidate (int chan); @@ -172,7 +172,7 @@ void multi_modem_init (struct audio_s *pa) demod_init (save_audio_config_p); hdlc_rec_init (save_audio_config_p); - for (chan=0; chan<MAX_CHANS; chan++) { + for (chan=0; chan<MAX_RADIO_CHANS; chan++) { if (save_audio_config_p->chan_medium[chan] == MEDIUM_RADIO) { if (save_audio_config_p->achan[chan].baud <= 0) { text_color_set(DW_COLOR_ERROR); @@ -222,7 +222,7 @@ void multi_modem_init (struct audio_s *pa) * *------------------------------------------------------------------------------*/ -static float dc_average[MAX_CHANS]; +static float dc_average[MAX_RADIO_CHANS]; int multi_modem_get_dc_average (int chan) { @@ -319,7 +319,7 @@ void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned c packet_t pp; - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); assert (slice >= 0 && slice < MAX_SUBCHANS); @@ -329,8 +329,15 @@ void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned c char nmea[256]; ais_to_nmea (fbuf, flen, nmea, sizeof(nmea)); + // The intention is for the AIS sentences to go only to attached applications. + // e.g. SARTrack knows how to parse the AIS sentences. + + // Put NOGATE in path so RF>IS IGates will block this. + // TODO: Use station callsign, rather than "AIS," so we know where it is coming from, + // if it happens to get onto RF somehow. + char monfmt[276]; - snprintf (monfmt, sizeof(monfmt), "AIS>%s%1d%1d:{%c%c%s", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, USER_DEF_USER_ID, USER_DEF_TYPE_AIS, nmea); + snprintf (monfmt, sizeof(monfmt), "AIS>%s%1d%1d,NOGATE:{%c%c%s", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, USER_DEF_USER_ID, USER_DEF_TYPE_AIS, nmea); pp = ax25_from_text (monfmt, 1); // alevel gets in there somehow making me question why it is passed thru here. @@ -338,7 +345,7 @@ void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned c else if (save_audio_config_p->achan[chan].modem_type == MODEM_EAS) { char monfmt[300]; // EAS SAME message max length is 268 - snprintf (monfmt, sizeof(monfmt), "EAS>%s%1d%1d:{%c%c%s", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, USER_DEF_USER_ID, USER_DEF_TYPE_EAS, fbuf); + snprintf (monfmt, sizeof(monfmt), "EAS>%s%1d%1d,NOGATE:{%c%c%s", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, USER_DEF_USER_ID, USER_DEF_TYPE_EAS, fbuf); pp = ax25_from_text (monfmt, 1); // alevel gets in there somehow making me question why it is passed thru here. diff --git a/src/nettnc.c b/src/nettnc.c new file mode 100644 index 00000000..72d18cc5 --- /dev/null +++ b/src/nettnc.c @@ -0,0 +1,491 @@ + +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2024 John Langner, WB2OSZ +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + + + +/*------------------------------------------------------------------ + * + * Module: nettnc.c + * + * Purpose: Attach to Network KISS TNC(s) for NCHANNEL config file item(s). + * + * Description: Called once at application start up. + * + *---------------------------------------------------------------*/ + + +#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h + +#if __WIN32__ +#include <winsock2.h> +#include <ws2tcpip.h> // _WIN32_WINNT must be set to 0x0501 before including this +#else +#include <stdlib.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <errno.h> +#endif + +#include <unistd.h> +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <time.h> +#include <ctype.h> +#include <stddef.h> + +#include "textcolor.h" +#include "audio.h" // configuration. +#include "kiss.h" +#include "dwsock.h" // socket helper functions. +#include "ax25_pad.h" // for AX25_MAX_PACKET_LEN +#include "dlq.h" // received packet queue + +#include "nettnc.h" + + + +void hex_dump (unsigned char *p, int len); + + +// TODO: define macros in common locaation to hide platform specifics. + +#if __WIN32__ +#define THREAD_F unsigned __stdcall +#else +#define THREAD_F void * +#endif + +#if __WIN32__ +static HANDLE nettnc_listen_th[MAX_TOTAL_CHANS]; +static THREAD_F nettnc_listen_thread (void *arg); +#else +static pthread_t nettnc_listen_tid[MAX_TOTAL_CHANS]; +static THREAD_F nettnc_listen_thread (void *arg); +#endif + +static void my_kiss_rec_byte (kiss_frame_t *kf, unsigned char b, int debug, int channel_override); + +int s_kiss_debug = 0; + + +/*------------------------------------------------------------------- + * + * Name: nettnc_init + * + * Purpose: Attach to Network KISS TNC(s) for NCHANNEL config file item(s). + * + * Inputs: pa - Address of structure of type audio_s. + * + * debug ? TBD + * + * + * Returns: 0 for success, -1 for failure. + * + * Description: Called once at direwolf application start up time. + * Calls nettnc_attach for each NCHANNEL configuration item. + * + *--------------------------------------------------------------------*/ + +void nettnc_init (struct audio_s *pa) +{ + for (int i = 0; i < MAX_TOTAL_CHANS; i++) { + + if (pa->chan_medium[i] == MEDIUM_NETTNC) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Channel %d: Network TNC %s %d\n", i, pa->nettnc_addr[i], pa->nettnc_port[i]); + int e = nettnc_attach (i, pa->nettnc_addr[i], pa->nettnc_port[i]); + if (e < 0) { + exit (1); + } + } + } + +} // end nettnc_init + + + +/*------------------------------------------------------------------- + * + * Name: nettnc_attach + * + * Purpose: Attach to one Network KISS TNC. + * + * Inputs: chan - channel number from NCHANNEL configuration. + * + * host - Host name or IP address. Often "localhost". + * + * port - TCP port number. Typically 8001. + * + * init_func - Call this function after establishing communication // + * with the TNC. We put it here, so that it can be done// + * again automatically if the TNC disappears and we// + * reattach to it.// + * It must return 0 for success.// + * Can be NULL if not needed.// + * + * Returns: 0 for success, -1 for failure. + * + * Description: This starts up a thread, for each socket, which listens to the socket and + * dispatches the messages to the corresponding callback functions. + * It will also attempt to re-establish communication with the + * TNC if it goes away. + * + *--------------------------------------------------------------------*/ + +static char s_tnc_host[MAX_TOTAL_CHANS][80]; +static char s_tnc_port[MAX_TOTAL_CHANS][20]; +static volatile int s_tnc_sock[MAX_TOTAL_CHANS]; // Socket handle or file descriptor. -1 for invalid. + + +int nettnc_attach (int chan, char *host, int port) +{ + assert (chan >= 0 && chan < MAX_TOTAL_CHANS); + + char tncaddr[DWSOCK_IPADDR_LEN]; + + char sport[20]; // need port as text string later. + snprintf (sport, sizeof(sport), "%d", port); + + strlcpy (s_tnc_host[chan], host, sizeof(s_tnc_host[chan])); + strlcpy (s_tnc_port[chan], sport, sizeof(s_tnc_port[chan])); + s_tnc_sock[chan] = -1; + + dwsock_init(); + + s_tnc_sock[chan] = dwsock_connect (s_tnc_host[chan], s_tnc_port[chan], "Network TNC", 0, 0, tncaddr); + + if (s_tnc_sock[chan] == -1) { + return (-1); + } + + +/* + * Read frames from the network TNC. + * If the TNC disappears, try to reestablish communication. + */ + + +#if __WIN32__ + nettnc_listen_th[chan] = (HANDLE)_beginthreadex (NULL, 0, nettnc_listen_thread, (void *)(ptrdiff_t)chan, 0, NULL); + if (nettnc_listen_th[chan] == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error: Could not create remore TNC listening thread\n"); + return (-1); + } +#else + int e = pthread_create (&nettnc_listen_tid[chan], NULL, nettnc_listen_thread, (void *)(ptrdiff_t)chan); + if (e != 0) { + text_color_set(DW_COLOR_ERROR); + perror("Internal error: Could not create network TNC listening thread"); + return (-1); + } +#endif + +// TNC initialization if specified. + +// if (s_tnc_init_func != NULL) { +// e = (*s_tnc_init_func)(); +// return (e); +// } + + return (0); + +} // end nettnc_attach + + + +/*------------------------------------------------------------------- + * + * Name: nettnc_listen_thread + * + * Purpose: Listen for anything from TNC and process it. + * Reconnect if something goes wrong and we got disconnected. + * + * Inputs: arg - Channel number. + * s_tnc_host[chan] - Host & port for re-connection. + * s_tnc_port[chan] + * + * Outputs: s_tnc_sock[chan] - File descriptor for communicating with TNC. + * Will be -1 if not connected. + * + *--------------------------------------------------------------------*/ + +#if __WIN32__ +static unsigned __stdcall nettnc_listen_thread (void *arg) +#else +static void * nettnc_listen_thread (void *arg) +#endif +{ + int chan = (int)(ptrdiff_t)arg; + assert (chan >= 0 && chan < MAX_TOTAL_CHANS); + + kiss_frame_t kstate; // State machine to gather a KISS frame. + memset (&kstate, 0, sizeof(kstate)); + + char tncaddr[DWSOCK_IPADDR_LEN]; // IP address used by dwsock_connect. + // Useful when rotate addresses used. + +// Set up buffer for collecting a KISS frame.$CC exttnc.c + + while (1) { +/* + * Re-attach to TNC if not currently attached. + */ + if (s_tnc_sock[chan] == -1) { + + text_color_set(DW_COLOR_ERROR); + // I'm using the term "attach" here, in an attempt to + // avoid confusion with the AX.25 connect. + dw_printf ("Attempting to reattach to network TNC...\n"); + + s_tnc_sock[chan] = dwsock_connect (s_tnc_host[chan], s_tnc_port[chan], "Network TNC", 0, 0, tncaddr); + + if (s_tnc_sock[chan] != -1) { + dw_printf ("Successfully reattached to network TNC.\n"); + } + } + else { +#define NETTNCBUFSIZ 2048 + unsigned char buf[NETTNCBUFSIZ]; + int n = SOCK_RECV (s_tnc_sock[chan], (char *)buf, sizeof(buf)); + + if (n == -1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Lost communication with network TNC. Will try to reattach.\n"); + dwsock_close (s_tnc_sock[chan]); + s_tnc_sock[chan] = -1; + SLEEP_SEC(5); + continue; + } + +#if 0 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("TEMP DEBUG: %d bytes received from channel %d network TNC.\n", n, chan); +#endif + for (int j = 0; j < n; j++) { + // Separate the byte stream into KISS frame(s) and make it + // look like this came from a radio channel. + my_kiss_rec_byte (&kstate, buf[j], s_kiss_debug, chan); + } + } // s_tnc_sock != -1 + } // while (1) + + return (0); // unreachable but shutup warning. + +} // end nettnc_listen_thread + + + +/*------------------------------------------------------------------- + * + * Name: my_kiss_rec_byte + * + * Purpose: Process one byte from a KISS network TNC. + * + * Inputs: kf - Current state of building a frame. + * b - A byte from the input stream. + * debug - Activates debug output. + * channel_overide - Set incoming channel number to the NCHANNEL + * number rather than the channel in the KISS frame. + * + * Outputs: kf - Current state is updated. + * + * Returns: none. + * + * Description: This is a simplified version of kiss_rec_byte used + * for talking to KISS client applications. It already has + * too many special cases and I don't want to make it worse. + * This also needs to make the packet look like it came from + * a radio channel, not from a client app. + * + *-----------------------------------------------------------------*/ + +static void my_kiss_rec_byte (kiss_frame_t *kf, unsigned char b, int debug, int channel_override) +{ + + //dw_printf ("my_kiss_rec_byte ( %c %02x ) \n", b, b); + + switch (kf->state) { + + case KS_SEARCHING: /* Searching for starting FEND. */ + default: + + if (b == FEND) { + + /* Start of frame. */ + + kf->kiss_len = 0; + kf->kiss_msg[kf->kiss_len++] = b; + kf->state = KS_COLLECTING; + return; + } + return; + break; + + case KS_COLLECTING: /* Frame collection in progress. */ + + + if (b == FEND) { + + unsigned char unwrapped[AX25_MAX_PACKET_LEN]; + int ulen; + + /* End of frame. */ + + if (kf->kiss_len == 0) { + /* Empty frame. Starting a new one. */ + kf->kiss_msg[kf->kiss_len++] = b; + return; + } + if (kf->kiss_len == 1 && kf->kiss_msg[0] == FEND) { + /* Empty frame. Just go on collecting. */ + return; + } + + kf->kiss_msg[kf->kiss_len++] = b; + if (debug) { + /* As received over the wire from network TNC. */ + // May include escapted characters. What about FEND? +// FIXME: make it say Network TNC. + kiss_debug_print (FROM_CLIENT, NULL, kf->kiss_msg, kf->kiss_len); + } + + ulen = kiss_unwrap (kf->kiss_msg, kf->kiss_len, unwrapped); + + if (debug >= 2) { + /* Append CRC to this and it goes out over the radio. */ + text_color_set(DW_COLOR_DEBUG); + dw_printf ("\n"); + dw_printf ("Frame content after removing KISS framing and any escapes:\n"); + /* Don't include the "type" indicator. */ + /* It contains the radio channel and type should always be 0 here. */ + hex_dump (unwrapped+1, ulen-1); + } + + // Convert to packet object and send to received packet queue. + // Note that we use channel associated with the network TNC, not channel in KISS frame. + + int subchan = -3; + int slice = 0; + alevel_t alevel; + memset(&alevel, 0, sizeof(alevel)); + packet_t pp = ax25_from_frame (unwrapped+1, ulen-1, alevel); + if (pp != NULL) { + fec_type_t fec_type = fec_type_none; + retry_t retries; + memset (&retries, 0, sizeof(retries)); + char spectrum[] = "Network TNC"; + dlq_rec_frame (channel_override, subchan, slice, pp, alevel, fec_type, retries, spectrum); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Failed to create packet object for KISS frame from channel %d network TNC.\n", channel_override); + } + + kf->state = KS_SEARCHING; + return; + } + + if (kf->kiss_len < MAX_KISS_LEN) { + kf->kiss_msg[kf->kiss_len++] = b; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("KISS frame from network TNC exceeded maximum length.\n"); + } + return; + break; + } + + return; /* unreachable but suppress compiler warning. */ + +} /* end my_kiss_rec_byte */ + + + + + + + + + +/*------------------------------------------------------------------- + * + * Name: nettnc_send_packet + * + * Purpose: Send packet to a KISS network TNC. + * + * Inputs: chan - Channel number from NCHANNEL configuration. + * pp - Packet object. + * b - A byte from the input stream. + * + * Outputs: Packet is converted to KISS and send to network TNC. + * + * Returns: none. + * + * Description: This does not free the packet object; caller is responsible. + * + *-----------------------------------------------------------------*/ + +void nettnc_send_packet (int chan, packet_t pp) +{ + +// First, get the on-air frame format from packet object. +// Prepend 0 byte for KISS command and channel. + + unsigned char frame_buff[AX25_MAX_PACKET_LEN + 2]; // One byte for channel/command, + // followed by the AX.25 on-air format frame. + frame_buff[0] = 0; // For now, set channel to 0. + + unsigned char *fbuf = ax25_get_frame_data_ptr (pp); + int flen = ax25_get_frame_len (pp); + + memcpy (frame_buff+1, fbuf, flen); + +// Next, encapsulate into KISS frame with surrounding FENDs and any escapes. + + unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN]; + int kiss_len = kiss_encapsulate (frame_buff, flen+1, kiss_buff); + +#if __WIN32__ + int err = SOCK_SEND(s_tnc_sock[chan], (char*)kiss_buff, kiss_len); + if (err == SOCKET_ERROR) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\nError %d sending packet to KISS Network TNC for channel %d. Closing connection.\n\n", WSAGetLastError(), chan); + closesocket (s_tnc_sock[chan]); + s_tnc_sock[chan] = -1; + } +#else + int err = SOCK_SEND (s_tnc_sock[chan], kiss_buff, kiss_len); + if (err <= 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\nError %d sending packet to KISS Network TNC for channel %d. Closing connection.\n\n", err, chan); + close (s_tnc_sock[chan]); + s_tnc_sock[chan] = -1; + } +#endif + + // Do not free packet object; caller will take care of it. + +} /* end nettnc_send_packet */ + diff --git a/src/nettnc.h b/src/nettnc.h new file mode 100644 index 00000000..d8a10f43 --- /dev/null +++ b/src/nettnc.h @@ -0,0 +1,7 @@ + + +void nettnc_init (struct audio_s *pa); + +int nettnc_attach (int chan, char *host, int port); + +void nettnc_send_packet (int chan, packet_t pp); \ No newline at end of file diff --git a/src/pfilter.c b/src/pfilter.c index 35767a67..cc51519e 100644 --- a/src/pfilter.c +++ b/src/pfilter.c @@ -99,7 +99,7 @@ typedef enum token_type_e { TOKEN_AND, TOKEN_OR, TOKEN_NOT, TOKEN_LPAREN, TOKEN_ typedef struct pfstate_s { - int from_chan; /* From and to channels. MAX_CHANS is used for IGate. */ + int from_chan; /* From and to channels. MAX_TOTAL_CHANS is used for IGate. */ int to_chan; /* Used only for debug and error messages. */ /* @@ -175,7 +175,7 @@ static char *bool2text (int val) * * Inputs: from_chan - Channel packet is coming from. * to_chan - Channel packet is going to. - * Both are 0 .. MAX_CHANS-1 or MAX_CHANS for IGate. + * Both are 0 .. MAX_TOTAL_CHANS-1 or MAX_TOTAL_CHANS for IGate. * For debug/error messages only. * * filter - String of filter specs and logical operators to combine them. @@ -201,8 +201,8 @@ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp, int is_aprs) char *p; int result; - assert (from_chan >= 0 && from_chan <= MAX_CHANS); - assert (to_chan >= 0 && to_chan <= MAX_CHANS); + assert (from_chan >= 0 && from_chan <= MAX_TOTAL_CHANS); + assert (to_chan >= 0 && to_chan <= MAX_TOTAL_CHANS); memset (&pfstate, 0, sizeof(pfstate)); @@ -258,10 +258,10 @@ int pfilter (int from_chan, int to_chan, char *filter, packet_t pp, int is_aprs) if (s_debug >= 1) { text_color_set(DW_COLOR_DEBUG); - if (from_chan == MAX_CHANS) { + if (from_chan == MAX_TOTAL_CHANS) { dw_printf (" Packet filter from IGate to radio channel %d returns %s\n", to_chan, bool2text(result)); } - else if (to_chan == MAX_CHANS) { + else if (to_chan == MAX_TOTAL_CHANS) { dw_printf (" Packet filter from radio channel %d to IGate returns %s\n", from_chan, bool2text(result)); } else if (is_aprs) { @@ -1478,9 +1478,9 @@ static void print_error (pfstate_t *pf, char *msg) { char intro[50]; - if (pf->from_chan == MAX_CHANS) { + if (pf->from_chan == MAX_TOTAL_CHANS) { - if (pf->to_chan == MAX_CHANS) { + if (pf->to_chan == MAX_TOTAL_CHANS) { snprintf (intro, sizeof(intro), "filter[IG,IG]: "); } else { @@ -1489,7 +1489,7 @@ static void print_error (pfstate_t *pf, char *msg) } else { - if (pf->to_chan == MAX_CHANS) { + if (pf->to_chan == MAX_TOTAL_CHANS) { snprintf (intro, sizeof(intro), "filter[%d,IG]: ", pf->from_chan); } else { diff --git a/src/ptt.c b/src/ptt.c index 5187f1df..ec093878 100644 --- a/src/ptt.c +++ b/src/ptt.c @@ -162,6 +162,10 @@ #include <hamlib/rig.h> #endif +#ifdef USE_GPIOD +#include <gpiod.h> +#endif + /* So we can have more common code for fd. */ typedef int HANDLE; #define INVALID_HANDLE_VALUE (-1) @@ -468,6 +472,20 @@ void export_gpio(int ch, int ot, int invert, int direction) text_color_set(DW_COLOR_ERROR); dw_printf ("Error writing \"%s\" to %s, errno=%d\n", stemp, gpio_export_path, e); dw_printf ("%s\n", strerror(e)); + + if (e == 22) { + // It appears that error 22 occurs when sysfs gpio is not available. + // (See https://github.com/wb2osz/direwolf/issues/503) + // + // The solution might be to use the new gpiod approach. + + dw_printf ("It looks like gpio with sysfs is not supported on this operating system.\n"); + dw_printf ("Rather than the following form, in the configuration file,\n"); + dw_printf (" PTT GPIO %s\n", stemp); + dw_printf ("try using gpiod form instead. e.g.\n"); + dw_printf (" PTT GPIOD gpiochip0 %s\n", stemp); + dw_printf ("You can get a list of gpio chip names and corresponding I/O lines with \"gpioinfo\" command.\n"); + } exit (1); } } @@ -634,6 +652,31 @@ void export_gpio(int ch, int ot, int invert, int direction) get_access_to_gpio (gpio_value_path); } +#if defined(USE_GPIOD) +int gpiod_probe(const char *chip_name, int line_number) +{ + struct gpiod_chip *chip; + chip = gpiod_chip_open_by_name(chip_name); + if (chip == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Can't open GPIOD chip %s.\n", chip_name); + return -1; + } + + struct gpiod_line *line; + line = gpiod_chip_get_line(chip, line_number); + if (line == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Can't get GPIOD line %d.\n", line_number); + return -1; + } + if (ptt_debug_level >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf("GPIOD probe OK. Chip: %s line: %d\n", chip_name, line_number); + } + return 0; +} +#endif /* USE_GPIOD */ #endif /* not __WIN32__ */ @@ -650,7 +693,8 @@ void export_gpio(int ch, int ot, int invert, int direction) * ptt_method Method for PTT signal. * PTT_METHOD_NONE - not configured. Could be using VOX. * PTT_METHOD_SERIAL - serial (com) port. - * PTT_METHOD_GPIO - general purpose I/O. + * PTT_METHOD_GPIO - general purpose I/O (sysfs). + * PTT_METHOD_GPIOD - general purpose I/O (libgpiod). * PTT_METHOD_LPT - Parallel printer port. * PTT_METHOD_HAMLIB - HAMLib rig control. * PTT_METHOD_CM108 - GPIO pins of CM108 etc. USB Audio. @@ -686,12 +730,12 @@ void export_gpio(int ch, int ot, int invert, int direction) -static HANDLE ptt_fd[MAX_CHANS][NUM_OCTYPES]; +static HANDLE ptt_fd[MAX_RADIO_CHANS][NUM_OCTYPES]; /* Serial port handle or fd. */ /* Could be the same for two channels */ /* if using both RTS and DTR. */ #if USE_HAMLIB -static RIG *rig[MAX_CHANS][NUM_OCTYPES]; +static RIG *rig[MAX_RADIO_CHANS][NUM_OCTYPES]; #endif static char otnames[NUM_OCTYPES][8]; @@ -717,7 +761,7 @@ void ptt_init (struct audio_s *audio_config_p) strlcpy (otnames[OCTYPE_CON], "CON", sizeof(otnames[OCTYPE_CON])); - for (ch = 0; ch < MAX_CHANS; ch++) { + for (ch = 0; ch < MAX_RADIO_CHANS; ch++) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { @@ -729,12 +773,13 @@ void ptt_init (struct audio_s *audio_config_p) if (ptt_debug_level >= 2) { text_color_set(DW_COLOR_DEBUG); - dw_printf ("ch=%d, %s method=%d, device=%s, line=%d, gpio=%d, lpt_bit=%d, invert=%d\n", + dw_printf ("ch=%d, %s method=%d, device=%s, line=%d, name=%s, gpio=%d, lpt_bit=%d, invert=%d\n", ch, otnames[ot], audio_config_p->achan[ch].octrl[ot].ptt_method, audio_config_p->achan[ch].octrl[ot].ptt_device, audio_config_p->achan[ch].octrl[ot].ptt_line, + audio_config_p->achan[ch].octrl[ot].out_gpio_name, audio_config_p->achan[ch].octrl[ot].out_gpio_num, audio_config_p->achan[ch].octrl[ot].ptt_lpt_bit, audio_config_p->achan[ch].octrl[ot].ptt_invert); @@ -746,7 +791,7 @@ void ptt_init (struct audio_s *audio_config_p) * Set up serial ports. */ - for (ch = 0; ch < MAX_CHANS; ch++) { + for (ch = 0; ch < MAX_RADIO_CHANS; ch++) { if (audio_config_p->chan_medium[ch] == MEDIUM_RADIO) { int ot; @@ -861,7 +906,7 @@ void ptt_init (struct audio_s *audio_config_p) */ using_gpio = 0; - for (ch=0; ch<MAX_CHANS; ch++) { + for (ch=0; ch<MAX_RADIO_CHANS; ch++) { if (save_audio_config_p->chan_medium[ch] == MEDIUM_RADIO) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { @@ -880,13 +925,34 @@ void ptt_init (struct audio_s *audio_config_p) if (using_gpio) { get_access_to_gpio ("/sys/class/gpio/export"); } - +#if defined(USE_GPIOD) + // GPIOD + for (ch = 0; ch < MAX_RADIO_CHANS; ch++) { + if (save_audio_config_p->chan_medium[ch] == MEDIUM_RADIO) { + for (int ot = 0; ot < NUM_OCTYPES; ot++) { + if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_GPIOD) { + const char *chip_name = audio_config_p->achan[ch].octrl[ot].out_gpio_name; + int line_number = audio_config_p->achan[ch].octrl[ot].out_gpio_num; + int rc = gpiod_probe(chip_name, line_number); + if (rc < 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Disable PTT for channel %d\n", ch); + audio_config_p->achan[ch].octrl[ot].ptt_method = PTT_METHOD_NONE; + } else { + // Set initial state off ptt_set will invert output signal if appropriate. + ptt_set (ot, ch, 0); + } + } + } + } + } +#endif /* USE_GPIOD */ /* * We should now be able to create the device nodes for * the pins we want to use. */ - for (ch = 0; ch < MAX_CHANS; ch++) { + for (ch = 0; ch < MAX_RADIO_CHANS; ch++) { if (save_audio_config_p->chan_medium[ch] == MEDIUM_RADIO) { int ot; // output control type, PTT, DCD, CON, ... @@ -918,7 +984,7 @@ void ptt_init (struct audio_s *audio_config_p) #if ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) ) - for (ch = 0; ch < MAX_CHANS; ch++) { + for (ch = 0; ch < MAX_RADIO_CHANS; ch++) { if (save_audio_config_p->chan_medium[ch] == MEDIUM_RADIO) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { @@ -985,7 +1051,7 @@ void ptt_init (struct audio_s *audio_config_p) #endif /* x86 Linux */ #ifdef USE_HAMLIB - for (ch = 0; ch < MAX_CHANS; ch++) { + for (ch = 0; ch < MAX_RADIO_CHANS; ch++) { if (save_audio_config_p->chan_medium[ch] == MEDIUM_RADIO) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { @@ -1097,7 +1163,7 @@ void ptt_init (struct audio_s *audio_config_p) #if USE_CM108 - for (ch = 0; ch < MAX_CHANS; ch++) { + for (ch = 0; ch < MAX_RADIO_CHANS; ch++) { if (audio_config_p->chan_medium[ch] == MEDIUM_RADIO) { int ot; @@ -1119,11 +1185,31 @@ void ptt_init (struct audio_s *audio_config_p) /* Why doesn't it transmit? Probably forgot to specify PTT option. */ - for (ch=0; ch<MAX_CHANS; ch++) { + for (ch=0; ch<MAX_RADIO_CHANS; ch++) { if (audio_config_p->chan_medium[ch] == MEDIUM_RADIO) { if(audio_config_p->achan[ch].octrl[OCTYPE_PTT].ptt_method == PTT_METHOD_NONE) { text_color_set(DW_COLOR_INFO); - dw_printf ("Note: PTT not configured for channel %d. (Ignore this if using VOX.)\n", ch); + dw_printf ("\n"); + dw_printf ("Note: PTT not configured for channel %d. (OK if using VOX.)\n", ch); + dw_printf ("When using VOX, ensure that it adds very little delay (e.g. 10-20) milliseconds\n"); + dw_printf ("between the time that transmit audio ends and PTT is deactivated.\n"); + dw_printf ("For example, if using a SignaLink USB, turn the DLY control all the\n"); + dw_printf ("way counter clockwise.\n"); + dw_printf ("\n"); + dw_printf ("Using VOX built in to the radio is a VERY BAD idea. This is intended\n"); + dw_printf ("for voice operation, with gaps in the sound, and typically has a delay of about a\n"); + dw_printf ("half second between the time the audio stops and the transmitter is turned off.\n"); + dw_printf ("When using APRS your transmiter will be sending a quiet carrier for\n"); + dw_printf ("about a half second after your packet ends. This may interfere with the\n"); + dw_printf ("the next station to transmit. This is being inconsiderate.\n"); + dw_printf ("\n"); + dw_printf ("If you are trying to use VOX with connected mode packet, expect\n"); + dw_printf ("frustration and disappointment. Connected mode involves rapid responses\n"); + dw_printf ("which you will probably miss because your transmitter is still on when\n"); + dw_printf ("the response is being transmitted.\n"); + dw_printf ("\n"); + dw_printf ("Read the User Guide 'Transmit Timing' section for more details.\n"); + dw_printf ("\n"); } } } @@ -1165,14 +1251,14 @@ void ptt_set (int ot, int chan, int ptt_signal) int ptt2 = ptt_signal; assert (ot >= 0 && ot < NUM_OCTYPES); - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); if (ptt_debug_level >= 1) { text_color_set(DW_COLOR_DEBUG); dw_printf ("%s %d = %d\n", otnames[ot], chan, ptt_signal); } - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); if ( save_audio_config_p->chan_medium[chan] != MEDIUM_RADIO) { text_color_set(DW_COLOR_ERROR); @@ -1298,6 +1384,18 @@ void ptt_set (int ot, int chan, int ptt_signal) close (fd); } + +#if defined(USE_GPIOD) + if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_GPIOD) { + const char *chip = save_audio_config_p->achan[chan].octrl[ot].out_gpio_name; + int line = save_audio_config_p->achan[chan].octrl[ot].out_gpio_num; + int rc = gpiod_ctxless_set_value(chip, line, ptt, false, "direwolf", NULL, NULL); + if (ptt_debug_level >= 1) { + text_color_set(DW_COLOR_DEBUG); + dw_printf("PTT_METHOD_GPIOD chip: %s line: %d ptt: %d rc: %d\n", chip, line, ptt, rc); + } + } +#endif /* USE_GPIOD */ #endif /* @@ -1396,7 +1494,7 @@ void ptt_set (int ot, int chan, int ptt_signal) int get_input (int it, int chan) { assert (it >= 0 && it < NUM_ICTYPES); - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); if ( save_audio_config_p->chan_medium[chan] != MEDIUM_RADIO) { text_color_set(DW_COLOR_ERROR); @@ -1461,7 +1559,7 @@ void ptt_term (void) { int n; - for (n = 0; n < MAX_CHANS; n++) { + for (n = 0; n < MAX_RADIO_CHANS; n++) { if (save_audio_config_p->chan_medium[n] == MEDIUM_RADIO) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { @@ -1470,7 +1568,7 @@ void ptt_term (void) } } - for (n = 0; n < MAX_CHANS; n++) { + for (n = 0; n < MAX_RADIO_CHANS; n++) { if (save_audio_config_p->chan_medium[n] == MEDIUM_RADIO) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { @@ -1488,7 +1586,7 @@ void ptt_term (void) #ifdef USE_HAMLIB - for (n = 0; n < MAX_CHANS; n++) { + for (n = 0; n < MAX_RADIO_CHANS; n++) { if (save_audio_config_p->chan_medium[n] == MEDIUM_RADIO) { int ot; for (ot = 0; ot < NUM_OCTYPES; ot++) { diff --git a/src/recv.c b/src/recv.c index 49040e55..d6281567 100644 --- a/src/recv.c +++ b/src/recv.c @@ -108,6 +108,7 @@ #include "dtmf.h" #include "aprs_tt.h" #include "ax25_link.h" +//#include "ring.h" #if __WIN32__ @@ -278,7 +279,7 @@ static void * recv_adev_thread (void *arg) // Try to re-init the audio device a couple times before giving up? text_color_set(DW_COLOR_ERROR); - dw_printf ("Terminating after audio input failure.\n"); + dw_printf ("Terminating after audio device %d input failure.\n", a); exit (1); } diff --git a/src/rrbb.c b/src/rrbb.c index e787dae5..0666d42b 100644 --- a/src/rrbb.c +++ b/src/rrbb.c @@ -83,7 +83,7 @@ rrbb_t rrbb_new (int chan, int subchan, int slice, int is_scrambled, int descram { rrbb_t result; - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); assert (subchan >= 0 && subchan < MAX_SUBCHANS); assert (slice >= 0 && slice < MAX_SLICERS); @@ -333,7 +333,7 @@ int rrbb_get_chan (rrbb_t b) assert (b->magic1 == MAGIC1); assert (b->magic2 == MAGIC2); - assert (b->chan >= 0 && b->chan < MAX_CHANS); + assert (b->chan >= 0 && b->chan < MAX_RADIO_CHANS); return (b->chan); } diff --git a/src/server.c b/src/server.c index 6b41af25..a54dd6b4 100644 --- a/src/server.c +++ b/src/server.c @@ -379,7 +379,7 @@ static void debug_print (fromto_t fromto, int client, struct agwpe_s *pmsg, int case 'C': strlcpy (datakind, "AX.25 Connection Received", sizeof(datakind)); break; case 'D': strlcpy (datakind, "Connected AX.25 Data", sizeof(datakind)); break; case 'd': strlcpy (datakind, "Disconnected", sizeof(datakind)); break; - case 'M': strlcpy (datakind, "Monitored Connected Information", sizeof(datakind)); break; + case 'I': strlcpy (datakind, "Monitored Connected Information", sizeof(datakind)); break; case 'S': strlcpy (datakind, "Monitored Supervisory Information", sizeof(datakind)); break; case 'U': strlcpy (datakind, "Monitored Unproto Information", sizeof(datakind)); break; case 'T': strlcpy (datakind, "Monitoring Own Information", sizeof(datakind)); break; @@ -820,7 +820,7 @@ void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int fl /* Stick in extra byte for the "TNC" to use. */ - agwpe_msg.data[0] = 0; + agwpe_msg.data[0] = chan << 4; // Was 0. Fixed in 1.8. memcpy (agwpe_msg.data + 1, fbuf, (size_t)flen); if (debug_client) { @@ -976,6 +976,14 @@ void server_send_monitored (int chan, packet_t pp, int own_xmit) // Format addresses in AGWPR monitoring format such as: // 1:Fm ZL4FOX-8 To Q7P2U2 Via WIDE3-3 +// There is some disagreement, in the user community, about whether to: +// * follow the lead of UZ7HO SoundModem and mark all of the used addresses, or +// * follow the TNC-2 Monitoring format and mark only the last used, i.e. the station heard. + +// I think my opinion (which could change) is that we should try to be consistent with TNC-2 format +// rather than continuing to propagate historical inconsistencies. + + static void mon_addrs (int chan, packet_t pp, char *result, int result_size) { char src[AX25_MAX_ADDR_LEN]; @@ -986,16 +994,25 @@ static void mon_addrs (int chan, packet_t pp, char *result, int result_size) int num_digi = ax25_get_num_repeaters(pp); if (num_digi > 0) { + char via[AX25_MAX_REPEATERS*(AX25_MAX_ADDR_LEN+1)]; // complete via path + strlcpy (via, "", sizeof(via)); + + for (int j = 0; j < num_digi; j++) { + char digiaddr[AX25_MAX_ADDR_LEN]; - char via[AX25_MAX_REPEATERS*(AX25_MAX_ADDR_LEN+1)]; - char stemp[AX25_MAX_ADDR_LEN+1]; - int j; + if (j != 0) { + strlcat (via, ",", sizeof(via)); // comma if not first address + } + ax25_get_addr_with_ssid (pp, AX25_REPEATER_1 + j, digiaddr); + strlcat (via, digiaddr, sizeof(via)); +#if 0 // Mark each used with * as seen in UZ7HO SoundModem. + if (ax25_get_h(pp, AX25_REPEATER_1 + j)) { +#else // Mark only last used (i.e. the heard station) with * as in TNC-2 Monitoring format. + if (AX25_REPEATER_1 + j == ax25_get_heard(pp)) { +#endif + strlcat (via, "*", sizeof(via)); + } - ax25_get_addr_with_ssid (pp, AX25_REPEATER_1, via); - for (j = 1; j < num_digi; j++) { - ax25_get_addr_with_ssid (pp, AX25_REPEATER_1 + j, stemp); - strlcat (via, ",", sizeof(via)); - strlcat (via, stemp, sizeof(via)); } snprintf (result, result_size, " %d:Fm %s To %s Via %s ", chan+1, src, dst, via); @@ -1413,7 +1430,7 @@ static THREAD_F cmd_listen_thread (void *arg) /* * Take some precautions to guard against bad data which could cause problems later. */ - if (cmd.hdr.portx < 0 || cmd.hdr.portx >= MAX_CHANS) { + if (cmd.hdr.portx < 0 || cmd.hdr.portx >= MAX_TOTAL_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("\nInvalid port number, %d, in command '%c', from AGW client application %d.\n", cmd.hdr.portx, cmd.hdr.datakind, client); @@ -1544,7 +1561,7 @@ static THREAD_F cmd_listen_thread (void *arg) // No other place cares about total number. count = 0; - for (j=0; j<MAX_CHANS; j++) { + for (j=0; j<MAX_TOTAL_CHANS; j++) { if (save_audio_config_p->chan_medium[j] == MEDIUM_RADIO || save_audio_config_p->chan_medium[j] == MEDIUM_IGATE || save_audio_config_p->chan_medium[j] == MEDIUM_NETTNC) { @@ -1553,12 +1570,13 @@ static THREAD_F cmd_listen_thread (void *arg) } snprintf (reply.info, sizeof(reply.info), "%d;", count); - for (j=0; j<MAX_CHANS; j++) { + for (j=0; j<MAX_TOTAL_CHANS; j++) { switch (save_audio_config_p->chan_medium[j]) { case MEDIUM_RADIO: { + // Misleading if using stdin or udp. char stemp[100]; int a = ACHAN2ADEV(j); // If I was really ambitious, some description could be provided. @@ -1593,12 +1611,7 @@ static THREAD_F cmd_listen_thread (void *arg) break; default: - { - // could elaborate with hostname, etc. - char stemp[100]; - snprintf (stemp, sizeof(stemp), "Port%d INVALID CHANNEL;", j+1); - strlcat (reply.info, stemp, sizeof(reply.info)); - } + ; // Only list valid channels. break; } // switch @@ -1721,6 +1734,7 @@ static THREAD_F cmd_listen_thread (void *arg) packet_t pp; + int pid = cmd.hdr.pid; strlcpy (stemp, cmd.hdr.call_from, sizeof(stemp)); strlcat (stemp, ">", sizeof(stemp)); strlcat (stemp, cmd.hdr.call_to, sizeof(stemp)); @@ -1734,33 +1748,45 @@ static THREAD_F cmd_listen_thread (void *arg) strlcat (stemp, p, sizeof(stemp)); p += 10; } + // At this point, p now points to info part after digipeaters. + + // Issue 527: NET/ROM routing broadcasts are binary info so we can't treat as string. + // Originally, I just appended the information part. + // That was fine until NET/ROM, with binary data, came along. + // Now we set the information field after creating the packet object. + strlcat (stemp, ":", sizeof(stemp)); - strlcat (stemp, p, sizeof(stemp)); + strlcat (stemp, " ", sizeof(stemp)); //text_color_set(DW_COLOR_DEBUG); //dw_printf ("Transmit '%s'\n", stemp); pp = ax25_from_text (stemp, 1); - if (pp == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Failed to create frame from AGW 'V' message.\n"); + break; } - else { - /* This goes into the low priority queue because it is an original. */ + // Issue 550: Info part was one byte too long resulting in an extra nul character. + // Original calculation was data_len-ndigi*10 but we need to subtract one + // for first byte which is number of digipeaters. + ax25_set_info (pp, (unsigned char*)p, data_len - ndigi * 10 - 1); - /* Note that the protocol has no way to set the "has been used" */ - /* bits in the digipeater fields. */ + // Issue 527: NET/ROM routing broadcasts use PID 0xCF which was not preserved here. + ax25_set_pid (pp, pid); - /* This explains why the digipeating option is grayed out in */ - /* xastir when using the AGW interface. */ - /* The current version uses only the 'V' message, not 'K' for transmitting. */ + /* This goes into the low priority queue because it is an original. */ - tq_append (cmd.hdr.portx, TQ_PRIO_1_LO, pp); + /* Note that the protocol has no way to set the "has been used" */ + /* bits in the digipeater fields. */ - } + /* This explains why the digipeating option is grayed out in */ + /* xastir when using the AGW interface. */ + /* The current version uses only the 'V' message, not 'K' for transmitting. */ + + tq_append (cmd.hdr.portx, TQ_PRIO_1_LO, pp); } break; @@ -1784,11 +1810,17 @@ static THREAD_F cmd_listen_thread (void *arg) // 00=Port 1 // 16=Port 2 // - // I don't know what that means; we already a port number in the header. + // The seems to be redundant; we already a port number in the header. // Anyhow, the original code here added one to cmd.data to get the // first byte of the frame. Unfortunately, it did not subtract one from // cmd.hdr.data_len so we ended up sending an extra byte. + // TODO: Right now I just use the port (channel) number in the header. + // What if the second one is inconsistent? + // - Continue to ignore port number at beginning of data? + // - Use second one instead? + // - Error message if a mismatch? + memset (&alevel, 0xff, sizeof(alevel)); pp = ax25_from_frame ((unsigned char *)cmd.data+1, data_len - 1, alevel); @@ -1839,7 +1871,7 @@ static THREAD_F cmd_listen_thread (void *arg) // Connected mode can only be used with internal modems. - if (chan >= 0 && chan < MAX_CHANS && save_audio_config_p->chan_medium[chan] == MEDIUM_RADIO) { + if (chan >= 0 && chan < MAX_RADIO_CHANS && save_audio_config_p->chan_medium[chan] == MEDIUM_RADIO) { ok = 1; dlq_register_callsign (cmd.hdr.call_from, chan, client); } @@ -1868,7 +1900,7 @@ static THREAD_F cmd_listen_thread (void *arg) // Connected mode can only be used with internal modems. - if (chan >= 0 && chan < MAX_CHANS && save_audio_config_p->chan_medium[chan] == MEDIUM_RADIO) { + if (chan >= 0 && chan < MAX_RADIO_CHANS && save_audio_config_p->chan_medium[chan] == MEDIUM_RADIO) { dlq_unregister_callsign (cmd.hdr.call_from, chan, client); } else { @@ -1888,7 +1920,7 @@ static THREAD_F cmd_listen_thread (void *arg) unsigned char num_digi; /* Expect to be in range 1 to 7. Why not up to 8? */ char dcall[7][10]; } -#if 1 + // October 2017. gcc ??? complained: // warning: dereferencing pointer 'v' does break strict-aliasing rules // Try adding this attribute to get rid of the warning. @@ -1896,7 +1928,6 @@ static THREAD_F cmd_listen_thread (void *arg) // Let me know. Maybe we could put in a compiler version check here. __attribute__((__may_alias__)) -#endif *v = (struct via_info *)cmd.data; char callsigns[AX25_MAX_ADDRS][AX25_MAX_ADDR_LEN]; @@ -2005,19 +2036,7 @@ static THREAD_F cmd_listen_thread (void *arg) { int pid = cmd.hdr.pid; - (void)(pid); - /* The AGW protocol spec says, */ - /* "AX.25 PID 0x00 or 0xF0 for AX.25 0xCF NETROM and others" */ - - /* BUG: In theory, the AX.25 PID octet should be set from this. */ - /* All examples seen (above) have 0. */ - /* The AX.25 protocol spec doesn't list 0 as a valid value. */ - /* We always send 0xf0, meaning no layer 3. */ - /* Maybe we should have an ax25_set_pid function for cases when */ - /* it is neither 0 nor 0xf0. */ - char stemp[AX25_MAX_PACKET_LEN]; - packet_t pp; strlcpy (stemp, cmd.hdr.call_from, sizeof(stemp)); strlcat (stemp, ">", sizeof(stemp)); @@ -2025,21 +2044,29 @@ static THREAD_F cmd_listen_thread (void *arg) cmd.data[data_len] = '\0'; + // Issue 527: NET/ROM routing broadcasts are binary info so we can't treat as string. + // Originally, I just appended the information part as a text string. + // That was fine until NET/ROM, with binary data, came along. + // Now we set the information field after creating the packet object. + strlcat (stemp, ":", sizeof(stemp)); - strlcat (stemp, cmd.data, sizeof(stemp)); + strlcat (stemp, " ", sizeof(stemp)); //text_color_set(DW_COLOR_DEBUG); //dw_printf ("Transmit '%s'\n", stemp); - pp = ax25_from_text (stemp, 1); + packet_t pp = ax25_from_text (stemp, 1); if (pp == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Failed to create frame from AGW 'M' message.\n"); } - else { - tq_append (cmd.hdr.portx, TQ_PRIO_1_LO, pp); - } + + ax25_set_info (pp, (unsigned char*)cmd.data, data_len); + // Issue 527: NET/ROM routing broadcasts use PID 0xCF which was not preserved here. + ax25_set_pid (pp, pid); + + tq_append (cmd.hdr.portx, TQ_PRIO_1_LO, pp); } break; @@ -2060,7 +2087,7 @@ static THREAD_F cmd_listen_thread (void *arg) reply.hdr.data_len_NETLE = host2netle(4); int n = 0; - if (cmd.hdr.portx >= 0 && cmd.hdr.portx < MAX_CHANS) { + if (cmd.hdr.portx >= 0 && cmd.hdr.portx < MAX_RADIO_CHANS) { // Count both normal and expedited in transmit queue for given channel. n = tq_count (cmd.hdr.portx, -1, "", "", 0); } diff --git a/src/telemetry.c b/src/telemetry.c index 2a6c690c..d796cf14 100644 --- a/src/telemetry.c +++ b/src/telemetry.c @@ -71,7 +71,7 @@ #define T_NUM_ANALOG 5 /* Number of analog channels. */ #define T_NUM_DIGITAL 8 /* Number of digital channels. */ -#define T_STR_LEN 16 /* Max len for labels and units. */ +#define T_STR_LEN 32 /* Max len for labels and units. */ #define MAGIC1 0x5a1111a5 /* For checking storage allocation problems. */ diff --git a/src/tnctest.c b/src/tnctest.c index 0d4c26b4..f5fb8616 100644 --- a/src/tnctest.c +++ b/src/tnctest.c @@ -285,7 +285,7 @@ int main (int argc, char *argv[]) setlinebuf (stdout); #endif - start_dtime = dtime_now(); + start_dtime = dtime_monotonic(); /* * Extract command line args. @@ -615,7 +615,7 @@ void process_rec_data (int my_index, char *data) * and sent to a common function to check that they * all arrived in order. * - * Global Out: is_connected - Updated when connected/disconnected notifications are received. + * Global Out: is_connected - Updated when connected/disconnected notfications are received. * * Description: Perform any necessary configuration for the TNC then wait * for responses and process them. @@ -859,7 +859,7 @@ static void * tnc_thread_net (void *arg) * What did we get? */ - dnow = dtime_now(); + dnow = dtime_monotonic(); switch (mon_cmd.datakind) { @@ -943,7 +943,7 @@ static void * tnc_thread_net (void *arg) * and sent to a common function to check that they * all arrived in order. * - * Global Out: is_connected - Updated when connected/disconnected notifications are received. + * Global Out: is_connected - Updated when connected/disconnected notfications are received. * * Description: Perform any necessary configuration for the TNC then wait * for responses and process them. @@ -1038,12 +1038,12 @@ static void * tnc_thread_serial (void *arg) done = 1; } else if (ch == XOFF) { - double dnow = dtime_now(); + double dnow = dtime_monotonic(); printf("%*s[R %.3f] <XOFF>\n", my_index*column_width, "", dnow-start_dtime); busy[my_index] = 1; } else if (ch == XON) { - double dnow = dtime_now(); + double dnow = dtime_monotonic(); printf("%*s[R %.3f] <XON>\n", my_index*column_width, "", dnow-start_dtime); busy[my_index] = 0; } @@ -1070,7 +1070,7 @@ static void * tnc_thread_serial (void *arg) if (len > 0) { - double dnow = dtime_now(); + double dnow = dtime_monotonic(); printf("%*s[R %.3f] %s\n", my_index*column_width, "", dnow-start_dtime, result); @@ -1109,7 +1109,7 @@ static void * tnc_thread_serial (void *arg) static void tnc_connect (int from, int to) { - double dnow = dtime_now(); + double dnow = dtime_monotonic(); printf("%*s[T %.3f] *** Send connect request ***\n", from*column_width, "", dnow-start_dtime); @@ -1160,7 +1160,7 @@ static void tnc_connect (int from, int to) static void tnc_disconnect (int from, int to) { - double dnow = dtime_now(); + double dnow = dtime_monotonic(); printf("%*s[T %.3f] *** Send disconnect request ***\n", from*column_width, "", dnow-start_dtime); @@ -1201,7 +1201,7 @@ static void tnc_disconnect (int from, int to) static void tnc_reset (int from, int to) { - double dnow = dtime_now(); + double dnow = dtime_monotonic(); printf("%*s[T %.3f] *** Send reset ***\n", from*column_width, "", dnow-start_dtime); @@ -1232,7 +1232,7 @@ static void tnc_reset (int from, int to) static void tnc_send_data (int from, int to, char * data) { - double dnow = dtime_now(); + double dnow = dtime_monotonic(); printf("%*s[T %.3f] %s\n", from*column_width, "", dnow-start_dtime, data); @@ -1257,7 +1257,7 @@ static void tnc_send_data (int from, int to, char * data) else { // The assumption is that we are in CONVERS mode. - // The data should be terminated by carriage return. + // The data sould be terminated by carriage return. int timeout = 600; // 60 sec. I've seen it take more than 20. while (timeout > 0 && busy[from]) { diff --git a/src/tq.c b/src/tq.c index c656af54..0738eca1 100644 --- a/src/tq.c +++ b/src/tq.c @@ -52,10 +52,10 @@ #include "dedupe.h" #include "igate.h" #include "dtime_now.h" +#include "nettnc.h" - -static packet_t queue_head[MAX_CHANS][TQ_NUM_PRIO]; /* Head of linked list for each queue. */ +static packet_t queue_head[MAX_RADIO_CHANS][TQ_NUM_PRIO]; /* Head of linked list for each queue. */ static dw_mutex_t tq_mutex; /* Critical section for updating queues. */ @@ -63,15 +63,15 @@ static dw_mutex_t tq_mutex; /* Critical section for updating queues. */ #if __WIN32__ -static HANDLE wake_up_event[MAX_CHANS]; /* Notify transmit thread when queue not empty. */ +static HANDLE wake_up_event[MAX_RADIO_CHANS]; /* Notify transmit thread when queue not empty. */ #else -static pthread_cond_t wake_up_cond[MAX_CHANS]; /* Notify transmit thread when queue not empty. */ +static pthread_cond_t wake_up_cond[MAX_RADIO_CHANS]; /* Notify transmit thread when queue not empty. */ -static pthread_mutex_t wake_up_mutex[MAX_CHANS]; /* Required by cond_wait. */ +static pthread_mutex_t wake_up_mutex[MAX_RADIO_CHANS]; /* Required by cond_wait. */ -static int xmit_thread_is_waiting[MAX_CHANS]; +static int xmit_thread_is_waiting[MAX_RADIO_CHANS]; #endif @@ -128,7 +128,7 @@ void tq_init (struct audio_s *audio_config_p) save_audio_config_p = audio_config_p; - for (c=0; c<MAX_CHANS; c++) { + for (c=0; c<MAX_RADIO_CHANS; c++) { for (p=0; p<TQ_NUM_PRIO; p++) { queue_head[c][p] = NULL; } @@ -147,7 +147,7 @@ void tq_init (struct audio_s *audio_config_p) #if __WIN32__ - for (c = 0; c < MAX_CHANS; c++) { + for (c = 0; c < MAX_RADIO_CHANS; c++) { if (audio_config_p->chan_medium[c] == MEDIUM_RADIO) { @@ -164,7 +164,7 @@ void tq_init (struct audio_s *audio_config_p) #else int err; - for (c = 0; c < MAX_CHANS; c++) { + for (c = 0; c < MAX_RADIO_CHANS; c++) { xmit_thread_is_waiting[c] = 0; @@ -199,6 +199,9 @@ void tq_init (struct audio_s *audio_config_p) * New in 1.7: * Channel can be assigned to IGate rather than a radio. * + * New in 1.8: + * Channel can be assigned to a network TNC. + * * prio - Priority, use TQ_PRIO_0_HI for digipeated or * TQ_PRIO_1_LO for normal. * @@ -252,10 +255,13 @@ 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. +// New in 1.8: Assign a channel to external network TNC. +// Send somewhere else, rather than the transmit queue. #ifndef DIGITEST // avoid dtest link error - if (save_audio_config_p->chan_medium[chan] == MEDIUM_IGATE) { + if (save_audio_config_p->chan_medium[chan] == MEDIUM_IGATE || + save_audio_config_p->chan_medium[chan] == MEDIUM_NETTNC) { char ts[100]; // optional time stamp. @@ -274,21 +280,39 @@ void tq_append (int chan, int prio, packet_t pp) 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); + if (save_audio_config_p->chan_medium[chan] == MEDIUM_IGATE) { + + 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); + } + else { // network TNC + dw_printf ("[%d>nt%s] ", chan, ts); + dw_printf ("%s", stemp); /* stations followed by : */ + ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp)); + dw_printf ("\n"); + + nettnc_send_packet (chan, pp); + + } + ax25_delete(pp); return; } #endif + + + + // Normal case - put in queue for radio transmission. // Error if trying to transmit to a radio channel which was not configured. - if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->chan_medium[chan] == MEDIUM_NONE) { + if (chan < 0 || chan >= MAX_RADIO_CHANS || save_audio_config_p->chan_medium[chan] == MEDIUM_NONE) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan); dw_printf ("This is probably a client application error, not a problem with direwolf.\n"); @@ -490,7 +514,7 @@ void lm_data_request (int chan, int prio, packet_t pp) } #endif - if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->chan_medium[chan] != MEDIUM_RADIO) { + if (chan < 0 || chan >= MAX_RADIO_CHANS || save_audio_config_p->chan_medium[chan] != MEDIUM_RADIO) { // Connected mode is allowed only with internal modems. text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan); @@ -648,7 +672,7 @@ void lm_seize_request (int chan) #endif - if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->chan_medium[chan] != MEDIUM_RADIO) { + if (chan < 0 || chan >= MAX_RADIO_CHANS || save_audio_config_p->chan_medium[chan] != MEDIUM_RADIO) { // Connected mode is allowed only with internal modems. text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan); @@ -748,7 +772,7 @@ void tq_wait_while_empty (int chan) text_color_set(DW_COLOR_DEBUG); dw_printf ("tq_wait_while_empty (%d) : enter critical section\n", chan); #endif - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); dw_mutex_lock (&tq_mutex); @@ -944,7 +968,7 @@ static int tq_is_empty (int chan) { int p; - assert (chan >= 0 && chan < MAX_CHANS); + assert (chan >= 0 && chan < MAX_RADIO_CHANS); for (p=0; p<TQ_NUM_PRIO; p++) { @@ -1001,7 +1025,7 @@ int tq_count (int chan, int prio, char *source, char *dest, int bytes) // Array bounds check. FIXME: TODO: should have internal error instead of dying. - if (chan < 0 || chan >= MAX_CHANS || prio < 0 || prio >= TQ_NUM_PRIO) { + if (chan < 0 || chan >= MAX_RADIO_CHANS || prio < 0 || prio >= TQ_NUM_PRIO) { text_color_set(DW_COLOR_DEBUG); dw_printf ("INTERNAL ERROR - tq_count(%d, %d, \"%s\", \"%s\", %d)\n", chan, prio, source, dest, bytes); return (0); diff --git a/src/tt_user.c b/src/tt_user.c index a73d6a46..63043b2d 100644 --- a/src/tt_user.c +++ b/src/tt_user.c @@ -819,10 +819,10 @@ static void xmit_object_report (int i, int first_time) */ if (save_tt_config_p->obj_xmit_chan >= 0) { - strlcpy (stemp, save_audio_config_p->achan[save_tt_config_p->obj_xmit_chan].mycall, sizeof(stemp)); + strlcpy (stemp, save_audio_config_p->mycall[save_tt_config_p->obj_xmit_chan], sizeof(stemp)); } else { - strlcpy (stemp, save_audio_config_p->achan[save_tt_config_p->obj_recv_chan].mycall, sizeof(stemp)); + strlcpy (stemp, save_audio_config_p->mycall[save_tt_config_p->obj_recv_chan], sizeof(stemp)); } strlcat (stemp, ">", sizeof(stemp)); strlcat (stemp, APP_TOCALL, sizeof(stemp)); @@ -1134,7 +1134,7 @@ int main (int argc, char *argv[]) memset (&my_audio_config, 0, sizeof(my_audio_config)); - strlcpy (my_audio_config.achan[0].mycall, "WB2OSZ-15", sizeof(my_audio_config.achan[0].mycall)); + strlcpy (my_audio_config.mycall[0], "WB2OSZ-15", sizeof(my_audio_config.mycall[0])); /* Fake TT gateway config. */ diff --git a/src/xmit.c b/src/xmit.c index 13bbaecb..0d938efa 100644 --- a/src/xmit.c +++ b/src/xmit.c @@ -88,24 +88,24 @@ */ -static int xmit_slottime[MAX_CHANS]; /* Slot time in 10 mS units for persistence algorithm. */ +static int xmit_slottime[MAX_RADIO_CHANS]; /* Slot time in 10 mS units for persistence algorithm. */ -static int xmit_persist[MAX_CHANS]; /* Sets probability for transmitting after each */ +static int xmit_persist[MAX_RADIO_CHANS]; /* Sets probability for transmitting after each */ /* slot time delay. Transmit if a random number */ /* in range of 0 - 255 <= persist value. */ /* Otherwise wait another slot time and try again. */ -static int xmit_txdelay[MAX_CHANS]; /* After turning on the transmitter, */ +static int xmit_txdelay[MAX_RADIO_CHANS]; /* After turning on the transmitter, */ /* send "flags" for txdelay * 10 mS. */ -static int xmit_txtail[MAX_CHANS]; /* Amount of time to keep transmitting after we */ +static int xmit_txtail[MAX_RADIO_CHANS]; /* Amount of time to keep transmitting after we */ /* are done sending the data. This is to avoid */ /* dropping PTT too soon and chopping off the end */ /* of the frame. Again 10 mS units. */ -static int xmit_fulldup[MAX_CHANS]; /* Full duplex if non-zero. */ +static int xmit_fulldup[MAX_RADIO_CHANS]; /* Full duplex if non-zero. */ -static int xmit_bits_per_sec[MAX_CHANS]; /* Data transmission rate. */ +static int xmit_bits_per_sec[MAX_RADIO_CHANS]; /* Data transmission rate. */ /* Often called baud rate which is equivalent for */ /* 1200 & 9600 cases but could be different with other */ /* modulation techniques. */ @@ -211,11 +211,11 @@ void xmit_init (struct audio_s *p_modem, int debug_xmit_packet) int ad; #if __WIN32__ - HANDLE xmit_th[MAX_CHANS]; + HANDLE xmit_th[MAX_RADIO_CHANS]; #else //pthread_attr_t attr; //struct sched_param sp; - pthread_t xmit_tid[MAX_CHANS]; + pthread_t xmit_tid[MAX_RADIO_CHANS]; #endif //int e; @@ -247,7 +247,7 @@ void xmit_init (struct audio_s *p_modem, int debug_xmit_packet) * TODO1.2: Any reason to use global config rather than making a copy? */ - for (j=0; j<MAX_CHANS; j++) { + for (j=0; j<MAX_RADIO_CHANS; j++) { xmit_bits_per_sec[j] = p_modem->achan[j].baud; xmit_slottime[j] = p_modem->achan[j].slottime; xmit_persist[j] = p_modem->achan[j].persist; @@ -276,7 +276,7 @@ void xmit_init (struct audio_s *p_modem, int debug_xmit_packet) // underrun on the audio output device. - for (j=0; j<MAX_CHANS; j++) { + for (j=0; j<MAX_RADIO_CHANS; j++) { if (p_modem->chan_medium[j] == MEDIUM_RADIO) { #if __WIN32__ @@ -365,35 +365,35 @@ void xmit_init (struct audio_s *p_modem, int debug_xmit_packet) void xmit_set_txdelay (int channel, int value) { - if (channel >= 0 && channel < MAX_CHANS) { + if (channel >= 0 && channel < MAX_RADIO_CHANS) { xmit_txdelay[channel] = value; } } void xmit_set_persist (int channel, int value) { - if (channel >= 0 && channel < MAX_CHANS) { + if (channel >= 0 && channel < MAX_RADIO_CHANS) { xmit_persist[channel] = value; } } void xmit_set_slottime (int channel, int value) { - if (channel >= 0 && channel < MAX_CHANS) { + if (channel >= 0 && channel < MAX_RADIO_CHANS) { xmit_slottime[channel] = value; } } void xmit_set_txtail (int channel, int value) { - if (channel >= 0 && channel < MAX_CHANS) { + if (channel >= 0 && channel < MAX_RADIO_CHANS) { xmit_txtail[channel] = value; } } void xmit_set_fulldup (int channel, int value) { - if (channel >= 0 && channel < MAX_CHANS) { + if (channel >= 0 && channel < MAX_RADIO_CHANS) { xmit_fulldup[channel] = value; } } diff --git a/systemd/direwolf.service b/systemd/direwolf.service index c3380fac..8d0709f3 100644 --- a/systemd/direwolf.service +++ b/systemd/direwolf.service @@ -25,3 +25,4 @@ WantedBy=multi-user.target DefaultInstance=1 # alternate version: https://www.f4fxl.org/start-direwolf-at-boot-the-systemd-way/ +# or: https://groups.io/g/direwolf/message/9883 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 91e06a2c..37c2b2c8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -147,6 +147,7 @@ list(APPEND dtest_SOURCES ${CUSTOM_SRC_DIR}/tq.c ${CUSTOM_SRC_DIR}/textcolor.c ${CUSTOM_SRC_DIR}/decode_aprs.c + ${CUSTOM_SRC_DIR}/deviceid.c ${CUSTOM_SRC_DIR}/dwgpsnmea.c ${CUSTOM_SRC_DIR}/dwgps.c ${CUSTOM_SRC_DIR}/dwgpsd.c @@ -232,6 +233,7 @@ list(APPEND pftest_SOURCES ${CUSTOM_SRC_DIR}/textcolor.c ${CUSTOM_SRC_DIR}/fcs_calc.c ${CUSTOM_SRC_DIR}/decode_aprs.c + ${CUSTOM_SRC_DIR}/deviceid.c ${CUSTOM_SRC_DIR}/dwgpsnmea.c ${CUSTOM_SRC_DIR}/dwgps.c ${CUSTOM_SRC_DIR}/dwgpsd.c @@ -522,6 +524,7 @@ if(OPTIONAL_TEST) ${CUSTOM_SRC_DIR}/pfilter.c ${CUSTOM_SRC_DIR}/telemetry.c ${CUSTOM_SRC_DIR}/decode_aprs.c + ${CUSTOM_SRC_DIR}/deviceid.c ${CUSTOM_SRC_DIR}/dwgpsnmea.c ${CUSTOM_SRC_DIR}/dwgps.c ${CUSTOM_SRC_DIR}/dwgpsd.c @@ -574,6 +577,7 @@ if(OPTIONAL_TEST) ${CUSTOM_SRC_DIR}/fcs_calc.c ${CUSTOM_SRC_DIR}/ax25_pad.c ${CUSTOM_SRC_DIR}/decode_aprs.c + ${CUSTOM_SRC_DIR}/deviceid.c ${CUSTOM_SRC_DIR}/dwgpsnmea.c ${CUSTOM_SRC_DIR}/dwgps.c ${CUSTOM_SRC_DIR}/dwgpsd.c