Use tocalls.yaml rather than tocalls.txt which is no longer maintained.

This commit is contained in:
wb2osz 2023-12-23 15:57:03 +00:00
parent d679e06846
commit 46f31d4453
10 changed files with 2203 additions and 657 deletions

View File

@ -2,6 +2,12 @@
# Revision History # # 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 ## ## Version 1.7 -- October 2023 ##

View File

@ -16,7 +16,7 @@
# #
# The destination field is often used to identify the manufacturer/model. # The destination field is often used to identify the manufacturer/model.
# These are not hardcoded into Dire Wolf. Instead they are read from # 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, # The original permanent symbols are built in but the "new" symbols,
# using overlays, are often updated. These are also read from files. # using overlays, are often updated. These are also read from files.
@ -25,17 +25,17 @@
include(ExternalProject) include(ExternalProject)
set(TOCALLS_TXT "tocalls.txt") set(TOCALLS_YAML "tocalls.yaml")
set(SYMBOLS-NEW_TXT "symbols-new.txt") set(SYMBOLS-NEW_TXT "symbols-new.txt")
set(SYMBOLSX_TXT "symbolsX.txt") set(SYMBOLSX_TXT "symbolsX.txt")
set(CUSTOM_BINARY_DATA_DIR "${CMAKE_BINARY_DIR}/data") set(CUSTOM_BINARY_DATA_DIR "${CMAKE_BINARY_DIR}/data")
# we can also move to a separate cmake file and use file(download) # we can also move to a separate cmake file and use file(download)
# see conf/install_conf.cmake as example # 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}/${SYMBOLS-NEW_TXT}" DESTINATION "${CUSTOM_BINARY_DATA_DIR}")
file(COPY "${CUSTOM_DATA_DIR}/${SYMBOLSX_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}/${SYMBOLS-NEW_TXT}" DESTINATION ${INSTALL_DATA_DIR})
install(FILES "${CUSTOM_BINARY_DATA_DIR}/${SYMBOLSX_TXT}" DESTINATION ${INSTALL_DATA_DIR}) install(FILES "${CUSTOM_BINARY_DATA_DIR}/${SYMBOLSX_TXT}" DESTINATION ${INSTALL_DATA_DIR})

View File

@ -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>

1481
data/tocalls.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -33,6 +33,7 @@ list(APPEND direwolf_SOURCES
beacon.c beacon.c
config.c config.c
decode_aprs.c decode_aprs.c
deviceid.c
dedupe.c dedupe.c
demod_9600.c demod_9600.c
demod_afsk.c demod_afsk.c
@ -171,6 +172,7 @@ endif()
# decode_aprs # decode_aprs
list(APPEND decode_aprs_SOURCES list(APPEND decode_aprs_SOURCES
decode_aprs.c decode_aprs.c
deviceid.c
ais.c ais.c
kiss_frame.c kiss_frame.c
ax25_pad.c ax25_pad.c
@ -355,6 +357,7 @@ list(APPEND atest_SOURCES
ax25_pad.c ax25_pad.c
ax25_pad2.c ax25_pad2.c
decode_aprs.c decode_aprs.c
deviceid.c
dwgpsnmea.c dwgpsnmea.c
dwgps.c dwgps.c
dwgpsd.c dwgpsd.c

View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // 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 // 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 // it under the terms of the GNU General Public License as published by
@ -56,7 +56,7 @@
#include "decode_aprs.h" #include "decode_aprs.h"
#include "telemetry.h" #include "telemetry.h"
#include "ais.h" #include "ais.h"
#include "deviceid.h"
#define TRUE 1 #define TRUE 1
#define FALSE 0 #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 time_t get_timestamp (decode_aprs_t *A, char *p);
static int get_maidenhead (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 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 get_symbol (decode_aprs_t *A, char dti, char *src, char *dest);
static void process_comment (decode_aprs_t *A, char *pstart, int clen); 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. * 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; break;
default: default:
decode_tocall (A, A->g_dest); deviceid_decode_dest (A->g_dest, A->g_mfr, sizeof(A->g_mfr));
break; 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; int cust_msg = 0;
const char *std_text[8] = {"Emergency", "Priority", "Special", "Committed", "Returning", "In Service", "En Route", "Off Duty" }; 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" }; 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)); 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; A->g_course = n;
// 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 "_ "
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';
}
/* Now try to pick out manufacturer and other optional items. */ /* Now try to pick out manufacturer and other optional items. */
/* The telemetry field, in the original spec, is no longer used. */ /* The telemetry field, in the original spec, is no longer used. */
strlcpy (A->g_mfr, "Unknown manufacturer", sizeof(A->g_mfr)); char trimmed[256]; // Comment with vendor/model removed.
deviceid_decode_mice (mcomment, trimmed, sizeof(trimmed), A->g_mfr, 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) == '\'') // Possible altitude. 3 characters followed by }
// Last Updated Dec. 2021
// This does not change very often but I'm wondering if we could parse if (strlen(trimmed) >=4 && trimmed[3] == '}') {
// 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. A->g_altitude_ft = DW_METERS_TO_FEET((trimmed[0]-33)*91*91 + (trimmed[1]-33)*91 + (trimmed[2]-33) - 10000);
if (isT(*pfirst)) { if ( ! isdigit91(trimmed[0]) || ! isdigit91(trimmed[1]) || ! isdigit91(trimmed[2]))
// "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; }
}
/*
* 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 (plast > pfirst && pfirst[3] == '}') {
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) { if ( ! A->g_quiet) {
text_color_set(DW_COLOR_ERROR); 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; 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<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 +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. * clen - Length of comment or -1 to take it all.
* *
* Outputs: A->g_telemetry - Base 91 telemetry |ss1122| * 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_lat - Might be adjusted from !DAO!
* A->g_lon - Might be adjusted from !DAO! * A->g_lon - Might be adjusted from !DAO!
* A->g_aprstt_loc - Private extension to !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. * Protocol reference, end of chapter 6.
* *
* /A=123456 Altitude * /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? * 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); 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) { if (e) {
regerror (e, &alt_re, emsg, sizeof(emsg)); regerror (e, &alt_re, emsg, sizeof(emsg));
dw_printf("%s:%d: %s\n", __FILE__, __LINE__, 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) 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 * 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. * Inputs: stdin for raw data to decode.
* This is in the usual display format either from * 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. // If you don't like the text colors, use 0 instead of 1 here.
text_color_init(1); text_color_init(1);
text_color_set(DW_COLOR_INFO); text_color_set(DW_COLOR_INFO);
deviceid_init();
while (fgets(stuff, sizeof(stuff), stdin) != NULL) while (fgets(stuff, sizeof(stuff), stdin) != NULL)
{ {

660
src/deviceid.c Normal file
View File

@ -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 <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);
// 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

6
src/deviceid.h Normal file
View File

@ -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);

View File

@ -129,6 +129,7 @@
#include "dwsock.h" #include "dwsock.h"
#include "dns_sd_dw.h" #include "dns_sd_dw.h"
#include "dlq.h" // for fec_type_t definition. #include "dlq.h" // for fec_type_t definition.
#include "deviceid.h"
//static int idx_decoded = 0; //static int idx_decoded = 0;
@ -985,6 +986,7 @@ int main (int argc, char *argv[])
* Files not supported at this time. * Files not supported at this time.
* Can always "cat" the file and pipe it into stdin. * Can always "cat" the file and pipe it into stdin.
*/ */
deviceid_init();
err = audio_open (&audio_config); err = audio_open (&audio_config);
if (err < 0) { if (err < 0) {

View File

@ -147,6 +147,7 @@ list(APPEND dtest_SOURCES
${CUSTOM_SRC_DIR}/tq.c ${CUSTOM_SRC_DIR}/tq.c
${CUSTOM_SRC_DIR}/textcolor.c ${CUSTOM_SRC_DIR}/textcolor.c
${CUSTOM_SRC_DIR}/decode_aprs.c ${CUSTOM_SRC_DIR}/decode_aprs.c
${CUSTOM_SRC_DIR}/deviceid.c
${CUSTOM_SRC_DIR}/dwgpsnmea.c ${CUSTOM_SRC_DIR}/dwgpsnmea.c
${CUSTOM_SRC_DIR}/dwgps.c ${CUSTOM_SRC_DIR}/dwgps.c
${CUSTOM_SRC_DIR}/dwgpsd.c ${CUSTOM_SRC_DIR}/dwgpsd.c
@ -232,6 +233,7 @@ list(APPEND pftest_SOURCES
${CUSTOM_SRC_DIR}/textcolor.c ${CUSTOM_SRC_DIR}/textcolor.c
${CUSTOM_SRC_DIR}/fcs_calc.c ${CUSTOM_SRC_DIR}/fcs_calc.c
${CUSTOM_SRC_DIR}/decode_aprs.c ${CUSTOM_SRC_DIR}/decode_aprs.c
${CUSTOM_SRC_DIR}/deviceid.c
${CUSTOM_SRC_DIR}/dwgpsnmea.c ${CUSTOM_SRC_DIR}/dwgpsnmea.c
${CUSTOM_SRC_DIR}/dwgps.c ${CUSTOM_SRC_DIR}/dwgps.c
${CUSTOM_SRC_DIR}/dwgpsd.c ${CUSTOM_SRC_DIR}/dwgpsd.c
@ -522,6 +524,7 @@ if(OPTIONAL_TEST)
${CUSTOM_SRC_DIR}/pfilter.c ${CUSTOM_SRC_DIR}/pfilter.c
${CUSTOM_SRC_DIR}/telemetry.c ${CUSTOM_SRC_DIR}/telemetry.c
${CUSTOM_SRC_DIR}/decode_aprs.c ${CUSTOM_SRC_DIR}/decode_aprs.c
${CUSTOM_SRC_DIR}/deviceid.c.c
${CUSTOM_SRC_DIR}/dwgpsnmea.c ${CUSTOM_SRC_DIR}/dwgpsnmea.c
${CUSTOM_SRC_DIR}/dwgps.c ${CUSTOM_SRC_DIR}/dwgps.c
${CUSTOM_SRC_DIR}/dwgpsd.c ${CUSTOM_SRC_DIR}/dwgpsd.c
@ -574,6 +577,7 @@ if(OPTIONAL_TEST)
${CUSTOM_SRC_DIR}/fcs_calc.c ${CUSTOM_SRC_DIR}/fcs_calc.c
${CUSTOM_SRC_DIR}/ax25_pad.c ${CUSTOM_SRC_DIR}/ax25_pad.c
${CUSTOM_SRC_DIR}/decode_aprs.c ${CUSTOM_SRC_DIR}/decode_aprs.c
${CUSTOM_SRC_DIR}/deviceid.c
${CUSTOM_SRC_DIR}/dwgpsnmea.c ${CUSTOM_SRC_DIR}/dwgpsnmea.c
${CUSTOM_SRC_DIR}/dwgps.c ${CUSTOM_SRC_DIR}/dwgps.c
${CUSTOM_SRC_DIR}/dwgpsd.c ${CUSTOM_SRC_DIR}/dwgpsd.c