//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011, 2012, 2013, 2014, 2015 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
/*------------------------------------------------------------------
*
* File: symbols.c
*
* Purpose: Functions related to the APRS symbols
*
*------------------------------------------------------------------*/
#include "direwolf.h"
#include
#include
#include
#include
#include
#include "textcolor.h"
#include "symbols.h"
#include "tt_text.h"
/*
* APRS symbol tables.
*
* Derived from http://www.aprs.org/symbols/symbolsX.txt
* version of 20 Oct 2009.
*/
/*
* Primary symbol table.
*/
#define SYMTAB_SIZE 95
static const struct {
char xy[3];
char *description;
} primary_symtab[SYMTAB_SIZE] = {
/* 00 */ { "~~", "--no-symbol--" },
/* ! 01 */ { "BB", "Police, Sheriff" },
/* " 02 */ { "BC", "reserved (was rain)" },
/* # 03 */ { "BD", "DIGI (white center)" },
/* $ 04 */ { "BE", "PHONE" },
/* % 05 */ { "BF", "DX CLUSTER" },
/* & 06 */ { "BG", "HF GATEway" },
/* ' 07 */ { "BH", "Small AIRCRAFT" },
/* ( 08 */ { "BI", "Mobile Satellite Station" },
/* ) 09 */ { "BJ", "Wheelchair (handicapped)" },
/* * 10 */ { "BK", "SnowMobile" },
/* + 11 */ { "BL", "Red Cross" },
/* , 12 */ { "BM", "Boy Scouts" },
/* - 13 */ { "BN", "House QTH (VHF)" },
/* . 14 */ { "BO", "X" },
/* / 15 */ { "BP", "Red Dot" },
/* 0 16 */ { "P0", "# circle (obsolete)" },
/* 1 17 */ { "P1", "TBD" },
/* 2 18 */ { "P2", "TBD" },
/* 3 19 */ { "P3", "TBD" },
/* 4 20 */ { "P4", "TBD" },
/* 5 21 */ { "P5", "TBD" },
/* 6 22 */ { "P6", "TBD" },
/* 7 23 */ { "P7", "TBD" },
/* 8 24 */ { "P8", "TBD" },
/* 9 25 */ { "P9", "TBD" },
/* : 26 */ { "MR", "FIRE" },
/* ; 27 */ { "MS", "Campground (Portable ops)" },
/* < 28 */ { "MT", "Motorcycle" },
/* = 29 */ { "MU", "RAILROAD ENGINE" },
/* > 30 */ { "MV", "CAR" },
/* ? 31 */ { "MW", "SERVER for Files" },
/* @ 32 */ { "MX", "HC FUTURE predict (dot)" },
/* A 33 */ { "PA", "Aid Station" },
/* B 34 */ { "PB", "BBS or PBBS" },
/* C 35 */ { "PC", "Canoe" },
/* D 36 */ { "PD", "" },
/* E 37 */ { "PE", "EYEBALL (Eye catcher!)" },
/* F 38 */ { "PF", "Farm Vehicle (tractor)" },
/* G 39 */ { "PG", "Grid Square (6 digit)" },
/* H 40 */ { "PH", "HOTEL (blue bed symbol)" },
/* I 41 */ { "PI", "TcpIp on air network stn" },
/* J 42 */ { "PJ", "" },
/* K 43 */ { "PK", "School" },
/* L 44 */ { "PL", "PC user" },
/* M 45 */ { "PM", "MacAPRS" },
/* N 46 */ { "PN", "NTS Station" },
/* O 47 */ { "PO", "BALLOON" },
/* P 48 */ { "PP", "Police" },
/* Q 49 */ { "PQ", "TBD" },
/* R 50 */ { "PR", "REC. VEHICLE" },
/* S 51 */ { "PS", "SHUTTLE" },
/* T 52 */ { "PT", "SSTV" },
/* U 53 */ { "PU", "BUS" },
/* V 54 */ { "PV", "ATV" },
/* W 55 */ { "PW", "National WX Service Site" },
/* X 56 */ { "PX", "HELO" },
/* Y 57 */ { "PY", "YACHT (sail)" },
/* Z 58 */ { "PZ", "WinAPRS" },
/* [ 59 */ { "HS", "Human/Person (HT)" },
/* \ 60 */ { "HT", "TRIANGLE(DF station)" },
/* ] 61 */ { "HU", "MAIL/PostOffice(was PBBS)" },
/* ^ 62 */ { "HV", "LARGE AIRCRAFT" },
/* _ 63 */ { "HW", "WEATHER Station (blue)" },
/* ` 64 */ { "HX", "Dish Antenna" },
/* a 65 */ { "LA", "AMBULANCE" },
/* b 66 */ { "LB", "BIKE" },
/* c 67 */ { "LC", "Incident Command Post" },
/* d 68 */ { "LD", "Fire dept" },
/* e 69 */ { "LE", "HORSE (equestrian)" },
/* f 70 */ { "LF", "FIRE TRUCK" },
/* g 71 */ { "LG", "Glider" },
/* h 72 */ { "LH", "HOSPITAL" },
/* i 73 */ { "LI", "IOTA (islands on the air)" },
/* j 74 */ { "LJ", "JEEP" },
/* k 75 */ { "LK", "TRUCK" },
/* l 76 */ { "LL", "Laptop" },
/* m 77 */ { "LM", "Mic-E Repeater" },
/* n 78 */ { "LN", "Node (black bulls-eye)" },
/* o 79 */ { "LO", "EOC" },
/* p 80 */ { "LP", "ROVER (puppy, or dog)" },
/* q 81 */ { "LQ", "GRID SQ shown above 128 m" },
/* r 82 */ { "LR", "Repeater" },
/* s 83 */ { "LS", "SHIP (pwr boat)" },
/* t 84 */ { "LT", "TRUCK STOP" },
/* u 85 */ { "LU", "TRUCK (18 wheeler)" },
/* v 86 */ { "LV", "VAN" },
/* w 87 */ { "LW", "WATER station" },
/* x 88 */ { "LX", "xAPRS (Unix)" },
/* y 89 */ { "LY", "YAGI @ QTH" },
/* z 90 */ { "LZ", "TBD" },
/* { 91 */ { "J1", "" },
/* | 92 */ { "J2", "TNC Stream Switch" },
/* } 93 */ { "J3", "" },
/* ~ 94 */ { "J3", "TNC Stream Switch" } };
/*
* Alternate symbol table.
*/
static const struct {
char xy[3];
char *description;
} alternate_symtab[SYMTAB_SIZE] = {
/* 00 */ { "~~", "--no-symbol--" },
/* ! 01 */ { "OB", "EMERGENCY (!)" },
/* " 02 */ { "OC", "reserved" },
/* # 03 */ { "OD", "OVERLAY DIGI (green star)" },
/* $ 04 */ { "OE", "Bank or ATM (green box)" },
/* % 05 */ { "OF", "Power Plant with overlay" },
/* & 06 */ { "OG", "I=Igte IGate R=RX T=1hopTX 2=2hopTX" },
/* ' 07 */ { "OH", "Crash (& now Incident sites)" },
/* ( 08 */ { "OI", "CLOUDY (other clouds w ovrly)" },
/* ) 09 */ { "OJ", "Firenet MEO, MODIS Earth Obs." },
/* * 10 */ { "OK", "SNOW (& future ovrly codes)" },
/* + 11 */ { "OL", "Church" },
/* , 12 */ { "OM", "Girl Scouts" },
/* - 13 */ { "ON", "House (H=HF) (O = Op Present)" },
/* . 14 */ { "OO", "Ambiguous (Big Question mark)" },
/* / 15 */ { "OP", "Waypoint Destination" },
/* 0 16 */ { "A0", "CIRCLE (E/I/W=IRLP/Echolink/WIRES)" },
/* 1 17 */ { "A1", "" },
/* 2 18 */ { "A2", "" },
/* 3 19 */ { "A3", "" },
/* 4 20 */ { "A4", "" },
/* 5 21 */ { "A5", "" },
/* 6 22 */ { "A6", "" },
/* 7 23 */ { "A7", "" },
/* 8 24 */ { "A8", "802.11 or other network node" },
/* 9 25 */ { "A9", "Gas Station (blue pump)" },
/* : 26 */ { "NR", "Hail (& future ovrly codes)" },
/* ; 27 */ { "NS", "Park/Picnic area" },
/* < 28 */ { "NT", "ADVISORY (one WX flag)" },
/* = 29 */ { "NU", "APRStt Touchtone (DTMF users)" },
/* > 30 */ { "NV", "OVERLAYED CAR" },
/* ? 31 */ { "NW", "INFO Kiosk (Blue box with ?)" },
/* @ 32 */ { "NX", "HURICANE/Trop-Storm" },
/* A 33 */ { "AA", "overlayBOX DTMF & RFID & XO" },
/* B 34 */ { "AB", "Blwng Snow (& future codes)" },
/* C 35 */ { "AC", "Coast Guard" },
/* D 36 */ { "AD", "Drizzle (proposed APRStt)" },
/* E 37 */ { "AE", "Smoke (& other vis codes)" },
/* F 38 */ { "AF", "Freezng rain (&future codes)" },
/* G 39 */ { "AG", "Snow Shwr (& future ovrlys)" },
/* H 40 */ { "AH", "Haze (& Overlay Hazards)" },
/* I 41 */ { "AI", "Rain Shower" },
/* J 42 */ { "AJ", "Lightening (& future ovrlys)" },
/* K 43 */ { "AK", "Kenwood HT (W)" },
/* L 44 */ { "AL", "Lighthouse" },
/* M 45 */ { "AM", "MARS (A=Army,N=Navy,F=AF)" },
/* N 46 */ { "AN", "Navigation Buoy" },
/* O 47 */ { "AO", "Rocket" },
/* P 48 */ { "AP", "Parking" },
/* Q 49 */ { "AQ", "QUAKE" },
/* R 50 */ { "AR", "Restaurant" },
/* S 51 */ { "AS", "Satellite/Pacsat" },
/* T 52 */ { "AT", "Thunderstorm" },
/* U 53 */ { "AU", "SUNNY" },
/* V 54 */ { "AV", "VORTAC Nav Aid" },
/* W 55 */ { "AW", "# NWS site (NWS options)" },
/* X 56 */ { "AX", "Pharmacy Rx (Apothicary)" },
/* Y 57 */ { "AY", "Radios and devices" },
/* Z 58 */ { "AZ", "" },
/* [ 59 */ { "DS", "W.Cloud (& humans w Ovrly)" },
/* \ 60 */ { "DT", "New overlayable GPS symbol" },
/* ] 61 */ { "DU", "" },
/* ^ 62 */ { "DV", "# Aircraft (shows heading)" },
/* _ 63 */ { "DW", "# WX site (green digi)" },
/* ` 64 */ { "DX", "Rain (all types w ovrly)" },
/* a 65 */ { "SA", "ARRL, ARES, WinLINK" },
/* b 66 */ { "SB", "Blwng Dst/Snd (& others)" },
/* c 67 */ { "SC", "CD triangle RACES/SATERN/etc" },
/* d 68 */ { "SD", "DX spot by callsign" },
/* e 69 */ { "SE", "Sleet (& future ovrly codes)" },
/* f 70 */ { "SF", "Funnel Cloud" },
/* g 71 */ { "SG", "Gale Flags" },
/* h 72 */ { "SH", "Store. or HAMFST Hh=HAM store" },
/* i 73 */ { "SI", "BOX or points of Interest" },
/* j 74 */ { "SJ", "WorkZone (Steam Shovel)" },
/* k 75 */ { "SK", "Special Vehicle SUV,ATV,4x4" },
/* l 76 */ { "SL", "Areas (box,circles,etc)" },
/* m 77 */ { "SM", "Value Sign (3 digit display)" },
/* n 78 */ { "SN", "OVERLAY TRIANGLE" },
/* o 79 */ { "SO", "small circle" },
/* p 80 */ { "SP", "Prtly Cldy (& future ovrlys)" },
/* q 81 */ { "SQ", "" },
/* r 82 */ { "SR", "Restrooms" },
/* s 83 */ { "SS", "OVERLAY SHIP/boat (top view)" },
/* t 84 */ { "ST", "Tornado" },
/* u 85 */ { "SU", "OVERLAYED TRUCK" },
/* v 86 */ { "SV", "OVERLAYED Van" },
/* w 87 */ { "SW", "Flooding" },
/* x 88 */ { "SX", "Wreck or Obstruction ->X<-" },
/* y 89 */ { "SY", "Skywarn" },
/* z 90 */ { "SZ", "OVERLAYED Shelter" },
/* { 91 */ { "Q1", "Fog (& future ovrly codes)" },
/* | 92 */ { "Q2", "TNC Stream Switch" },
/* } 93 */ { "Q3", "" },
/* ~ 94 */ { "Q4", "TNC Stream Switch" } };
// Make sure the array is null terminated.
// If search order is changed, do the same in decode_aprs.c
static const char *search_locations[] = {
(const char *) "symbols-new.txt",
(const char *) "../data/symbols-new.txt",
#ifndef __WIN32__
(const char *) "/usr/local/share/direwolf/symbols-new.txt",
(const char *) "/usr/share/direwolf/symbols-new.txt",
#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/symbols-new.txt",
#endif
(const char *) NULL
};
/*------------------------------------------------------------------
*
* Function: symbols_init
*
* Purpose: Initialization for functions related to symbols.
*
* Inputs:
*
* Global output:
* new_sym_ptr
* new_sym_size
* new_sym_len
*
* Description: The primary and alternate symbol tables are constant
* so they are hardcoded.
* However the "new" sysmbols, which give new meanings to
* overlayed symbols, are always evolving.
* 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/symbols/symbols-new.txt
*
* Windows version: File must be in current working directory.
*
* Linux version: Search order is current working directory
* then /usr/share/direwolf directory.
*
*------------------------------------------------------------------*/
#define NEW_SYM_INIT_SIZE 20
#define NEW_SYM_DESC_LEN 29
typedef struct new_sym_s {
char overlay;
char symbol;
char description[NEW_SYM_DESC_LEN+1];
} new_sym_t;
static new_sym_t *new_sym_ptr = NULL; /* Dynamically allocated array. */
static int new_sym_size = 0; /* Number of elements allocated. */
static int new_sym_len = 0; /* Number of elements used. */
void symbols_init (void)
{
FILE *fp = NULL;
/*
* We only care about lines with this format:
*
* Column 1 - overlay character of / \ upper case or digit
* Column 2 - symbol in range of ! thru ~
* Column 3 - space
* Column 4 - equal sign
* Column 5 - space
* Column 6 - Start of description.
*/
#define COL1_OVERLAY 0
#define COL2_SYMBOL 1
#define COL3_SP 2
#define COL4_EQUAL 3
#define COL5_SP 4
#define COL6_DESC 5
char stuff[200];
int j;
#define GOOD_LINE(x) (strlen(x) > 6 && \
(x[COL1_OVERLAY] == '/' || x[COL1_OVERLAY] == '\\' || isupper(x[COL1_OVERLAY]) || isdigit(x[COL1_OVERLAY])) \
&& x[COL2_SYMBOL] >= '!' && x[COL2_SYMBOL] <= '~' \
&& x[COL3_SP] == ' ' && x[COL4_EQUAL] == '=' && x[COL5_SP] == ' ' && x[COL6_DESC] != ' ')
if (new_sym_ptr != NULL) {
return; /* was called already. */
}
// If search strategy changes, be sure to keep decode_tocall in sync.
fp = NULL;
j = 0;
do {
if (search_locations[j] == NULL) break;
fp = fopen(search_locations[j++], "r");
} while (fp == NULL);
if (fp == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Warning: Could not open 'symbols-new.txt'.\n");
dw_printf ("The \"new\" overlayed character information will not be available.\n");
new_sym_size = 1;
new_sym_ptr = calloc(new_sym_size, sizeof(new_sym_t)); /* Don't try again. */
new_sym_len = 0;
return;
}
/*
* Count number of interesting lines and allocate storage.
*/
while (fgets(stuff, sizeof(stuff), fp) != NULL) {
if (GOOD_LINE(stuff)) {
new_sym_size++;
}
}
new_sym_ptr = calloc(new_sym_size, sizeof(new_sym_t));
/*
* Rewind, read again, and save contents of interesting lines.
*/
rewind (fp);
while (fgets(stuff, sizeof(stuff), fp) != NULL) {
if (GOOD_LINE(stuff)) {
for (j = strlen(stuff+COL6_DESC) - 1; j>=0 && stuff[COL6_DESC+j] <= ' '; j--) {
stuff[COL6_DESC+j] = '\0';
}
new_sym_ptr[new_sym_len].overlay = stuff[COL1_OVERLAY];
new_sym_ptr[new_sym_len].symbol = stuff[COL2_SYMBOL];
strncpy(new_sym_ptr[new_sym_len].description, stuff+COL6_DESC, NEW_SYM_DESC_LEN);
new_sym_len++;
}
}
fclose (fp);
assert (new_sym_len == new_sym_size);
#if 0
for (j=0; j', /* 9 - Car */
'<', /* 10 - Motorcycle */
'O', /* 11 - Ballon */
'j', /* 12 - Jeep */
'R', /* 13 - Recreational Vehicle */
'k', /* 14 - Truck */
'v' /* 15 - Van */
};
void symbols_from_dest_or_src (char dti, char *src, char *dest, char *symtab, char *symbol)
{
char *p;
/*
* This part does not apply to MIC-E format because the destination
* is used to encode latitude and other information.
*/
if (dti != '\'' && dti != '`') {
/*
* For GPSCnn, nn is the index into the primary symbol table.
*/
if (strncmp(dest, "GPSC", 4) == 0)
{
int nn;
nn = atoi(dest+4);
if (nn >= 1 && nn <= 94) {
*symtab = '/'; /* Primary */
*symbol = ' ' + nn;
return;
}
}
/*
* For GPSEnn, nn is the index into the primary symbol table.
*/
if (strncmp(dest, "GPSE", 4) == 0)
{
int nn;
nn = atoi(dest+4);
if (nn >= 1 && nn <= 94) {
*symtab = '\\'; /* Alternate. */
*symbol = ' ' + nn;
return;
}
}
/*
* For GPSxy or SPCxy or SYMxy, look up xy in the translation tables.
* First search the primary table.
*/
if (strncmp(dest, "GPS", 3) == 0 ||
strncmp(dest, "SPC", 3) == 0 ||
strncmp(dest, "SYM", 3) == 0)
{
char xy[3];
int nn;
xy[0] = dest[3];
xy[1] = dest[4];
xy[2] = '\0';
for (nn = 1; nn <= 94; nn++) {
if (strcmp(xy, primary_symtab[nn].xy) == 0) {
*symtab = '/'; /* Primary. */
*symbol = ' ' + nn;
return;
}
}
}
/*
* Next, search the alternate table.
* This time, we can have the format ...xyz, where z is an overlay character.
* Only upper case letters and digits are valid overlay characters.
*/
if (strncmp(dest, "GPS", 3) == 0 ||
strncmp(dest, "SPC", 3) == 0 ||
strncmp(dest, "SYM", 3) == 0)
{
char xy[3];
char z;
int nn;
xy[0] = dest[3];
xy[1] = dest[4];
xy[2] = '\0';
z = dest[5];
for (nn = 1; nn <= 94; nn++) {
if (strcmp(xy, alternate_symtab[nn].xy) == 0) {
*symtab = '\\'; /* Alternate. */
*symbol = ' ' + nn;
if (isupper((int)z) || isdigit((int)z)) {
*symtab = z;
}
return;
}
}
}
} /* end not MIC-E */
/*
* When all else fails, use source SSID.
*/
p = strchr (src, '-');
if (p != NULL)
{
int ssid;
ssid = atoi(p+1);
if (ssid >= 1 && ssid <= 15) {
*symtab = '/'; /* All in Primary table. */
*symbol = ssid_to_sym[ssid];
return;
}
}
} /* symbols_from_dest_or_src */
/*------------------------------------------------------------------
*
* Function: symbols_into_dest
*
* Purpose: Encode symbol for destination field.
*
* Inputs: symtab /, \, 0-9, A-Z
* symbol any printable character ! to ~
*
* Outputs: dest 6 character "destination" of the forms
* GPSCnn - primary table.
* GPSEnn - alternate table.
* GPSxyz - alternate with overlay.
*
* Returns: 0 for success, 1 for error.
*
*------------------------------------------------------------------*/
int symbols_into_dest (char symtab, char symbol, char *dest)
{
if (symbol >= '!' && symbol <= '~' && symtab == '/') {
/* Primary Symbol table. */
snprintf (dest, 7, "GPSC%02d", symbol - ' ');
return (0);
}
else if (symbol >= '!' && symbol <= '~' && symtab == '\\') {
/* Alternate Symbol table. */
snprintf (dest, 7, "GPSE%02d", symbol - ' ');
return (0);
}
else if (symbol >= '!' && symbol <= '~' && (isupper(symtab) || isdigit(symtab))) {
/* Alternate Symbol table with overlay. */
snprintf (dest, 7, "GPS%s%c", alternate_symtab[symbol - ' '].xy, symtab);
return (0);
}
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not convert symbol \"%c%c\" to GPSxyz destination format.\n",
symtab, symbol);
strlcpy (dest, "GPS???", sizeof(dest)); /* Error. */
return (1);
}
/*------------------------------------------------------------------
*
* Function: symbols_get_description
*
* Purpose: Get description for given symbol table/code/overlay.
*
* Inputs: symtab /, \, 0-9, A-Z
* symbol any printable character ! to ~
*
* desc_size Size of description provided by caller
* so we can avoid buffer overflow.
*
* Outputs: description Text description.
* "--no-symbol--" if error.
*
*
* Description: This is used for the monitoring and the
* decode_aprs utility.
*
*------------------------------------------------------------------*/
void symbols_get_description (char symtab, char symbol, char *description, size_t desc_size)
{
char tmp2[2];
int j;
symbols_init();
// The symbol table identifier should be
// / for symbol from primary table
// \ for symbol from alternate table
// 0-9,A-Z for alternate symbol table with overlay character
if (symtab != '/' &&
symtab != '\\' &&
! isdigit(symtab) &&
! isupper(symtab)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Symbol table identifier is not '/' (primary), '\\' (alternate), or valid overlay character.\n");
/* Possibilities: */
/* Select primary table and keep going, or */
/* report no symbol. It IS an error. */
/* We do the latter. */
symbol = ' ';
strlcpy (description, primary_symtab[symbol-' '].description, desc_size);
return;
}
// Bounds check before using symbol as index into table.
if (symbol < ' ' || symbol > '~') {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Symbol code is not a printable character.\n");
symbol = ' '; /* Avoid subscript out of bounds. */
}
// First try to match with the "new" symbols.
for (j=0; j