diff --git a/CHANGES.md b/CHANGES.md
index 4b78ca1..0903e9e 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -2,6 +2,12 @@
# Revision History #
+## Version 1.8 -- Development Version
+
+### New Features: ###
+
+- [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.
+
## Version 1.7 -- October 2023 ##
diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt
index 7972cc2..11a82a4 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/tocalls.txt b/data/tocalls.txt
deleted file mode 100644
index 169c986..0000000
--- a/data/tocalls.txt
+++ /dev/null
@@ -1,326 +0,0 @@
-
-APRS TO-CALL VERSION NUMBERS 14 Dec 2021
----------------------------------------------------------------------
- WB4APR
-
-
-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
-
-
-
-
-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.
-
-
-
-
- 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
-
-
-
-
-
-
-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:
-
-
-
- SATERN - Salvation Army Altnet
- AFMARS - Airforce Mars
- AMARS - Army Mars
-
\ No newline at end of file
diff --git a/data/tocalls.yaml b/data/tocalls.yaml
new file mode 100644
index 0000000..adf26a1
--- /dev/null
+++ b/data/tocalls.yaml
@@ -0,0 +1,1481 @@
+#
+# 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: "_)"
+ 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
+
+#
+# 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: APAM??
+ vendor: Altus Metrum
+ model: AltOS
+ class: tracker
+
+ - tocall: APAND?
+ vendor: Open Source
+ model: APRSdroid
+ os: Android
+ class: app
+
+ - 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: 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: 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: 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: 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: APLG??
+ vendor: OE5BPA
+ model: LoRa Gateway/Digipeater
+ class: digi
+
+ - 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, CD2RXU
+ model: ESP32 LoRa iGate
+ class: igate
+ os: embedded
+ contact: richonguzman@gmail.com
+
+ - tocall: APLRT?
+ vendor: Ricardo, CD2RXU
+ 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: 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
+
+ - tocall: APNV1?
+ vendor: SQ8L
+ model: VP-Node
+ os: embedded
+
+ - 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: APOG7?
+ vendor: OpenGD77
+ model: OpenGD77
+ os: embedded
+ contact: Roger VK3KYY/G4KYF
+
+ - tocall: APOLU?
+ vendor: AMSAT-LU
+ model: Oscar
+ class: satellite
+
+ - 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: 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: APTKJ?
+ vendor: W9JAJ
+ model: ATTiny APRS Tracker
+ os: embedded
+
+ - 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: APY300
+ vendor: Yaesu
+ model: FTM-300D
+ class: rig
+
+ - tocall: APY400
+ vendor: Yaesu
+ model: FTM-400
+ class: rig
+
+ - tocall: APYS??
+ vendor: W2GMD
+ model: Python APRS
+ class: software
+
+ - 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/src/CMakeLists.txt b/src/CMakeLists.txt
index 5320a16..19dada4 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -33,6 +33,7 @@ list(APPEND direwolf_SOURCES
beacon.c
config.c
decode_aprs.c
+ deviceid.c
dedupe.c
demod_9600.c
demod_afsk.c
@@ -171,6 +172,7 @@ endif()
# decode_aprs
list(APPEND decode_aprs_SOURCES
decode_aprs.c
+ deviceid.c
ais.c
kiss_frame.c
ax25_pad.c
@@ -355,6 +357,7 @@ list(APPEND atest_SOURCES
ax25_pad.c
ax25_pad2.c
decode_aprs.c
+ deviceid.c
dwgpsnmea.c
dwgps.c
dwgpsd.c
diff --git a/src/decode_aprs.c b/src/decode_aprs.c
index ab93327..54c2839 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 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);
@@ -292,7 +291,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 +302,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,7 +1391,6 @@ 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));
@@ -1622,111 +1620,32 @@ 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));
+// 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 suffix is "_ "
- 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++; }
-
- 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++; }
-
-// ` should be used for message capable devices.
-
- else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ' ') { strlcpy (A->g_mfr, "Yaesu VX-8", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '"') { strlcpy (A->g_mfr, "Yaesu FTM-350", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '#') { strlcpy (A->g_mfr, "Yaesu VX-8G", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- 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; }
-
- else if (*pfirst == '`' && *(plast-1) == ' ' && *plast == 'X') { strlcpy (A->g_mfr, "AP510", sizeof(A->g_mfr)); pfirst++; plast-=2; }
-
- else if (*pfirst == '`' && *(plast-1) == '(' && *plast == '5') { strlcpy (A->g_mfr, "Anytone D578UV", sizeof(A->g_mfr)); pfirst++; plast-=2; }
- else if (*pfirst == '`' ) { strlcpy (A->g_mfr, "Generic Mic-Emsg", sizeof(A->g_mfr)); pfirst++; }
-
-// ' should be used for trackers (not message capable).
-
- 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; }
-
- 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; }
+ char mcomment[256];
+ strlcpy (mcomment, info + sizeof(struct aprs_mic_e_s), sizeof(mcomment));
+ if (mcomment[strlen(mcomment)-1] == '\r') {
+ mcomment[strlen(mcomment)-1] = '\0';
}
-/*
- * 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 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 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 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 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.
- */
+/* Now try to pick out manufacturer and other optional items. */
+/* The telemetry field, in the original spec, is no longer used. */
- if (plast > pfirst && pfirst[3] == '}') {
+ char trimmed[256]; // Comment with vendor/model removed.
+ deviceid_decode_mice (mcomment, trimmed, sizeof(trimmed), A->g_mfr, sizeof(A->g_mfr));
- 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]))
+
+// Possible altitude. 3 characters followed by }
+
+
+ if (strlen(trimmed) >=4 && trimmed[3] == '}') {
+
+ A->g_altitude_ft = DW_METERS_TO_FEET((trimmed[0]-33)*91*91 + (trimmed[1]-33)*91 + (trimmed[2]-33) - 10000);
+
+ if ( ! isdigit91(trimmed[0]) || ! isdigit91(trimmed[1]) || ! isdigit91(trimmed[2]))
{
if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR);
@@ -1736,12 +1655,13 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int
A->g_altitude_ft = G_UNKNOWN;
}
- pfirst += 4;
+ process_comment (A, mcomment+4, strlen(mcomment) - 4);
+ return;
}
- process_comment (A, (char*)pfirst, (int)(plast - pfirst) + 1);
+ process_comment (A, mcomment, strlen(mcomment));
-}
+} // end aprs_mic_e
/*------------------------------------------------------------------
@@ -4251,221 +4171,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 '%s'\n", n, tocalls[n].len, tocalls[n].prefix, tocalls[n].description);
- //}
- }
-
-
- for (n=0; ng_mfr, tocalls[n].description, sizeof(A->g_mfr));
- return;
- }
- }
-
-} /* end decode_tocall */
@@ -4513,7 +4218,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 +4248,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 +4417,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 +4777,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 +4895,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 +5041,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/deviceid.c b/src/deviceid.c
new file mode 100644
index 0000000..594b20e
--- /dev/null
+++ b/src/deviceid.c
@@ -0,0 +1,660 @@
+//
+// 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 .
+//
+
+
+/*------------------------------------------------------------------
+ *
+ * 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
+#include
+#include
+#include
+
+#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);
+
+
+// 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);
+
+
+// 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++) {
+ dw_printf ("Trying %s\n", search_locations[n]);
+ 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)
+{
+ *device = '\0';
+
+ 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;
+ }
+ }
+
+ 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.
+ *
+ * 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.
+ *
+ * device - Vendor and model.
+ *
+ * Description: This 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
+ *
+ *------------------------------------------------------------------*/
+
+// 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)
+{
+ *device = '\0';
+
+ 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), //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);
+
+} // end deviceid_decode_mice
+
+// end deviceid.c
diff --git a/src/deviceid.h b/src/deviceid.h
new file mode 100644
index 0000000..d7a1b30
--- /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/direwolf.c b/src/direwolf.c
index c8bb3a1..2dfa58d 100644
--- a/src/direwolf.c
+++ b/src/direwolf.c
@@ -129,6 +129,7 @@
#include "dwsock.h"
#include "dns_sd_dw.h"
#include "dlq.h" // for fec_type_t definition.
+#include "deviceid.h"
//static int idx_decoded = 0;
@@ -985,6 +986,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) {
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 91e06a2..da732ac 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.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