2015-07-27 00:35:07 +00:00
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
2022-02-25 22:29:07 +00:00
// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2017, 2022 John Langner, WB2OSZ
2015-07-27 00:35:07 +00:00
//
// 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 : decode_aprs . c
*
* Purpose : Decode the information part of APRS frame .
*
* Description : Present the packet contents in human readable format .
* This is a fairly complete implementation with error messages
2021-09-19 18:51:18 +00:00
* pointing out various specification violations .
2015-07-27 00:35:07 +00:00
*
* Assumptions : ax25_from_frame ( ) has been called to
* separate the header and information .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2016-07-03 22:09:34 +00:00
# include "direwolf.h"
2015-07-27 00:35:07 +00:00
# include <stdio.h>
# include <time.h>
# include <assert.h>
# include <stdlib.h> /* for atof */
# include <string.h> /* for strtok */
2015-09-07 23:56:20 +00:00
2015-07-27 00:35:07 +00:00
# include <math.h> /* for pow */
# include <ctype.h> /* for isdigit */
# include <fcntl.h>
# include "regex.h"
# include "ax25_pad.h"
# include "textcolor.h"
# include "symbols.h"
# include "latlong.h"
2015-11-08 01:57:02 +00:00
# include "dwgpsnmea.h"
2015-07-27 01:05:48 +00:00
# include "decode_aprs.h"
# include "telemetry.h"
2020-04-19 04:50:18 +00:00
# include "ais.h"
2015-07-27 01:05:48 +00:00
2015-07-27 00:35:07 +00:00
# define TRUE 1
# define FALSE 0
/* Position & symbol fields common to several message formats. */
typedef struct {
char lat [ 8 ] ;
char sym_table_id ; /* / \ 0-9 A-Z */
char lon [ 9 ] ;
char symbol_code ;
} position_t ;
typedef struct {
char sym_table_id ; /* / \ a-j A-Z */
/* "The presence of the leading Symbol Table Identifier */
/* instead of a digit indicates that this is a compressed */
/* Position Report and not a normal lat/long report." */
/* "a-j" is not a typographical error. */
/* The first 10 lower case letters represent the overlay */
/* characters of 0-9 in the compressed format. */
char y [ 4 ] ; /* Compressed Latitude. */
char x [ 4 ] ; /* Compressed Longitude. */
char symbol_code ;
char c ; /* Course/speed or altitude. */
char s ;
char t ; /* Compression type. */
} compressed_position_t ;
2015-07-27 01:05:48 +00:00
/* Range of digits for Base 91 representation. */
# define B91_MIN '!'
# define B91_MAX '{'
# define isdigit91(c) ((c) >= B91_MIN && (c) <= B91_MAX)
//static void print_decoded (decode_aprs_t *A);
static void aprs_ll_pos ( decode_aprs_t * A , unsigned char * , int ) ;
static void aprs_ll_pos_time ( decode_aprs_t * A , unsigned char * , int ) ;
static void aprs_raw_nmea ( decode_aprs_t * A , unsigned char * , int ) ;
static void aprs_mic_e ( decode_aprs_t * A , packet_t , unsigned char * , int ) ;
//static void aprs_compressed_pos (decode_aprs_t *A, unsigned char *, int);
2015-07-27 01:17:23 +00:00
static void aprs_message ( decode_aprs_t * A , unsigned char * , int , int quiet ) ;
2015-07-27 01:05:48 +00:00
static void aprs_object ( decode_aprs_t * A , unsigned char * , int ) ;
static void aprs_item ( decode_aprs_t * A , unsigned char * , int ) ;
static void aprs_station_capabilities ( decode_aprs_t * A , char * , int ) ;
static void aprs_status_report ( decode_aprs_t * A , char * , int ) ;
2015-09-07 23:56:20 +00:00
static void aprs_general_query ( decode_aprs_t * A , char * , int , int quiet ) ;
static void aprs_directed_station_query ( decode_aprs_t * A , char * addressee , char * query , int quiet ) ;
2016-12-17 01:12:38 +00:00
static void aprs_telemetry ( decode_aprs_t * A , char * info , int info_len , int quiet ) ;
2020-04-19 04:50:18 +00:00
static void aprs_user_defined ( decode_aprs_t * A , char * , int ) ;
2015-07-27 01:05:48 +00:00
static void aprs_raw_touch_tone ( decode_aprs_t * A , char * , int ) ;
static void aprs_morse_code ( decode_aprs_t * A , char * , int ) ;
static void aprs_positionless_weather_report ( decode_aprs_t * A , unsigned char * , int ) ;
static void weather_data ( decode_aprs_t * A , char * wdata , int wind_prefix ) ;
static void aprs_ultimeter ( decode_aprs_t * A , char * , int ) ;
static void decode_position ( decode_aprs_t * A , position_t * ppos ) ;
static void decode_compressed_position ( decode_aprs_t * A , compressed_position_t * ppos ) ;
2015-07-27 01:17:23 +00:00
static double get_latitude_8 ( char * p , int quiet ) ;
static double get_longitude_9 ( char * p , int quiet ) ;
2015-07-27 01:05:48 +00:00
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 ) ;
2015-07-27 00:35:07 +00:00
2015-09-07 23:56:20 +00:00
2015-07-27 00:35:07 +00:00
/*------------------------------------------------------------------
*
* Function : decode_aprs
*
2015-07-27 01:05:48 +00:00
* Purpose : Split APRS packet into separate properties that it contains .
2015-07-27 00:35:07 +00:00
*
2015-07-27 01:05:48 +00:00
* Inputs : pp - APRS packet object .
2015-07-27 00:35:07 +00:00
*
2015-07-27 01:17:23 +00:00
* quiet - Suppress error messages .
*
2022-02-25 22:29:07 +00:00
* third_party - True when parsing a third party header .
* ( decode_aprs is called recursively . )
* This is mostly found when an IGate transmits a message
* that came via APRS - IS .
*
2015-07-27 01:05:48 +00:00
* Outputs : A - > g_symbol_table , g_symbol_code ,
2015-07-27 00:35:07 +00:00
* g_lat , g_lon ,
2015-11-08 01:57:02 +00:00
* g_speed_mph , g_course , g_altitude_ft ,
2015-07-27 00:35:07 +00:00
* g_comment
2015-07-27 01:05:48 +00:00
* . . . and many others . . .
2015-07-27 00:35:07 +00:00
*
2015-07-27 01:05:48 +00:00
* Major Revisions : 1.1 Reorganized so parts are returned in a structure .
* Print function is now called separately .
2015-07-27 00:35:07 +00:00
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2022-02-25 22:29:07 +00:00
void decode_aprs ( decode_aprs_t * A , packet_t pp , int quiet , int third_party )
2015-07-27 00:35:07 +00:00
{
2022-02-25 22:29:07 +00:00
//dw_printf ("DEBUG decode_aprs quiet=%d, third_party=%d\n", quiet, third_party);
2015-07-27 01:05:48 +00:00
2022-02-25 22:29:07 +00:00
//char dest[AX25_MAX_ADDR_LEN];
2015-07-27 00:35:07 +00:00
unsigned char * pinfo ;
int info_len ;
info_len = ax25_get_info ( pp , & pinfo ) ;
2022-02-25 22:29:07 +00:00
//dw_printf ("DEBUG decode_aprs info=\"%s\"\n", pinfo);
2015-07-27 01:05:48 +00:00
memset ( A , 0 , sizeof ( * A ) ) ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:17:23 +00:00
A - > g_quiet = quiet ;
2019-11-09 01:36:10 +00:00
if ( isprint ( * pinfo ) ) {
snprintf ( A - > g_msg_type , sizeof ( A - > g_msg_type ) , " Unknown APRS Data Type Indicator \" %c \" " , * pinfo ) ;
}
else {
snprintf ( A - > g_msg_type , sizeof ( A - > g_msg_type ) , " ERROR!!! Unknown APRS Data Type Indicator: unprintable 0x%02x " , * pinfo ) ;
}
2015-07-27 00:35:07 +00:00
2015-09-07 23:56:20 +00:00
A - > g_symbol_table = ' / ' ; /* Default to primary table. */
2015-07-27 01:05:48 +00:00
A - > g_symbol_code = ' ' ; /* What should we have for default symbol? */
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
A - > g_lat = G_UNKNOWN ;
A - > g_lon = G_UNKNOWN ;
2015-07-27 00:35:07 +00:00
2015-11-08 01:57:02 +00:00
A - > g_speed_mph = G_UNKNOWN ;
2015-07-27 01:05:48 +00:00
A - > g_course = G_UNKNOWN ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
A - > g_power = G_UNKNOWN ;
A - > g_height = G_UNKNOWN ;
A - > g_gain = G_UNKNOWN ;
A - > g_range = G_UNKNOWN ;
2015-11-08 01:57:02 +00:00
A - > g_altitude_ft = G_UNKNOWN ;
2015-07-27 01:05:48 +00:00
A - > g_freq = G_UNKNOWN ;
A - > g_tone = G_UNKNOWN ;
A - > g_dcs = G_UNKNOWN ;
A - > g_offset = G_UNKNOWN ;
2015-09-07 23:56:20 +00:00
A - > g_footprint_lat = G_UNKNOWN ;
A - > g_footprint_lon = G_UNKNOWN ;
A - > g_footprint_radius = G_UNKNOWN ;
2020-11-14 01:46:57 +00:00
// If third-party header, try to decode just the payload.
2015-07-27 00:35:07 +00:00
2020-11-14 01:46:57 +00:00
if ( * pinfo = = ' } ' ) {
2022-02-25 22:29:07 +00:00
//dw_printf ("DEBUG decode_aprs recursively process third party header\n");
// This must not be strict because the addresses in third party payload doesn't
// need to adhere to the AX.25 address format (i.e. 6 upper case alphanumeric.)
// SSID can be 2 alphanumeric characters.
// Addresses can include lower case, e.g. q contruct.
// e.g. WR2X-2>APRS,WA1PLE-13*:}
// K1BOS-B>APOSB,TCPIP,WR2X-2*:@122015z4221.42ND07111.93W&/A=000000SharkRF openSPOT3 MMDVM446.025 MA/SW
2020-11-14 01:46:57 +00:00
packet_t pp_payload = ax25_from_text ( ( char * ) pinfo + 1 , 0 ) ;
if ( pp_payload ! = NULL ) {
2022-02-25 22:29:07 +00:00
decode_aprs ( A , pp_payload , quiet , 1 ) ; // 1 means used recursively
2020-11-14 01:46:57 +00:00
ax25_delete ( pp_payload ) ;
return ;
}
else {
strlcpy ( A - > g_msg_type , " Third Party Header: Unable to parse payload. " , sizeof ( A - > g_msg_type ) ) ;
ax25_get_addr_with_ssid ( pp , AX25_SOURCE , A - > g_src ) ;
2022-02-25 22:29:07 +00:00
ax25_get_addr_with_ssid ( pp , AX25_DESTINATION , A - > g_dest ) ;
2020-11-14 01:46:57 +00:00
}
}
2015-11-18 13:09:45 +00:00
2015-07-27 00:35:07 +00:00
/*
* Extract source and destination including the SSID .
*/
2015-07-27 01:05:48 +00:00
ax25_get_addr_with_ssid ( pp , AX25_SOURCE , A - > g_src ) ;
2022-02-25 22:29:07 +00:00
ax25_get_addr_with_ssid ( pp , AX25_DESTINATION , A - > g_dest ) ;
//dw_printf ("DEBUG decode_aprs source=%s, dest=%s\n", A->g_src, A->g_dest);
2015-07-27 00:35:07 +00:00
2017-03-05 20:15:33 +00:00
/*
* Report error if the information part contains a nul character .
* There are two known cases where this can happen .
*
* - The Kenwood TM - D710A sometimes sends packets like this :
*
* VA3AJ - 9 > T2QU6X , VE3WRC , WIDE1 , K8UNS , WIDE2 * : 4 P < 0x00 > < 0x0f > 4 T < 0x00 > < 0x0f > 4 X < 0x00 > < 0x0f > 4 \ < 0x00 > ` nW < 0x1f > oS8 > / ] " 6M}driving fast=
* K4JH - 9 > S5UQ6X , WR4AGC - 3 * , WIDE1 * : 4 P < 0x00 > < 0x0f > 4 T < 0x00 > < 0x0f > 4 X < 0x00 > < 0x0f > 4 \ < 0x00 > ` jP } l " &>/] " 47 } QRV from the EV =
*
* Notice that the data type indicator of " 4 " is not valid . If we remove
* 4 P < 0x00 > < 0x0f > 4 T < 0x00 > < 0x0f > 4 X < 0x00 > < 0x0f > 4 \ < 0x00 > we are left with a good MIC - E format .
* This same thing has been observed from others and is intermittent .
*
* - AGW Tracker can send UTF - 16 if an option is selected . This can introduce nul bytes .
* This is wrong , it should be using UTF - 8.
*/
if ( ( ! A - > g_quiet ) & & ( ( int ) strlen ( ( char * ) pinfo ) ! = info_len ) ) {
text_color_set ( DW_COLOR_ERROR ) ;
2019-01-21 16:07:20 +00:00
dw_printf ( " 'nul' character found in Information part. This should never happen with APRS. \n " ) ;
dw_printf ( " If this is meant to be APRS, %s is transmitting with defective software. \n " , A - > g_src ) ;
2017-03-05 20:15:33 +00:00
if ( strcmp ( ( char * ) pinfo , " 4P " ) = = 0 ) {
dw_printf ( " The TM-D710 will do this intermittently. A firmware upgrade is needed to fix it. \n " ) ;
}
}
2015-07-27 00:35:07 +00:00
2020-04-19 04:50:18 +00:00
/*
* Application might be in the destination field for most message types .
* MIC - E format has part of location in the destination field .
*/
switch ( * pinfo ) { /* "DTI" data type identifier. */
case ' \' ' : /* Old Mic-E Data */
case ' ` ' : /* Current Mic-E Data */
break ;
default :
2022-02-25 22:29:07 +00:00
decode_tocall ( A , A - > g_dest ) ;
2020-04-19 04:50:18 +00:00
break ;
}
2015-07-27 00:35:07 +00:00
switch ( * pinfo ) { /* "DTI" data type identifier. */
case ' ! ' : /* Position without timestamp (no APRS messaging). */
/* or Ultimeter 2000 WX Station */
case ' = ' : /* Position without timestamp (with APRS messaging). */
if ( strncmp ( ( char * ) pinfo , " !! " , 2 ) = = 0 )
{
2015-07-27 01:05:48 +00:00
aprs_ultimeter ( A , ( char * ) pinfo , info_len ) ;
2015-07-27 00:35:07 +00:00
}
else
{
2015-07-27 01:05:48 +00:00
aprs_ll_pos ( A , pinfo , info_len ) ;
2015-07-27 00:35:07 +00:00
}
break ;
//case '#': /* Peet Bros U-II Weather station */
//case '*': /* Peet Bros U-II Weather station */
//break;
case ' $ ' : /* Raw GPS data or Ultimeter 2000 */
if ( strncmp ( ( char * ) pinfo , " $ULTW " , 5 ) = = 0 )
{
2015-07-27 01:05:48 +00:00
aprs_ultimeter ( A , ( char * ) pinfo , info_len ) ;
2015-07-27 00:35:07 +00:00
}
else
{
2015-07-27 01:05:48 +00:00
aprs_raw_nmea ( A , pinfo , info_len ) ;
2015-07-27 00:35:07 +00:00
}
break ;
case ' \' ' : /* Old Mic-E Data (but Current data for TM-D700) */
case ' ` ' : /* Current Mic-E Data (not used in TM-D700) */
2015-07-27 01:05:48 +00:00
aprs_mic_e ( A , pp , pinfo , info_len ) ;
2015-07-27 00:35:07 +00:00
break ;
case ' ) ' : /* Item. */
2015-07-27 01:05:48 +00:00
aprs_item ( A , pinfo , info_len ) ;
2015-07-27 00:35:07 +00:00
break ;
case ' / ' : /* Position with timestamp (no APRS messaging) */
case ' @ ' : /* Position with timestamp (with APRS messaging) */
2015-07-27 01:05:48 +00:00
aprs_ll_pos_time ( A , pinfo , info_len ) ;
2015-07-27 00:35:07 +00:00
break ;
2022-02-25 22:29:07 +00:00
case ' : ' : /* "Message" (special APRS meaning): for one person, a group, or a bulletin. */
2015-09-07 23:56:20 +00:00
/* Directed Station Query */
2016-05-01 22:46:47 +00:00
/* Telemetry metadata. */
2015-07-27 00:35:07 +00:00
2015-07-27 01:17:23 +00:00
aprs_message ( A , pinfo , info_len , quiet ) ;
2015-07-27 00:35:07 +00:00
break ;
case ' ; ' : /* Object */
2015-07-27 01:05:48 +00:00
aprs_object ( A , pinfo , info_len ) ;
2015-07-27 00:35:07 +00:00
break ;
case ' < ' : /* Station Capabilities */
2015-07-27 01:05:48 +00:00
aprs_station_capabilities ( A , ( char * ) pinfo , info_len ) ;
2015-07-27 00:35:07 +00:00
break ;
case ' > ' : /* Status Report */
2015-07-27 01:05:48 +00:00
aprs_status_report ( A , ( char * ) pinfo , info_len ) ;
2015-07-27 00:35:07 +00:00
break ;
2015-09-07 23:56:20 +00:00
case ' ? ' : /* General Query */
aprs_general_query ( A , ( char * ) pinfo , info_len , quiet ) ;
break ;
2015-07-27 00:35:07 +00:00
case ' T ' : /* Telemetry */
2015-09-07 23:56:20 +00:00
2015-07-27 01:17:23 +00:00
aprs_telemetry ( A , ( char * ) pinfo , info_len , quiet ) ;
2015-07-27 00:35:07 +00:00
break ;
case ' _ ' : /* Positionless Weather Report */
2015-07-27 01:05:48 +00:00
aprs_positionless_weather_report ( A , pinfo , info_len ) ;
2015-07-27 00:35:07 +00:00
break ;
case ' { ' : /* user defined data */
2020-04-19 04:50:18 +00:00
aprs_user_defined ( A , ( char * ) pinfo , info_len ) ;
2015-07-27 00:35:07 +00:00
break ;
case ' t ' : /* Raw touch tone data - NOT PART OF STANDARD */
/* Used to convey raw touch tone sequences to */
/* to an application that might want to interpret them. */
/* Might move into user defined data, above. */
2015-07-27 01:05:48 +00:00
aprs_raw_touch_tone ( A , ( char * ) pinfo , info_len ) ;
2015-07-27 00:35:07 +00:00
break ;
case ' m ' : /* Morse Code data - NOT PART OF STANDARD */
/* Used by APRStt gateway to put audible responses */
/* into the transmit queue. Could potentially find */
/* other uses such as CW ID for station. */
/* Might move into user defined data, above. */
2015-07-27 01:05:48 +00:00
aprs_morse_code ( A , ( char * ) pinfo , info_len ) ;
2015-07-27 00:35:07 +00:00
break ;
2020-11-14 01:46:57 +00:00
//case '}': /* third party header */
2015-07-27 00:35:07 +00:00
2020-11-14 01:46:57 +00:00
// was already caught earlier.
2015-07-27 00:35:07 +00:00
//case '\r': /* CR or LF? */
//case '\n':
//break;
default :
break ;
}
/*
2020-11-14 01:46:57 +00:00
* Priority order for determining the symbol is :
* - Information part , where appropriate . Already done above .
* - Destination field starting with GPS , SPC , or SYM .
* - Source SSID - Confusing to most people . Even I forgot about it when
* someone questioned where the symbol came from . It ' s in the APRS
* protocol spec , end of Chapter 20.
2015-07-27 00:35:07 +00:00
*/
2015-07-27 01:05:48 +00:00
if ( A - > g_symbol_table = = ' ' | | A - > g_symbol_code = = ' ' ) {
2015-07-27 00:35:07 +00:00
2022-02-25 22:29:07 +00:00
// A symbol on a "message" makes no sense and confuses people.
// Third party too. Set from the payload.
// Maybe eliminate for a couple others.
//dw_printf ("DEBUG decode_aprs@end1 third_party=%d, symbol_table=%c, symbol_code=%c, *pinfo=%c\n", third_party, A->g_symbol_table, A->g_symbol_code, *pinfo);
if ( * pinfo ! = ' : ' & & * pinfo ! = ' } ' ) {
symbols_from_dest_or_src ( * pinfo , A - > g_src , A - > g_dest , & A - > g_symbol_table , & A - > g_symbol_code ) ;
}
//dw_printf ("DEBUG decode_aprs@end2 third_party=%d, symbol_table=%c, symbol_code=%c, *pinfo=%c\n", third_party, A->g_symbol_table, A->g_symbol_code, *pinfo);
2015-07-27 00:35:07 +00:00
}
2015-07-27 01:05:48 +00:00
} /* end decode_aprs */
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
void decode_aprs_print ( decode_aprs_t * A ) {
2015-07-27 00:35:07 +00:00
2022-02-25 22:29:07 +00:00
char stemp [ 500 ] ;
2015-07-27 00:35:07 +00:00
double absll ;
char news ;
int deg ;
double min ;
char s_lat [ 30 ] ;
char s_lon [ 30 ] ;
int n ;
/*
* First line has :
2022-02-25 22:29:07 +00:00
* - packet type
2015-07-27 00:35:07 +00:00
* - object name
* - symbol
* - manufacturer / application
* - mic - e status
* - power / height / gain , range
*/
2015-09-07 23:56:20 +00:00
strlcpy ( stemp , A - > g_msg_type , sizeof ( stemp ) ) ;
2015-07-27 00:35:07 +00:00
2022-02-25 22:29:07 +00:00
//dw_printf ("DEBUG decode_aprs_print stemp1=%s\n", stemp);
2015-07-27 01:05:48 +00:00
if ( strlen ( A - > g_name ) > 0 ) {
2015-09-07 23:56:20 +00:00
strlcat ( stemp , " , \" " , sizeof ( stemp ) ) ;
strlcat ( stemp , A - > g_name , sizeof ( stemp ) ) ;
strlcat ( stemp , " \" " , sizeof ( stemp ) ) ;
2015-07-27 00:35:07 +00:00
}
2022-02-25 22:29:07 +00:00
//dw_printf ("DEBUG decode_aprs_print stemp2=%s\n", stemp);
//dw_printf ("DEBUG decode_aprs_print symbol_code=%c=0x%02x\n", A->g_symbol_code, A->g_symbol_code);
2015-09-07 23:56:20 +00:00
if ( A - > g_symbol_code ! = ' ' ) {
2022-02-25 22:29:07 +00:00
char symbol_description [ 100 ] ;
symbols_get_description ( A - > g_symbol_table , A - > g_symbol_code , symbol_description , sizeof ( symbol_description ) ) ;
//dw_printf ("DEBUG decode_aprs_print symbol_description_description=%s\n", symbol_description);
2015-09-07 23:56:20 +00:00
strlcat ( stemp , " , " , sizeof ( stemp ) ) ;
strlcat ( stemp , symbol_description , sizeof ( stemp ) ) ;
}
2015-07-27 00:35:07 +00:00
2022-02-25 22:29:07 +00:00
//dw_printf ("DEBUG decode_aprs_print stemp3=%s mfr=%s\n", stemp, A->g_mfr);
2015-07-27 01:05:48 +00:00
if ( strlen ( A - > g_mfr ) > 0 ) {
2022-02-25 22:29:07 +00:00
if ( strcmp ( A - > g_dest , " APRS " ) = = 0 ) {
strlcat ( stemp , " \n Use of \" APRS \" in the destination field is obsolete. " , sizeof ( stemp ) ) ;
strlcat ( stemp , " You can help to improve the quality of APRS signals. " , sizeof ( stemp ) ) ;
strlcat ( stemp , " \n Tell the sender ( " , sizeof ( stemp ) ) ;
strlcat ( stemp , A - > g_src , sizeof ( stemp ) ) ;
strlcat ( stemp , " ) to use the proper product code from " , sizeof ( stemp ) ) ;
strlcat ( stemp , " http://www.aprs.org/aprs11/tocalls.txt " , sizeof ( stemp ) ) ;
}
else {
strlcat ( stemp , " , " , sizeof ( stemp ) ) ;
strlcat ( stemp , A - > g_mfr , sizeof ( stemp ) ) ;
}
2015-07-27 00:35:07 +00:00
}
2022-02-25 22:29:07 +00:00
//dw_printf ("DEBUG decode_aprs_print stemp4=%s\n", stemp);
2015-07-27 01:05:48 +00:00
if ( strlen ( A - > g_mic_e_status ) > 0 ) {
2015-09-07 23:56:20 +00:00
strlcat ( stemp , " , " , sizeof ( stemp ) ) ;
strlcat ( stemp , A - > g_mic_e_status , sizeof ( stemp ) ) ;
2015-07-27 00:35:07 +00:00
}
2022-02-25 22:29:07 +00:00
//dw_printf ("DEBUG decode_aprs_print stemp5=%s\n", stemp);
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
if ( A - > g_power > 0 ) {
2015-07-27 00:35:07 +00:00
char phg [ 100 ] ;
2021-09-19 18:51:18 +00:00
/* Protocol spec doesn't mention whether this is dBd or dBi. */
2015-11-08 01:57:02 +00:00
/* Clarified later. */
/* http://eng.usna.navy.mil/~bruninga/aprs/aprs11.html */
/* "The Antenna Gain in the PHG format on page 28 is in dBi." */
2015-09-07 23:56:20 +00:00
snprintf ( phg , sizeof ( phg ) , " , %d W height=%d %ddBi %s " , A - > g_power , A - > g_height , A - > g_gain , A - > g_directivity ) ;
strlcat ( stemp , phg , sizeof ( stemp ) ) ;
2015-07-27 00:35:07 +00:00
}
2015-07-27 01:05:48 +00:00
if ( A - > g_range > 0 ) {
2015-07-27 00:35:07 +00:00
char rng [ 100 ] ;
2015-09-07 23:56:20 +00:00
snprintf ( rng , sizeof ( rng ) , " , range=%.1f " , A - > g_range ) ;
strlcat ( stemp , rng , sizeof ( stemp ) ) ;
2015-07-27 00:35:07 +00:00
}
text_color_set ( DW_COLOR_DECODED ) ;
dw_printf ( " %s \n " , stemp ) ;
/*
* Second line has :
* - Latitude
* - Longitude
* - speed
* - direction
* - altitude
* - frequency
*/
/*
* Convert Maidenhead locator to latitude and longitude .
*
* Any example was checked for each hemihemisphere using
* http : //www.amsat.org/cgi-bin/gridconv
*/
2020-10-26 02:01:09 +00:00
// FIXME soften language about upper case.
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
if ( strlen ( A - > g_maidenhead ) > 0 ) {
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
if ( A - > g_lat = = G_UNKNOWN & & A - > g_lon = = G_UNKNOWN ) {
2015-07-27 00:35:07 +00:00
2015-07-27 01:17:23 +00:00
ll_from_grid_square ( A - > g_maidenhead , & ( A - > g_lat ) , & ( A - > g_lon ) ) ;
2015-07-27 00:35:07 +00:00
}
2015-07-27 01:17:23 +00:00
dw_printf ( " Grid square = %s, " , A - > g_maidenhead ) ;
2015-07-27 00:35:07 +00:00
}
2015-09-07 23:56:20 +00:00
strlcpy ( stemp , " " , sizeof ( stemp ) ) ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
if ( A - > g_lat ! = G_UNKNOWN | | A - > g_lon ! = G_UNKNOWN ) {
2015-07-27 00:35:07 +00:00
2021-09-19 18:51:18 +00:00
// Have location but it is possible one part is invalid.
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
if ( A - > g_lat ! = G_UNKNOWN ) {
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
if ( A - > g_lat > = 0 ) {
absll = A - > g_lat ;
2015-07-27 00:35:07 +00:00
news = ' N ' ;
}
else {
2015-07-27 01:05:48 +00:00
absll = - A - > g_lat ;
2015-07-27 00:35:07 +00:00
news = ' S ' ;
}
deg = ( int ) absll ;
min = ( absll - deg ) * 60.0 ;
2015-09-07 23:56:20 +00:00
snprintf ( s_lat , sizeof ( s_lat ) , " %c %02d%s%07.4f " , news , deg , CH_DEGREE , min ) ;
2015-07-27 00:35:07 +00:00
}
else {
2015-09-07 23:56:20 +00:00
strlcpy ( s_lat , " Invalid Latitude " , sizeof ( s_lat ) ) ;
2015-07-27 00:35:07 +00:00
}
2015-07-27 01:05:48 +00:00
if ( A - > g_lon ! = G_UNKNOWN ) {
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
if ( A - > g_lon > = 0 ) {
absll = A - > g_lon ;
2015-07-27 00:35:07 +00:00
news = ' E ' ;
}
else {
2015-07-27 01:05:48 +00:00
absll = - A - > g_lon ;
2015-07-27 00:35:07 +00:00
news = ' W ' ;
}
deg = ( int ) absll ;
min = ( absll - deg ) * 60.0 ;
2015-09-07 23:56:20 +00:00
snprintf ( s_lon , sizeof ( s_lon ) , " %c %03d%s%07.4f " , news , deg , CH_DEGREE , min ) ;
2015-07-27 00:35:07 +00:00
}
else {
2015-09-07 23:56:20 +00:00
strlcpy ( s_lon , " Invalid Longitude " , sizeof ( s_lon ) ) ;
2015-07-27 00:35:07 +00:00
}
2015-09-07 23:56:20 +00:00
snprintf ( stemp , sizeof ( stemp ) , " %s, %s " , s_lat , s_lon ) ;
2015-07-27 00:35:07 +00:00
}
2015-07-27 01:05:48 +00:00
if ( strlen ( A - > g_aprstt_loc ) > 0 ) {
2015-09-07 23:56:20 +00:00
if ( strlen ( stemp ) > 0 ) strlcat ( stemp , " , " , sizeof ( stemp ) ) ;
strlcat ( stemp , A - > g_aprstt_loc , sizeof ( stemp ) ) ;
2015-07-27 01:05:48 +00:00
} ;
2015-11-08 01:57:02 +00:00
if ( A - > g_speed_mph ! = G_UNKNOWN ) {
2020-12-31 19:42:27 +00:00
char spd [ 32 ] ;
2015-07-27 00:35:07 +00:00
2015-09-07 23:56:20 +00:00
if ( strlen ( stemp ) > 0 ) strlcat ( stemp , " , " , sizeof ( stemp ) ) ;
2020-12-31 19:42:27 +00:00
snprintf ( spd , sizeof ( spd ) , " %.0f km/h (%.0f MPH) " , DW_MILES_TO_KM ( A - > g_speed_mph ) , A - > g_speed_mph ) ;
2015-09-07 23:56:20 +00:00
strlcat ( stemp , spd , sizeof ( stemp ) ) ;
2015-07-27 00:35:07 +00:00
} ;
2015-07-27 01:05:48 +00:00
if ( A - > g_course ! = G_UNKNOWN ) {
2015-07-27 00:35:07 +00:00
char cse [ 20 ] ;
2015-09-07 23:56:20 +00:00
if ( strlen ( stemp ) > 0 ) strlcat ( stemp , " , " , sizeof ( stemp ) ) ;
snprintf ( cse , sizeof ( cse ) , " course %.0f " , A - > g_course ) ;
strlcat ( stemp , cse , sizeof ( stemp ) ) ;
2015-07-27 00:35:07 +00:00
} ;
2015-11-08 01:57:02 +00:00
if ( A - > g_altitude_ft ! = G_UNKNOWN ) {
2020-12-31 19:42:27 +00:00
char alt [ 32 ] ;
2015-07-27 00:35:07 +00:00
2015-09-07 23:56:20 +00:00
if ( strlen ( stemp ) > 0 ) strlcat ( stemp , " , " , sizeof ( stemp ) ) ;
2020-12-31 19:42:27 +00:00
snprintf ( alt , sizeof ( alt ) , " alt %.0f m (%.0f ft) " , DW_FEET_TO_METERS ( A - > g_altitude_ft ) , A - > g_altitude_ft ) ;
2015-09-07 23:56:20 +00:00
strlcat ( stemp , alt , sizeof ( stemp ) ) ;
2015-07-27 00:35:07 +00:00
} ;
2015-07-27 01:05:48 +00:00
if ( A - > g_freq ! = G_UNKNOWN ) {
char ftemp [ 30 ] ;
2015-09-07 23:56:20 +00:00
snprintf ( ftemp , sizeof ( ftemp ) , " , %.3f MHz " , A - > g_freq ) ;
strlcat ( stemp , ftemp , sizeof ( stemp ) ) ;
2015-07-27 00:35:07 +00:00
}
2015-07-27 01:05:48 +00:00
if ( A - > g_offset ! = G_UNKNOWN ) {
char ftemp [ 30 ] ;
if ( A - > g_offset % 1000 = = 0 ) {
2015-09-07 23:56:20 +00:00
snprintf ( ftemp , sizeof ( ftemp ) , " , %+dM " , A - > g_offset / 1000 ) ;
2015-07-27 01:05:48 +00:00
}
else {
2015-09-07 23:56:20 +00:00
snprintf ( ftemp , sizeof ( ftemp ) , " , %+dk " , A - > g_offset ) ;
2015-07-27 01:05:48 +00:00
}
2015-09-07 23:56:20 +00:00
strlcat ( stemp , ftemp , sizeof ( stemp ) ) ;
2015-07-27 01:05:48 +00:00
}
if ( A - > g_tone ! = G_UNKNOWN ) {
if ( A - > g_tone = = 0 ) {
2015-09-07 23:56:20 +00:00
strlcat ( stemp , " , no PL " , sizeof ( stemp ) ) ;
2015-07-27 01:05:48 +00:00
}
else {
char ftemp [ 30 ] ;
2015-09-07 23:56:20 +00:00
snprintf ( ftemp , sizeof ( ftemp ) , " , PL %.1f " , A - > g_tone ) ;
strlcat ( stemp , ftemp , sizeof ( stemp ) ) ;
2015-07-27 01:05:48 +00:00
}
}
if ( A - > g_dcs ! = G_UNKNOWN ) {
char ftemp [ 30 ] ;
2015-09-07 23:56:20 +00:00
snprintf ( ftemp , sizeof ( ftemp ) , " , DCS %03o " , A - > g_dcs ) ;
strlcat ( stemp , ftemp , sizeof ( stemp ) ) ;
2015-07-27 01:05:48 +00:00
}
2015-07-27 00:35:07 +00:00
if ( strlen ( stemp ) > 0 ) {
text_color_set ( DW_COLOR_DECODED ) ;
dw_printf ( " %s \n " , stemp ) ;
}
/*
2015-07-27 01:05:48 +00:00
* Finally , any weather and / or comment .
2015-07-27 00:35:07 +00:00
*
* Non - printable characters are changed to safe hexadecimal representations .
* For example , carriage return is displayed as < 0x0d > .
*
2015-07-27 01:05:48 +00:00
* Drop annoying trailing CR LF . Anyone who cares can see it in the raw datA - >
2015-07-27 00:35:07 +00:00
*/
2015-07-27 01:05:48 +00:00
n = strlen ( A - > g_weather ) ;
if ( n > = 1 & & A - > g_weather [ n - 1 ] = = ' \n ' ) {
A - > g_weather [ n - 1 ] = ' \0 ' ;
2015-07-27 00:35:07 +00:00
n - - ;
}
2015-07-27 01:05:48 +00:00
if ( n > = 1 & & A - > g_weather [ n - 1 ] = = ' \r ' ) {
A - > g_weather [ n - 1 ] = ' \0 ' ;
n - - ;
}
2015-09-07 23:56:20 +00:00
if ( n > 0 ) {
2015-07-27 01:05:48 +00:00
ax25_safe_print ( A - > g_weather , - 1 , 0 ) ;
dw_printf ( " \n " ) ;
}
if ( strlen ( A - > g_telemetry ) > 0 ) {
ax25_safe_print ( A - > g_telemetry , - 1 , 0 ) ;
dw_printf ( " \n " ) ;
}
n = strlen ( A - > g_comment ) ;
if ( n > = 1 & & A - > g_comment [ n - 1 ] = = ' \n ' ) {
A - > g_comment [ n - 1 ] = ' \0 ' ;
n - - ;
}
if ( n > = 1 & & A - > g_comment [ n - 1 ] = = ' \r ' ) {
A - > g_comment [ n - 1 ] = ' \0 ' ;
2015-07-27 00:35:07 +00:00
n - - ;
}
if ( n > 0 ) {
int j ;
2015-07-27 01:05:48 +00:00
ax25_safe_print ( A - > g_comment , - 1 , 0 ) ;
2015-07-27 00:35:07 +00:00
dw_printf ( " \n " ) ;
/*
* Point out incorrect attempts a degree symbol .
* 0xb0 is degree in ISO Latin1 .
* To be part of a valid UTF - 8 sequence , it would need to be preceded by 11 xxxxxx or 10 xxxxxx .
* 0xf8 is degree in Microsoft code page 437.
* To be part of a valid UTF - 8 sequence , it would need to be followed by 10 xxxxxx .
*/
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
for ( j = 0 ; j < n ; j + + ) {
2016-12-23 16:28:39 +00:00
if ( ( unsigned char ) ( A - > g_comment [ j ] ) = = 0xb0 & & ( j = = 0 | | ! ( A - > g_comment [ j - 1 ] & 0x80 ) ) ) {
2015-07-27 01:17:23 +00:00
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Character code 0xb0 is probably an attempt at a degree symbol. \n " ) ;
dw_printf ( " The correct encoding is 0xc2 0xb0 in UTF-8. \n " ) ;
}
}
for ( j = 0 ; j < n ; j + + ) {
2016-12-23 16:28:39 +00:00
if ( ( unsigned char ) ( A - > g_comment [ j ] ) = = 0xf8 & & ( j = = n - 1 | | ( A - > g_comment [ j + 1 ] & 0xc0 ) ! = 0xc0 ) ) {
2015-07-27 01:17:23 +00:00
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Character code 0xf8 is probably an attempt at a degree symbol. \n " ) ;
dw_printf ( " The correct encoding is 0xc2 0xb0 in UTF-8. \n " ) ;
}
}
2015-07-27 00:35:07 +00:00
}
}
}
/*------------------------------------------------------------------
*
* Function : aprs_ll_pos
*
* Purpose : Decode " Lat/Long Position Report - without Timestamp "
*
* Reports without a timestamp can be regarded as real - time .
*
* Inputs : info - Pointer to Information field .
* ilen - Information field length .
*
2015-11-08 01:57:02 +00:00
* Outputs : A - > g_lat , A - > g_lon , A - > g_symbol_table , A - > g_symbol_code , A - > g_speed_mph , A - > g_course , A - > g_altitude_ft .
2015-07-27 00:35:07 +00:00
*
* Description : Type identifier ' = ' has APRS messaging .
* Type identifier ' ! ' does not have APRS messaging .
*
* The location can be in either compressed or human - readable form .
*
* When the symbol code is ' _ ' this is a weather report .
*
* Examples : ! 4309.95 NS07307 .13 W # PHG3320 W2 , NY2 Mt Equinox VT k2lm @ arrl . net
* ! 4237.14 NS07120 .83 W #
* = 4246.40 N / 07115.15 W # { UIV32 }
*
* TODO : ( ? ) Special case , DF report when sym table id = ' / ' and symbol code = ' \ ' .
*
* = 4903.50 N / 07201.75 W \ 088 / 036 / 270 / 729
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:05:48 +00:00
static void aprs_ll_pos ( decode_aprs_t * A , unsigned char * info , int ilen )
2015-07-27 00:35:07 +00:00
{
struct aprs_ll_pos_s {
char dti ; /* ! or = */
position_t pos ;
char comment [ 43 ] ; /* Start of comment could be data extension(s). */
} * p ;
struct aprs_compressed_pos_s {
char dti ; /* ! or = */
compressed_position_t cpos ;
char comment [ 40 ] ; /* No data extension allowed for compressed location. */
} * q ;
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " Position " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 00:35:07 +00:00
p = ( struct aprs_ll_pos_s * ) info ;
q = ( struct aprs_compressed_pos_s * ) info ;
if ( isdigit ( ( unsigned char ) ( p - > pos . lat [ 0 ] ) ) ) /* Human-readable location. */
{
2015-07-27 01:05:48 +00:00
decode_position ( A , & ( p - > pos ) ) ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
if ( A - > g_symbol_code = = ' _ ' ) {
2015-07-27 00:35:07 +00:00
/* Symbol code indidates it is a weather report. */
/* In this case, we expect 7 byte "data extension" */
/* for the wind direction and speed. */
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " Weather Report " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 01:05:48 +00:00
weather_data ( A , p - > comment , TRUE ) ;
2015-07-27 00:35:07 +00:00
}
else {
/* Regular position report. */
2015-07-27 01:05:48 +00:00
data_extension_comment ( A , p - > comment ) ;
2015-07-27 00:35:07 +00:00
}
}
else /* Compressed location. */
{
2015-07-27 01:05:48 +00:00
decode_compressed_position ( A , & ( q - > cpos ) ) ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
if ( A - > g_symbol_code = = ' _ ' ) {
2015-07-27 00:35:07 +00:00
/* Symbol code indidates it is a weather report. */
/* In this case, the wind direction and speed are in the */
/* compressed data so we don't expect a 7 byte "data */
/* extension" for them. */
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " Weather Report " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 01:05:48 +00:00
weather_data ( A , q - > comment , FALSE ) ;
2015-07-27 00:35:07 +00:00
}
else {
/* Regular position report. */
2015-07-27 01:05:48 +00:00
process_comment ( A , q - > comment , - 1 ) ;
2015-07-27 00:35:07 +00:00
}
}
}
/*------------------------------------------------------------------
*
* Function : aprs_ll_pos_time
*
* Purpose : Decode " Lat/Long Position Report - with Timestamp "
*
* Reports sent with a timestamp might contain very old information .
*
* Otherwise , same as above .
*
* Inputs : info - Pointer to Information field .
* ilen - Information field length .
*
2015-11-08 01:57:02 +00:00
* Outputs : A - > g_lat , A - > g_lon , A - > g_symbol_table , A - > g_symbol_code , A - > g_speed_mph , A - > g_course , A - > g_altitude_ft .
2015-07-27 00:35:07 +00:00
*
* Description : Type identifier ' @ ' has APRS messaging .
* Type identifier ' / ' does not have APRS messaging .
*
* The location can be in either compressed or human - readable form .
*
* When the symbol code is ' _ ' this is a weather report .
*
* Examples : @ 041025 z4232 .32 N / 07058.81 W_124 / 000 g000t036r000p000P000b10229h65 / wx rpt
* @ 281621 z4237 .55 N / 07120.20 W_017 / 002 g006t022r000p000P000h85b10195 . Dvs
* / 092345 z4903 .50 N / 07201.75 W > Test1234
*
* I think the symbol code of " _ " indicates weather report .
*
* ( ? ) Special case , DF report when sym table id = ' / ' and symbol code = ' \ ' .
*
* @ 092345 z4903 .50 N / 07201.75 W \ 088 / 036 / 270 / 729
* / 092345 z4903 .50 N / 07201.75 W \ 000 / 000 / 270 / 729
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:05:48 +00:00
static void aprs_ll_pos_time ( decode_aprs_t * A , unsigned char * info , int ilen )
2015-07-27 00:35:07 +00:00
{
struct aprs_ll_pos_time_s {
char dti ; /* / or @ */
char time_stamp [ 7 ] ;
position_t pos ;
char comment [ 43 ] ; /* First 7 bytes could be data extension. */
} * p ;
struct aprs_compressed_pos_time_s {
char dti ; /* / or @ */
char time_stamp [ 7 ] ;
compressed_position_t cpos ;
char comment [ 40 ] ; /* No data extension in this case. */
} * q ;
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " Position with time " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 00:35:07 +00:00
time_t ts = 0 ;
p = ( struct aprs_ll_pos_time_s * ) info ;
q = ( struct aprs_compressed_pos_time_s * ) info ;
if ( isdigit ( ( unsigned char ) ( p - > pos . lat [ 0 ] ) ) ) /* Human-readable location. */
{
2015-07-27 01:05:48 +00:00
ts = get_timestamp ( A , p - > time_stamp ) ;
decode_position ( A , & ( p - > pos ) ) ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
if ( A - > g_symbol_code = = ' _ ' ) {
2015-07-27 00:35:07 +00:00
/* Symbol code indidates it is a weather report. */
/* In this case, we expect 7 byte "data extension" */
/* for the wind direction and speed. */
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " Weather Report " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 01:05:48 +00:00
weather_data ( A , p - > comment , TRUE ) ;
2015-07-27 00:35:07 +00:00
}
else {
/* Regular position report. */
2015-07-27 01:05:48 +00:00
data_extension_comment ( A , p - > comment ) ;
2015-07-27 00:35:07 +00:00
}
}
else /* Compressed location. */
{
2015-07-27 01:05:48 +00:00
ts = get_timestamp ( A , p - > time_stamp ) ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
decode_compressed_position ( A , & ( q - > cpos ) ) ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
if ( A - > g_symbol_code = = ' _ ' ) {
2015-07-27 00:35:07 +00:00
/* Symbol code indidates it is a weather report. */
/* In this case, the wind direction and speed are in the */
/* compressed data so we don't expect a 7 byte "data */
/* extension" for them. */
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " Weather Report " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 01:05:48 +00:00
weather_data ( A , q - > comment , FALSE ) ;
2015-07-27 00:35:07 +00:00
}
else {
/* Regular position report. */
2015-07-27 01:05:48 +00:00
process_comment ( A , q - > comment , - 1 ) ;
2015-07-27 00:35:07 +00:00
}
}
2015-11-08 01:57:02 +00:00
( void ) ( ts ) ; // suppress 'set but not used' warning.
2015-07-27 00:35:07 +00:00
}
/*------------------------------------------------------------------
*
* Function : aprs_raw_nmea
*
* Purpose : Decode " Raw NMEA Position Report "
*
* Inputs : info - Pointer to Information field .
* ilen - Information field length .
*
2015-11-08 01:57:02 +00:00
* Outputs : A - > . . .
2015-07-27 00:35:07 +00:00
*
* Description : APRS recognizes raw ASCII data strings conforming to the NMEA 01 83
* Version 2.0 specification , originating from navigation equipment such
* as GPS and LORAN receivers . It is recommended that APRS stations
* interpret at least the following NMEA Received Sentence types :
*
* GGA Global Positioning System Fix Data
* GLL Geographic Position , Latitude / Longitude Data
* RMC Recommended Minimum Specific GPS / Transit Data
* VTG Velocity and Track Data
* WPL Way Point Location
*
2015-11-08 01:57:02 +00:00
* We presently recognize only RMC and GGA .
*
2015-07-27 00:35:07 +00:00
* Examples : $ GPGGA , 102705 , 5157.9762 , N , 00029.3256 , W , 1 , 04 , 2.0 , 75.7 , M , 47.6 , M , , * 62
* $ GPGLL , 2554.459 , N , 08020.187 , W , 154027.281 , A
* $ GPRMC , 063 909 , A , 3349.4302 , N , 11700.3721 , W , 43.022 , 89.3 , 291099 , 13.6 , E * 52
* $ GPVTG , 318.7 , T , , M , 35.1 , N , 65.0 , K * 69
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:05:48 +00:00
static void aprs_raw_nmea ( decode_aprs_t * A , unsigned char * info , int ilen )
2015-07-27 00:35:07 +00:00
{
2018-11-24 22:20:45 +00:00
if ( strncmp ( ( char * ) info , " $GPRMC, " , 7 ) = = 0 | |
strncmp ( ( char * ) info , " $GNRMC, " , 7 ) = = 0 )
2015-07-27 00:35:07 +00:00
{
2015-11-08 01:57:02 +00:00
float speed_knots = G_UNKNOWN ;
2015-09-07 23:56:20 +00:00
2015-11-08 01:57:02 +00:00
( void ) dwgpsnmea_gprmc ( ( char * ) info , A - > g_quiet , & ( A - > g_lat ) , & ( A - > g_lon ) , & speed_knots , & ( A - > g_course ) ) ;
A - > g_speed_mph = DW_KNOTS_TO_MPH ( speed_knots ) ;
2019-04-21 23:27:44 +00:00
strlcpy ( A - > g_msg_type , " Raw GPS data " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 00:35:07 +00:00
}
2018-11-24 22:20:45 +00:00
else if ( strncmp ( ( char * ) info , " $GPGGA, " , 7 ) = = 0 | |
strncmp ( ( char * ) info , " $GNGGA, " , 7 ) = = 0 )
2015-07-27 00:35:07 +00:00
{
2015-11-08 01:57:02 +00:00
float alt_meters = G_UNKNOWN ;
int num_sat = 0 ;
2015-07-27 00:35:07 +00:00
2015-11-08 01:57:02 +00:00
( void ) dwgpsnmea_gpgga ( ( char * ) info , A - > g_quiet , & ( A - > g_lat ) , & ( A - > g_lon ) , & alt_meters , & num_sat ) ;
A - > g_altitude_ft = DW_METERS_TO_FEET ( alt_meters ) ;
2019-04-21 23:27:44 +00:00
strlcpy ( A - > g_msg_type , " Raw GPS data " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 00:35:07 +00:00
}
2015-09-07 23:56:20 +00:00
2015-11-08 01:57:02 +00:00
// TODO (low): add a few other sentence types.
2015-09-07 23:56:20 +00:00
2015-11-08 01:57:02 +00:00
} /* end aprs_raw_nmea */
2015-07-27 00:35:07 +00:00
/*------------------------------------------------------------------
*
* Function : aprs_mic_e
*
* Purpose : Decode MIC - E ( also Kenwood D7 & D700 ) packet .
*
* Inputs : info - Pointer to Information field .
* ilen - Information field length .
*
* Outputs :
*
* Description :
*
2015-09-07 23:56:20 +00:00
* Destination Address Field -
2015-07-27 00:35:07 +00:00
*
* The 7 - byte Destination Address field contains
* the following encoded information :
*
* * The 6 latitude digits .
* * A 3 - bit Mic - E message identifier , specifying one of 7 Standard Mic - E
* Message Codes or one of 7 Custom Message Codes or an Emergency
* Message Code .
* * The North / South and West / East Indicators .
* * The Longitude Offset Indicator .
* * The generic APRS digipeater path code .
*
* " Although the destination address appears to be quite unconventional, it is
* still a valid AX .25 address , consisting only of printable 7 - bit ASCII values . "
*
* References : Mic - E TYPE CODES - - http : //www.aprs.org/aprs12/mic-e-types.txt
*
2017-07-20 02:03:40 +00:00
* This is up to date with the 24 Aug 16 version mentioning the TH - D74 .
*
2015-07-27 00:35:07 +00:00
* Mic - E TEST EXAMPLES - - http : //www.aprs.org/aprs12/mic-e-examples.txt
*
* Examples : ` b9Z ! 4 y > / > " 4N}Paul's_TH-D7
*
* TODO : Destination SSID can contain generic digipeater path .
*
* Bugs : Doesn ' t handle ambiguous position . " space " treated as zero .
* Invalid data results in a message but latitude is not set to unknown .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:05:48 +00:00
/* a few test cases
# example from http: //www.aprs.org/aprs12/mic-e-examples.txt produces 4 errors.
# TODO: Analyze all the bits someday and possibly report problem with document.
N0CALL > ABCDEF : ' abc123R / text
# Let's use an actual valid location and concentrate on the manufacturers
# as listed in http: //www.aprs.org/aprs12/mic-e-types.txt
N1ZZN - 9 > T2SP0W : ` c_Vm6hk / ` " 49}Jeff Mobile_%
N1ZZN - 9 > T2SP0W : ` c_Vm6hk / " 49}Originl Mic-E (leading space)
N1ZZN - 9 > T2SP0W : ` c_Vm6hk / > " 49}TH-D7A walkie Talkie
N1ZZN - 9 > T2SP0W : ` c_Vm6hk / > " 49}TH-D72 walkie Talkie=
2016-09-17 23:20:07 +00:00
W6GPS > S4PT3R : ` p ( 1 oR0K \ > TH - D74A ^
2015-07-27 01:05:48 +00:00
N1ZZN - 9 > T2SP0W : ` c_Vm6hk / ] " 49}TM-D700 MObile Radio
N1ZZN - 9 > T2SP0W : ` c_Vm6hk / ] " 49}TM-D710 Mobile Radio=
# Note: next line has trailing space character after _
N1ZZN - 9 > T2SP0W : ` c_Vm6hk / ` " 49}Yaesu VX-8_
N1ZZN - 9 > T2SP0W : ` c_Vm6hk / ` " 49}Yaesu FTM-350_ "
N1ZZN - 9 > T2SP0W : ` c_Vm6hk / ` " 49}Yaesu VX-8G_#
N1ZZN - 9 > T2SP0W : ` c_Vm6hk / ` " 49}Yaesu FT1D_$
N1ZZN - 9 > T2SP0W : ` c_Vm6hk / ` " 49}Yaesu FTM-400DR_%
N1ZZN - 9 > T2SP0W : ' c_Vm6hk / ` " 49}Byonics TinyTrack3|3
N1ZZN - 9 > T2SP0W : ' c_Vm6hk / ` " 49}Byonics TinyTrack4|4
# The next group starts with metacharacter "T" which can be any of space > ] ` '
# But space is for original Mic-E, # > and ] are for Kenwood,
2021-09-19 18:51:18 +00:00
# so ` ' would probably be less ambiguous choices but any appear to be valid.
2015-07-27 01:05:48 +00:00
N1ZZN - 9 > T2SP0W : ' c_Vm6hk / ` " 49}Hamhud \ 9
N1ZZN - 9 > T2SP0W : ' c_Vm6hk / ` " 49}Argent/9
N1ZZN - 9 > T2SP0W : ' c_Vm6hk / ` " 49}HinzTec anyfrog^9
N1ZZN - 9 > T2SP0W : ' c_Vm6hk / ` " 49}APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO*9
N1ZZN - 9 > T2SP0W : ' c_Vm6hk / ` " 49}OTHER~9
# TODO: Why is manufacturer unknown? Should we explicitly say unknown?
[ 0 ] VE2VL - 9 > TU3V0P , VE2PCQ - 3 , WIDE1 , W1UWS - 1 , UNCAN , WIDE2 * : ` eB ? l " )v/ " 3 y }
MIC - E , VAN , En Route
[ 0 ] VE2VL - 9 > TU3U5Q , VE2PCQ - 3 , WIDE1 , W1UWS - 1 , N1NCI - 3 , WIDE2 * : ` eBgl " $v/ " 42 } 73 de Julien , Tinytrak 3
MIC - E , VAN , En Route
[ 0 ] W1ERB - 9 > T1SW8P , KB1AEV - 15 , N1NCI - 3 , WIDE2 * : ` dI8l ! # j / " 3m}
MIC - E , JEEP , In Service
[ 0 ] W1ERB - 9 > T1SW8Q , KB1AEV - 15 , N1NCI - 3 , WIDE2 * : ` dI6l { ^ j / " 4+}IntheJeep..try146.79(PVRA)
" 146.79 " in comment looks like a frequency in non - standard format .
For most systems to recognize it , use exactly this form " 146.790MHz " at beginning of comment .
MIC - E , JEEP , In Service
*/
2015-07-27 01:17:23 +00:00
static int mic_e_digit ( decode_aprs_t * A , char c , int mask , int * std_msg , int * cust_msg )
2015-07-27 00:35:07 +00:00
{
if ( c > = ' 0 ' & & c < = ' 9 ' ) {
return ( c - ' 0 ' ) ;
}
if ( c > = ' A ' & & c < = ' J ' ) {
* cust_msg | = mask ;
return ( c - ' A ' ) ;
}
if ( c > = ' P ' & & c < = ' Y ' ) {
* std_msg | = mask ;
return ( c - ' P ' ) ;
}
/* K, L, Z should be converted to space. */
/* others are invalid. */
/* But caller expects only values 0 - 9. */
if ( c = = ' K ' ) {
* cust_msg | = mask ;
return ( 0 ) ;
}
if ( c = = ' L ' ) {
return ( 0 ) ;
}
if ( c = = ' Z ' ) {
* std_msg | = mask ;
return ( 0 ) ;
}
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid character \" %c \" in MIC-E destination/latitude. \n " , c ) ;
}
2015-07-27 00:35:07 +00:00
return ( 0 ) ;
}
2015-07-27 01:05:48 +00:00
static void aprs_mic_e ( decode_aprs_t * A , packet_t pp , unsigned char * info , int ilen )
2015-07-27 00:35:07 +00:00
{
struct aprs_mic_e_s {
char dti ; /* ' or ` */
unsigned char lon [ 3 ] ; /* "d+28", "m+28", "h+28" */
unsigned char speed_course [ 3 ] ;
char symbol_code ;
char sym_table_id ;
} * p ;
2022-02-25 22:29:07 +00:00
char dest [ 12 ] ;
2015-07-27 00:35:07 +00:00
int ch ;
int n ;
int offset ;
int std_msg = 0 ;
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 ;
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " MIC-E " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 00:35:07 +00:00
p = ( struct aprs_mic_e_s * ) info ;
/* Destination is really latitude of form ddmmhh. */
/* Message codes are buried in the first 3 digits. */
ax25_get_addr_with_ssid ( pp , AX25_DESTINATION , dest ) ;
2015-07-27 01:17:23 +00:00
A - > g_lat = mic_e_digit ( A , dest [ 0 ] , 4 , & std_msg , & cust_msg ) * 10 +
mic_e_digit ( A , dest [ 1 ] , 2 , & std_msg , & cust_msg ) +
( mic_e_digit ( A , dest [ 2 ] , 1 , & std_msg , & cust_msg ) * 1000 +
mic_e_digit ( A , dest [ 3 ] , 0 , & std_msg , & cust_msg ) * 100 +
mic_e_digit ( A , dest [ 4 ] , 0 , & std_msg , & cust_msg ) * 10 +
mic_e_digit ( A , dest [ 5 ] , 0 , & std_msg , & cust_msg ) ) / 6000.0 ;
2015-07-27 00:35:07 +00:00
2021-09-19 18:51:18 +00:00
/* 4th character of destination indicates north / south. */
2015-07-27 00:35:07 +00:00
if ( ( dest [ 3 ] > = ' 0 ' & & dest [ 3 ] < = ' 9 ' ) | | dest [ 3 ] = = ' L ' ) {
/* South */
2015-07-27 01:05:48 +00:00
A - > g_lat = ( - A - > g_lat ) ;
2015-07-27 00:35:07 +00:00
}
else if ( dest [ 3 ] > = ' P ' & & dest [ 3 ] < = ' Z ' )
{
/* North */
}
else
{
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid MIC-E N/S encoding in 4th character of destination. \n " ) ;
}
2015-07-27 00:35:07 +00:00
}
/* Longitude is mostly packed into 3 bytes of message but */
/* has a couple bits of information in the destination. */
if ( ( dest [ 4 ] > = ' 0 ' & & dest [ 4 ] < = ' 9 ' ) | | dest [ 4 ] = = ' L ' )
{
offset = 0 ;
}
else if ( dest [ 4 ] > = ' P ' & & dest [ 4 ] < = ' Z ' )
{
offset = 1 ;
}
else
{
offset = 0 ;
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid MIC-E Longitude Offset in 5th character of destination. \n " ) ;
}
2015-07-27 00:35:07 +00:00
}
/* First character of information field is longitude in degrees. */
/* It is possible for the unprintable DEL character to occur here. */
2021-09-19 18:51:18 +00:00
/* 5th character of destination indicates longitude offset of +100. */
2015-07-27 00:35:07 +00:00
/* Not quite that simple :-( */
ch = p - > lon [ 0 ] ;
if ( offset & & ch > = 118 & & ch < = 127 )
{
2015-07-27 01:05:48 +00:00
A - > g_lon = ch - 118 ; /* 0 - 9 degrees */
2015-07-27 00:35:07 +00:00
}
else if ( ! offset & & ch > = 38 & & ch < = 127 )
{
2015-07-27 01:05:48 +00:00
A - > g_lon = ( ch - 38 ) + 10 ; /* 10 - 99 degrees */
2015-07-27 00:35:07 +00:00
}
else if ( offset & & ch > = 108 & & ch < = 117 )
{
2015-07-27 01:05:48 +00:00
A - > g_lon = ( ch - 108 ) + 100 ; /* 100 - 109 degrees */
2015-07-27 00:35:07 +00:00
}
else if ( offset & & ch > = 38 & & ch < = 107 )
{
2015-07-27 01:05:48 +00:00
A - > g_lon = ( ch - 38 ) + 110 ; /* 110 - 179 degrees */
2015-07-27 00:35:07 +00:00
}
else
{
2015-07-27 01:17:23 +00:00
A - > g_lon = G_UNKNOWN ;
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid character 0x%02x for MIC-E Longitude Degrees. \n " , ch ) ;
}
2015-07-27 00:35:07 +00:00
}
2015-07-27 01:05:48 +00:00
/* Second character of information field is A->g_longitude minutes. */
2015-07-27 00:35:07 +00:00
/* These are all printable characters. */
/*
* More than once I ' ve see the TH - D72A put < 0x1a > here and flip between north and south .
*
* WB2OSZ > TRSW1R , WIDE1 - 1 , WIDE2 - 2 : ` c0ol ! O [ / > = < 0x0d >
* N 42 37.1200 , W 071 20.8300 , 0 MPH , course 151
*
* WB2OSZ > TRS7QR , WIDE1 - 1 , WIDE2 - 2 : ` v < 0x1a > n < 0x1c > " P[/>=<0x0d>
* Invalid character 0x1a for MIC - E Longitude Minutes .
* S 42 37.1200 , Invalid Longitude , 0 MPH , course 252
*
* This was direct over the air with no opportunity for a digipeater
* or anything else to corrupt the message .
*/
2015-07-27 01:05:48 +00:00
if ( A - > g_lon ! = G_UNKNOWN )
2015-07-27 00:35:07 +00:00
{
ch = p - > lon [ 1 ] ;
if ( ch > = 88 & & ch < = 97 )
{
2015-07-27 01:05:48 +00:00
A - > g_lon + = ( ch - 88 ) / 60.0 ; /* 0 - 9 minutes*/
2015-07-27 00:35:07 +00:00
}
else if ( ch > = 38 & & ch < = 87 )
{
2015-07-27 01:05:48 +00:00
A - > g_lon + = ( ( ch - 38 ) + 10 ) / 60.0 ; /* 10 - 59 minutes */
2015-07-27 00:35:07 +00:00
}
else {
2015-07-27 01:05:48 +00:00
A - > g_lon = G_UNKNOWN ;
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid character 0x%02x for MIC-E Longitude Minutes. \n " , ch ) ;
}
2015-07-27 00:35:07 +00:00
}
/* Third character of information field is longitude hundredths of minutes. */
/* There are 100 possible values, from 0 to 99. */
/* Note that the range includes 4 unprintable control characters and DEL. */
2015-07-27 01:05:48 +00:00
if ( A - > g_lon ! = G_UNKNOWN )
2015-07-27 00:35:07 +00:00
{
ch = p - > lon [ 2 ] ;
if ( ch > = 28 & & ch < = 127 )
{
2015-07-27 01:05:48 +00:00
A - > g_lon + = ( ( ch - 28 ) + 0 ) / 6000.0 ; /* 0 - 99 hundredths of minutes*/
2015-07-27 00:35:07 +00:00
}
else {
2015-07-27 01:05:48 +00:00
A - > g_lon = G_UNKNOWN ;
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid character 0x%02x for MIC-E Longitude hundredths of Minutes. \n " , ch ) ;
}
2015-07-27 00:35:07 +00:00
}
}
}
/* 6th character of destintation indicates east / west. */
2015-07-27 01:05:48 +00:00
/*
* Example of apparently invalid encoding . 6 th character missing .
*
* [ 0 ] KB1HOZ - 9 > TTRW5 , KQ1L - 2 , WIDE1 , KQ1L - 8 , UNCAN , WIDE2 * : ` aFo " ]|k/] " 4 m } < 0x0d >
* Invalid character " Invalid MIC-E E/W encoding in 6th character of destination.
* MIC - E , truck , Kenwood TM - D700 , Off Duty
* N 44 27.5000 , E 06 9 42.8300 , 76 MPH , course 196 , alt 282 ft
*/
2015-07-27 00:35:07 +00:00
if ( ( dest [ 5 ] > = ' 0 ' & & dest [ 5 ] < = ' 9 ' ) | | dest [ 5 ] = = ' L ' ) {
/* East */
}
else if ( dest [ 5 ] > = ' P ' & & dest [ 5 ] < = ' Z ' )
{
/* West */
2015-07-27 01:05:48 +00:00
if ( A - > g_lon ! = G_UNKNOWN ) {
A - > g_lon = ( - A - > g_lon ) ;
2015-07-27 00:35:07 +00:00
}
}
else
{
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid MIC-E E/W encoding in 6th character of destination. \n " ) ;
}
2015-07-27 00:35:07 +00:00
}
/* Symbol table and codes like everyone else. */
2015-07-27 01:05:48 +00:00
A - > g_symbol_table = p - > sym_table_id ;
A - > g_symbol_code = p - > symbol_code ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
if ( A - > g_symbol_table ! = ' / ' & & A - > g_symbol_table ! = ' \\ '
& & ! isupper ( A - > g_symbol_table ) & & ! isdigit ( A - > g_symbol_table ) )
2015-07-27 00:35:07 +00:00
{
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid symbol table code not one of / \\ A-Z 0-9 \n " ) ;
}
2015-07-27 01:05:48 +00:00
A - > g_symbol_table = ' / ' ;
2015-07-27 00:35:07 +00:00
}
/* Message type from two 3-bit codes. */
if ( std_msg = = 0 & & cust_msg = = 0 ) {
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_mic_e_status , " Emergency " , sizeof ( A - > g_mic_e_status ) ) ;
2015-07-27 00:35:07 +00:00
}
else if ( std_msg = = 0 & & cust_msg ! = 0 ) {
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_mic_e_status , cust_text [ cust_msg ] , sizeof ( A - > g_mic_e_status ) ) ;
2015-07-27 00:35:07 +00:00
}
else if ( std_msg ! = 0 & & cust_msg = = 0 ) {
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_mic_e_status , std_text [ std_msg ] , sizeof ( A - > g_mic_e_status ) ) ;
2015-07-27 00:35:07 +00:00
}
else {
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_mic_e_status , " Unknown MIC-E Message Type " , sizeof ( A - > g_mic_e_status ) ) ;
2015-07-27 00:35:07 +00:00
}
/* Speed and course from next 3 bytes. */
n = ( ( p - > speed_course [ 0 ] - 28 ) * 10 ) + ( ( p - > speed_course [ 1 ] - 28 ) / 10 ) ;
if ( n > = 800 ) n - = 800 ;
2015-11-08 01:57:02 +00:00
A - > g_speed_mph = DW_KNOTS_TO_MPH ( n ) ;
2015-07-27 00:35:07 +00:00
n = ( ( p - > speed_course [ 1 ] - 28 ) % 10 ) * 100 + ( p - > speed_course [ 2 ] - 28 ) ;
if ( n > = 400 ) n - = 400 ;
/* Result is 0 for unknown and 1 - 360 where 360 is north. */
/* Convert to 0 - 360 and reserved value for unknown. */
if ( n = = 0 )
2015-07-27 01:05:48 +00:00
A - > g_course = G_UNKNOWN ;
2015-07-27 00:35:07 +00:00
else if ( n = = 360 )
2015-07-27 01:05:48 +00:00
A - > g_course = 0 ;
2015-07-27 00:35:07 +00:00
else
2015-07-27 01:05:48 +00:00
A - > g_course = n ;
2015-07-27 00:35:07 +00:00
/* Now try to pick out manufacturer and other optional items. */
/* The telemetry field, in the original spec, is no longer used. */
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_mfr , " Unknown manufacturer " , sizeof ( A - > g_mfr ) ) ;
2015-07-27 01:05:48 +00:00
2015-07-27 00:35:07 +00:00
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. */
2015-07-27 01:05:48 +00:00
/* 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? */
2015-07-27 00:35:07 +00:00
if ( * plast = = ' \r ' ) plast - - ;
2015-07-27 01:05:48 +00:00
# define isT(c) ((c) == ' ' || (c) == '>' || (c) == ']' || (c) == '`' || (c) == '\'')
2022-01-02 02:55:11 +00:00
// Last Updated Dec. 2021
2022-02-25 22:29:07 +00:00
// This does not change very often but I'm wondering if we could parse
2022-01-02 02:55:11 +00:00
// http://www.aprs.org/aprs12/mic-e-types.txt similar to how we use tocalls.txt.
2015-07-27 01:05:48 +00:00
if ( isT ( * pfirst ) ) {
2022-01-02 02:55:11 +00:00
// "legacy" formats.
2015-07-27 00:35:07 +00:00
2016-09-17 23:20:07 +00:00
if ( * pfirst = = ' ' ) { strlcpy ( A - > g_mfr , " Original MIC-E " , sizeof ( A - > g_mfr ) ) ; pfirst + + ; }
2015-07-27 01:05:48 +00:00
2016-09-17 23:20:07 +00:00
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 = = ' > ' ) { strlcpy ( A - > g_mfr , " Kenwood TH-D7A " , sizeof ( A - > g_mfr ) ) ; pfirst + + ; }
2015-07-27 01:05:48 +00:00
2016-09-17 23:20:07 +00:00
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 + + ; }
2015-07-27 00:35:07 +00:00
2022-01-02 02:55:11 +00:00
// ` should be used for message capable devices.
2016-09-17 23:20:07 +00:00
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 ; }
2019-08-27 10:50:55 +00:00
else if ( * pfirst = = ' ` ' & & * ( plast - 1 ) = = ' _ ' & & * plast = = ' 0 ' ) { strlcpy ( A - > g_mfr , " Yaesu FT3D " , sizeof ( A - > g_mfr ) ) ; pfirst + + ; plast - = 2 ; }
2022-01-02 02:55:11 +00:00
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 ; }
2015-07-27 00:35:07 +00:00
2016-09-17 23:20:07 +00:00
else if ( * pfirst = = ' ` ' & & * ( plast - 1 ) = = ' ' & & * plast = = ' X ' ) { strlcpy ( A - > g_mfr , " AP510 " , sizeof ( A - > g_mfr ) ) ; pfirst + + ; plast - = 2 ; }
2022-01-02 02:55:11 +00:00
2019-08-27 10:50:55 +00:00
else if ( * pfirst = = ' ` ' & & * ( plast - 1 ) = = ' ( ' & & * plast = = ' 5 ' ) { strlcpy ( A - > g_mfr , " Anytone D578UV " , sizeof ( A - > g_mfr ) ) ; pfirst + + ; plast - = 2 ; }
2022-01-02 02:55:11 +00:00
else if ( * pfirst = = ' ` ' ) { strlcpy ( A - > g_mfr , " Generic Mic-Emsg " , sizeof ( A - > g_mfr ) ) ; pfirst + + ; }
2015-07-27 00:35:07 +00:00
2022-01-02 02:55:11 +00:00
// ' should be used for trackers (not message capable).
2015-07-27 00:35:07 +00:00
2019-08-27 10:50:55 +00:00
else if ( * pfirst = = ' \' ' & & * ( plast - 1 ) = = ' ( ' & & * plast = = ' 8 ' ) { strlcpy ( A - > g_mfr , " Anytone D878UV " , sizeof ( A - > g_mfr ) ) ; pfirst + + ; plast - = 2 ; }
2016-09-17 23:20:07 +00:00
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 ; }
2015-07-27 00:35:07 +00:00
2016-09-17 23:20:07 +00:00
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 ; }
2022-01-02 02:55:11 +00:00
else if ( * pfirst = = ' \' ' ) { strlcpy ( A - > g_mfr , " Generic McTrackr " , sizeof ( A - > g_mfr ) ) ; pfirst + + ; }
2016-09-17 23:20:07 +00:00
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 ; }
2022-01-02 02:55:11 +00:00
else if ( * ( plast - 1 ) = = ' ~ ' ) { strlcpy ( A - > g_mfr , " Unknown OTHER " , sizeof ( A - > g_mfr ) ) ; pfirst + + ; plast - = 2 ; }
2015-07-27 00:35:07 +00:00
}
/*
* 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.475 MHz
*
* KJ4ETP - 9 > SUUP6Y , GRNTOP - 3 * , WIDE2 - 1 , qAR , KI4HDU - 2 : ` oU ~ nT > / ] < 0x9a > xt } 162.475 MHz < 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.475 MHz
*
* KJ4ETP - 9 > SUUP6Y , N4NEQ - 3 , K4EGA - 1 , WIDE2 * , qAS , N5CWH - 1 : ` oU ~ nT > / ] ? xt } 162.475 MHz < Knox , TN > clintserman @ gmail =
* N 35 50.6900 , W 083 57.9800 , 29 MPH , course 204 , alt 808497 ft , 162.475 MHz
*
* KJ4ETP - 9 > SUUP2W , KE4OTZ - 3 , WIDE1 * , WIDE2 - 1 , qAR , KI4HDU - 2 : ` oV2o " J>/] " 7 ) } 162.475 MHz < Knox , TN > clintserman @ gmail =
* N 35 50.2700 , W 083 58.2200 , 35 MPH , course 246 , alt 955 ft , 162.475 MHz
*
* Note the < 0x9a > which is outside of the 7 - bit ASCII range . Clearly very wrong .
*/
if ( plast > pfirst & & pfirst [ 3 ] = = ' } ' ) {
2015-11-08 01:57:02 +00:00
A - > g_altitude_ft = DW_METERS_TO_FEET ( ( pfirst [ 0 ] - 33 ) * 91 * 91 + ( pfirst [ 1 ] - 33 ) * 91 + ( pfirst [ 2 ] - 33 ) - 10000 ) ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
if ( ! isdigit91 ( pfirst [ 0 ] ) | | ! isdigit91 ( pfirst [ 1 ] ) | | ! isdigit91 ( pfirst [ 2 ] ) )
2015-07-27 00:35:07 +00:00
{
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid character in MIC-E altitude. Must be in range of '!' to '{'. \n " ) ;
2015-11-08 01:57:02 +00:00
dw_printf ( " Bogus altitude of %.0f changed to unknown. \n " , A - > g_altitude_ft ) ;
2015-07-27 01:17:23 +00:00
}
2015-11-08 01:57:02 +00:00
A - > g_altitude_ft = G_UNKNOWN ;
2015-07-27 00:35:07 +00:00
}
pfirst + = 4 ;
}
2015-07-27 01:05:48 +00:00
process_comment ( A , ( char * ) pfirst , ( int ) ( plast - pfirst ) + 1 ) ;
2015-07-27 00:35:07 +00:00
}
/*------------------------------------------------------------------
*
* Function : aprs_message
*
2016-05-01 22:46:47 +00:00
* Purpose : Decode " Message Format. "
* The word message is used loosely all over the place , but it has a very specific meaning here .
2015-07-27 00:35:07 +00:00
*
* Inputs : info - Pointer to Information field .
* ilen - Information field length .
2021-09-19 18:51:18 +00:00
* quiet - suppress error messages .
2015-07-27 00:35:07 +00:00
*
2016-05-01 22:46:47 +00:00
* Outputs : A - > g_msg_type Text description for screen display .
*
* A - > g_addressee To whom is it addressed .
* Could be a specific station , alias , bulletin , etc .
* For telemetry metadata is is about this station ,
* not being sent to it .
*
* A - > g_message_subtype Subtype so caller might avoid replicating
* all the code to distinguish them .
*
* A - > g_message_number Message number if any . Required for ack / rej .
2015-07-27 00:35:07 +00:00
*
2016-05-29 14:51:54 +00:00
* Description : An APRS message is a text string with a specified addressee .
2015-07-27 00:35:07 +00:00
*
* It ' s a lot more complicated with different types of addressees
* and replies with acknowledgement or rejection .
*
2016-05-01 22:46:47 +00:00
* There is even a special case for telemetry metadata .
2015-07-27 00:35:07 +00:00
*
2016-05-01 22:46:47 +00:00
*
* Cases : : xxxxxxxxx : PARM . Telemetry metadata , parameter name
* : xxxxxxxxx : UNIT . Telemetry metadata , unit / label
2021-09-19 18:51:18 +00:00
* : xxxxxxxxx : EQNS . Telemetry metadata , Equation Coefficients
2016-05-01 22:46:47 +00:00
* : xxxxxxxxx : BITS . Telemetry metadata , Bit Sense / Project Name
* : xxxxxxxxx : ? Directed Station Query
2022-02-25 22:29:07 +00:00
* : xxxxxxxxx : ackNNNN Message acknowledged ( received )
* : xxxxxxxxx : rejNNNNN Message rejected ( unable to accept )
2016-05-01 22:46:47 +00:00
*
* : xxxxxxxxx : . . . Message with no message number .
* ( Text may not contain the { character because
* it indicates beginning of optional message number . )
2022-02-25 22:29:07 +00:00
* : xxxxxxxxx : . . . { NNNNN Message with message number , 1 to 5 alphanumeric .
* : xxxxxxxxx : . . . { mm } Message with new style message number .
* : xxxxxxxxx : . . . { mm } aa Message with new style message number and ack .
*
*
* Reference : See new message id style : http : //www.aprs.org/aprs11/replyacks.txt
2015-07-27 00:35:07 +00:00
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:17:23 +00:00
static void aprs_message ( decode_aprs_t * A , unsigned char * info , int ilen , int quiet )
2015-07-27 00:35:07 +00:00
{
struct aprs_message_s {
char dti ; /* : */
char addressee [ 9 ] ;
char colon ; /* : */
2022-02-25 22:29:07 +00:00
char message [ 256 - 1 - 9 - 1 ] ; /* Officially up to 67 characters for message text. */
/* Relaxing seemingly arbitrary restriction here; it doesn't need to fit on a punched card. */
/* Wouldn't surprize me if others did not pay attention to the limit. */
/* Optional { followed by 1-5 alphanumeric characters for message number */
2015-09-07 23:56:20 +00:00
2021-09-19 18:51:18 +00:00
/* If the first character is '?' it is a Directed Station Query. */
2015-07-27 00:35:07 +00:00
} * p ;
2015-07-27 01:05:48 +00:00
char addressee [ AX25_MAX_ADDR_LEN ] ;
int i ;
2015-07-27 00:35:07 +00:00
p = ( struct aprs_message_s * ) info ;
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " APRS Message " , sizeof ( A - > g_msg_type ) ) ;
2016-05-01 22:46:47 +00:00
A - > g_message_subtype = message_subtype_message ; /* until found otherwise */
2015-09-07 23:56:20 +00:00
if ( ilen < 11 ) {
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
2016-05-01 22:46:47 +00:00
dw_printf ( " APRS Message must have a minimum of 11 characters for : 9 character addressee : \n " ) ;
2015-09-07 23:56:20 +00:00
}
2016-05-01 22:46:47 +00:00
A - > g_message_subtype = message_subtype_invalid ;
2015-09-07 23:56:20 +00:00
return ;
}
if ( p - > colon ! = ' : ' ) {
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
2022-01-17 22:10:47 +00:00
dw_printf ( " APRS Message must begin with ':' 9 character addressee ':' \n " ) ;
dw_printf ( " Spaces must be added to shorter addressee to make 9 characters. \n " ) ;
2015-09-07 23:56:20 +00:00
}
2016-05-01 22:46:47 +00:00
A - > g_message_subtype = message_subtype_invalid ;
2015-09-07 23:56:20 +00:00
return ;
}
2015-07-27 01:05:48 +00:00
memset ( addressee , 0 , sizeof ( addressee ) ) ;
2015-09-07 23:56:20 +00:00
memcpy ( addressee , p - > addressee , sizeof ( p - > addressee ) ) ; // copy exactly 9 bytes.
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
/* Trim trailing spaces. */
i = strlen ( addressee ) - 1 ;
while ( i > = 0 & & addressee [ i ] = = ' ' ) {
addressee [ i - - ] = ' \0 ' ;
}
2015-07-27 00:35:07 +00:00
2022-01-17 22:10:47 +00:00
// Anytone AT-D878UV 2 plus would pad out station name to 6 characters
// before appending the SSID. e.g. "AE7MK -5 "
// Test cases. First is valid. Others should produce errors:
//
// cbeacon sendto=r0 delay=0:10 info=":AE7MK-5 :test0"
// cbeacon sendto=r0 delay=0:15 info=":AE7MK-5:test1"
// cbeacon sendto=r0 delay=0:20 info=":AE7MK -5 :test2"
// cbeacon sendto=r0 delay=0:25 info=":AE7 -5 :test3"
static regex_t bad_addressee_re ; /* Probably bad addressee. */
static int first_time = 1 ;
if ( first_time ) {
char emsg [ 100 ] ;
int e = regcomp ( & bad_addressee_re , " [A-Z0-9]+ +-[0-9] " , REG_EXTENDED ) ;
if ( e ) {
regerror ( e , & bad_addressee_re , emsg , sizeof ( emsg ) ) ;
dw_printf ( " %s:%d: %s \n " , __FILE__ , __LINE__ , emsg ) ;
}
first_time = 0 ;
}
# define MAXMATCH_AT 2
regmatch_t match [ MAXMATCH_AT ] ;
if ( regexec ( & bad_addressee_re , addressee , MAXMATCH_AT , match , 0 ) = = 0 ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Malformed addressee with space between station name and SSID. \n " ) ;
dw_printf ( " Please tell message sender this is invalid. \n " ) ;
}
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_addressee , addressee , sizeof ( A - > g_addressee ) ) ;
2015-07-27 01:17:23 +00:00
2016-05-01 22:46:47 +00:00
2015-07-27 01:05:48 +00:00
/*
* Special message formats contain telemetry metadata .
* It applies to the addressee , not the sender .
* Makes no sense to me that it would not apply to sender instead .
* Wouldn ' t the sender be describing his own data ?
*
* I also don ' t understand the reasoning for putting this in a " message. "
* Telemetry data always starts with " # " after the " T " data type indicator .
* Why not use other characters after the " T " for metadata ?
*/
if ( strncmp ( p - > message , " PARM. " , 5 ) = = 0 ) {
2015-09-07 23:56:20 +00:00
snprintf ( A - > g_msg_type , sizeof ( A - > g_msg_type ) , " Telemetry Parameter Name Message for \" %s \" " , addressee ) ;
2016-05-01 22:46:47 +00:00
A - > g_message_subtype = message_subtype_telem_parm ;
2015-07-27 01:05:48 +00:00
telemetry_name_message ( addressee , p - > message + 5 ) ;
}
else if ( strncmp ( p - > message , " UNIT. " , 5 ) = = 0 ) {
2015-09-07 23:56:20 +00:00
snprintf ( A - > g_msg_type , sizeof ( A - > g_msg_type ) , " Telemetry Unit/Label Message for \" %s \" " , addressee ) ;
2016-05-01 22:46:47 +00:00
A - > g_message_subtype = message_subtype_telem_unit ;
2015-07-27 01:05:48 +00:00
telemetry_unit_label_message ( addressee , p - > message + 5 ) ;
}
else if ( strncmp ( p - > message , " EQNS. " , 5 ) = = 0 ) {
2021-09-19 19:15:34 +00:00
snprintf ( A - > g_msg_type , sizeof ( A - > g_msg_type ) , " Telemetry Equation Coefficients Message for \" %s \" " , addressee ) ;
2016-05-01 22:46:47 +00:00
A - > g_message_subtype = message_subtype_telem_eqns ;
2015-07-27 01:17:23 +00:00
telemetry_coefficents_message ( addressee , p - > message + 5 , quiet ) ;
2015-07-27 01:05:48 +00:00
}
else if ( strncmp ( p - > message , " BITS. " , 5 ) = = 0 ) {
2015-09-07 23:56:20 +00:00
snprintf ( A - > g_msg_type , sizeof ( A - > g_msg_type ) , " Telemetry Bit Sense/Project Name Message for \" %s \" " , addressee ) ;
2016-05-01 22:46:47 +00:00
A - > g_message_subtype = message_subtype_telem_bits ;
2015-07-27 01:17:23 +00:00
telemetry_bit_sense_message ( addressee , p - > message + 5 , quiet ) ;
2015-07-27 01:05:48 +00:00
}
2015-09-07 23:56:20 +00:00
/*
* If first character of message is " ? " it is a query directed toward a specific station .
*/
else if ( p - > message [ 0 ] = = ' ? ' ) {
strlcpy ( A - > g_msg_type , " Directed Station Query " , sizeof ( A - > g_msg_type ) ) ;
2016-05-01 22:46:47 +00:00
A - > g_message_subtype = message_subtype_directed_query ;
2015-09-07 23:56:20 +00:00
aprs_directed_station_query ( A , addressee , p - > message + 1 , quiet ) ;
}
2016-05-01 22:46:47 +00:00
/* ack or rej? Message number is required for these. */
2022-02-25 22:29:07 +00:00
else if ( strncasecmp ( p - > message , " ack " , 3 ) = = 0 ) {
if ( strncmp ( p - > message , " ack " , 3 ) ! = 0 ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " ERROR: \" %s \" must be lower case \" ack \" \n " , p - > message ) ;
}
2016-05-01 22:46:47 +00:00
strlcpy ( A - > g_message_number , p - > message + 3 , sizeof ( A - > g_message_number ) ) ;
2022-02-25 22:29:07 +00:00
if ( strlen ( A - > g_message_number ) = = 0 ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " ERROR: Message number is missing after \" ack \" . \n " ) ;
}
if ( strlen ( A - > g_message_number ) > = 3 & & A - > g_message_number [ 2 ] = = ' } ' ) A - > g_message_number [ 2 ] = ' \0 ' ;
snprintf ( A - > g_msg_type , sizeof ( A - > g_msg_type ) , " \" %s \" ACKnowledged message number \" %s \" from \" %s \" " , A - > g_src , A - > g_message_number , addressee ) ;
2016-05-01 22:46:47 +00:00
A - > g_message_subtype = message_subtype_ack ;
}
2022-02-25 22:29:07 +00:00
else if ( strncasecmp ( p - > message , " rej " , 3 ) = = 0 ) {
if ( strncmp ( p - > message , " rej " , 3 ) ! = 0 ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " ERROR: \" %s \" must be lower case \" rej \" \n " , p - > message ) ;
}
2016-05-01 22:46:47 +00:00
strlcpy ( A - > g_message_number , p - > message + 3 , sizeof ( A - > g_message_number ) ) ;
2022-02-25 22:29:07 +00:00
if ( strlen ( A - > g_message_number ) = = 0 ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " ERROR: Message number is missing after \" rej \" . \n " ) ;
}
if ( strlen ( A - > g_message_number ) > = 3 & & A - > g_message_number [ 2 ] = = ' } ' ) A - > g_message_number [ 2 ] = ' \0 ' ;
snprintf ( A - > g_msg_type , sizeof ( A - > g_msg_type ) , " \" %s \" REJected message number \" %s \" from \" %s \" " , A - > g_src , A - > g_message_number , addressee ) ;
2016-05-01 22:46:47 +00:00
A - > g_message_subtype = message_subtype_ack ;
}
2022-02-25 22:29:07 +00:00
// Message to a particular station or a bulletin.
// message number is optional here.
// Test cases. Wrap in third party too.
// A>B::WA1XYX-15:Howdy y'all
// A>B::WA1XYX-15:Howdy y'all{12345
// A>B::WA1XYX-15:Howdy y'all{12}
// A>B::WA1XYX-15:Howdy y'all{12}34
// A>B::WA1XYX-15:Howdy y'all{toolong
// X>Y:}A>B::WA1XYX-15:Howdy y'all
// X>Y:}A>B::WA1XYX-15:Howdy y'all{12345
// X>Y:}A>B::WA1XYX-15:Howdy y'all{12}
// X>Y:}A>B::WA1XYX-15:Howdy y'all{12}34
// X>Y:}A>B::WA1XYX-15:Howdy y'all{toolong
2016-05-01 22:46:47 +00:00
2015-07-27 01:05:48 +00:00
else {
2022-02-25 22:29:07 +00:00
// Look for message number.
2016-05-01 22:46:47 +00:00
char * pno = strchr ( p - > message , ' { ' ) ;
if ( pno ! = NULL ) {
2022-02-25 22:29:07 +00:00
* pno = ' \0 ' ;
int mlen = strlen ( pno + 1 ) ;
if ( mlen < 1 | | mlen > 5 ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Message number \" %s \" has length outside range of 1 to 5. \n " , pno + 1 ) ;
}
2016-05-01 22:46:47 +00:00
strlcpy ( A - > g_message_number , pno + 1 , sizeof ( A - > g_message_number ) ) ;
2022-02-25 22:29:07 +00:00
// TODO: Complain if not alphanumeric.
char ack [ 8 ] = " " ;
if ( mlen > = 3 & & A - > g_message_number [ 2 ] = = ' } ' ) {
// New (1999) style.
A - > g_message_number [ 2 ] = ' \0 ' ;
strlcpy ( ack , A - > g_message_number + 3 , sizeof ( ack ) ) ;
}
if ( strlen ( ack ) > 0 ) {
// With ACK. Message number should be 2 characters.
snprintf ( A - > g_msg_type , sizeof ( A - > g_msg_type ) , " APRS Message, number \" %s \" , from \" %s \" to \" %s \" , with ACK for \" %s \" " , A - > g_message_number , A - > g_src , addressee , ack ) ;
}
else {
// Message number can be 1-5 characters.
snprintf ( A - > g_msg_type , sizeof ( A - > g_msg_type ) , " APRS Message, number \" %s \" , from \" %s \" to \" %s \" " , A - > g_message_number , A - > g_src , addressee ) ;
}
2016-05-01 22:46:47 +00:00
}
2022-02-25 22:29:07 +00:00
else {
// No message number.
snprintf ( A - > g_msg_type , sizeof ( A - > g_msg_type ) , " APRS Message, with no number, from \" %s \" to \" %s \" " , A - > g_src , addressee ) ;
}
2016-05-01 22:46:47 +00:00
A - > g_message_subtype = message_subtype_message ;
2015-07-27 01:05:48 +00:00
/* No location so don't use process_comment () */
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_comment , p - > message , sizeof ( A - > g_comment ) ) ;
2015-07-27 01:05:48 +00:00
}
2015-07-27 00:35:07 +00:00
}
/*------------------------------------------------------------------
*
* Function : aprs_object
*
* Purpose : Decode " Object Report Format "
*
* Inputs : info - Pointer to Information field .
* ilen - Information field length .
*
2015-11-08 01:57:02 +00:00
* Outputs : A - > g_object_name , A - > g_lat , A - > g_lon , A - > g_symbol_table , A - > g_symbol_code , A - > g_speed_mph , A - > g_course , A - > g_altitude_ft .
2015-07-27 00:35:07 +00:00
*
* Description : Message has a 9 character object name which could be quite different than
* the source station .
*
* This can also be a weather report when the symbol id is ' _ ' .
*
* Examples : ; WA2PNU * 050457 z4051 .72 N / 07325.53 W ] BBS & FlexNet 145.070 MHz
*
* ; ActonEOC * 070352 z4229 .20 N / 07125.95 WoFire , EMS , Police , Heli - pad , Dial 911
*
* ; IRLPC494 @ * 012112 zI9 * n * < ONV0 446325 - 146 IDLE < CR >
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:05:48 +00:00
static void aprs_object ( decode_aprs_t * A , unsigned char * info , int ilen )
2015-07-27 00:35:07 +00:00
{
struct aprs_object_s {
char dti ; /* ; */
char name [ 9 ] ;
char live_killed ; /* * for live or _ for killed */
char time_stamp [ 7 ] ;
position_t pos ;
char comment [ 43 ] ; /* First 7 bytes could be data extension. */
} * p ;
struct aprs_compressed_object_s {
char dti ; /* ; */
char name [ 9 ] ;
char live_killed ; /* * for live or _ for killed */
char time_stamp [ 7 ] ;
compressed_position_t cpos ;
char comment [ 40 ] ; /* No data extension in this case. */
} * q ;
time_t ts = 0 ;
int i ;
p = ( struct aprs_object_s * ) info ;
q = ( struct aprs_compressed_object_s * ) info ;
2015-09-07 23:56:20 +00:00
//assert (sizeof(A->g_name) > sizeof(p->name));
memset ( A - > g_name , 0 , sizeof ( A - > g_name ) ) ;
memcpy ( A - > g_name , p - > name , sizeof ( p - > name ) ) ; // copy exactly 9 bytes.
/* Trim trailing spaces. */
2015-07-27 01:05:48 +00:00
i = strlen ( A - > g_name ) - 1 ;
while ( i > = 0 & & A - > g_name [ i ] = = ' ' ) {
A - > g_name [ i - - ] = ' \0 ' ;
2015-07-27 00:35:07 +00:00
}
2015-09-07 23:56:20 +00:00
if ( p - > live_killed = = ' * ' )
strlcpy ( A - > g_msg_type , " Object " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 00:35:07 +00:00
else if ( p - > live_killed = = ' _ ' )
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " Killed Object " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 00:35:07 +00:00
else
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " Object - invalid live/killed " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
ts = get_timestamp ( A , p - > time_stamp ) ;
2015-07-27 00:35:07 +00:00
if ( isdigit ( ( unsigned char ) ( p - > pos . lat [ 0 ] ) ) ) /* Human-readable location. */
{
2015-07-27 01:05:48 +00:00
decode_position ( A , & ( p - > pos ) ) ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
if ( A - > g_symbol_code = = ' _ ' ) {
2015-07-27 00:35:07 +00:00
/* Symbol code indidates it is a weather report. */
/* In this case, we expect 7 byte "data extension" */
/* for the wind direction and speed. */
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " Weather Report with Object " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 01:05:48 +00:00
weather_data ( A , p - > comment , TRUE ) ;
2015-07-27 00:35:07 +00:00
}
else {
/* Regular object. */
2015-07-27 01:05:48 +00:00
data_extension_comment ( A , p - > comment ) ;
2015-07-27 00:35:07 +00:00
}
}
else /* Compressed location. */
{
2015-07-27 01:05:48 +00:00
decode_compressed_position ( A , & ( q - > cpos ) ) ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
if ( A - > g_symbol_code = = ' _ ' ) {
2015-07-27 00:35:07 +00:00
/* Symbol code indidates it is a weather report. */
/* The spec doesn't explicitly mention the combination */
/* of weather report and object with compressed */
/* position. */
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " Weather Report with Object " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 01:05:48 +00:00
weather_data ( A , q - > comment , FALSE ) ;
2015-07-27 00:35:07 +00:00
}
else {
/* Regular position report. */
2015-07-27 01:05:48 +00:00
process_comment ( A , q - > comment , - 1 ) ;
2015-07-27 00:35:07 +00:00
}
}
2015-11-08 01:57:02 +00:00
( void ) ( ts ) ;
} /* end aprs_object */
2015-07-27 00:35:07 +00:00
/*------------------------------------------------------------------
*
* Function : aprs_item
*
* Purpose : Decode " Item Report Format "
*
* Inputs : info - Pointer to Information field .
* ilen - Information field length .
*
2015-11-08 01:57:02 +00:00
* Outputs : A - > g_object_name , A - > g_lat , A - > g_lon , A - > g_symbol_table , A - > g_symbol_code , A - > g_speed_mph , A - > g_course , A - > g_altitude_ft .
2015-07-27 00:35:07 +00:00
*
* Description : An " item " is very much like an " object " except
*
* - - It doesn ' t have a time .
* - - Name is a VARIABLE length 3 to 9 instead of fixed 9.
* - - " live " indicator is ! rather than *
*
* Examples :
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:05:48 +00:00
static void aprs_item ( decode_aprs_t * A , unsigned char * info , int ilen )
2015-07-27 00:35:07 +00:00
{
struct aprs_item_s {
2017-01-01 16:49:55 +00:00
char dti ; /* ')' */
char name [ 10 ] ; /* Actually variable length 3 - 9 bytes. */
2015-09-07 23:56:20 +00:00
/* DON'T refer to the rest of this structure; */
/* the offsets will be wrong! */
2017-01-01 16:49:55 +00:00
/* We make it 10 here so we don't get subscript out of bounds */
/* warning when looking for following '!' or '_' character. */
2015-09-07 23:56:20 +00:00
2017-01-01 16:49:55 +00:00
char live_killed__ ; /* ! for live or _ for killed */
position_t pos__ ;
char comment__ [ 43 ] ; /* First 7 bytes could be data extension. */
2015-07-27 00:35:07 +00:00
} * p ;
struct aprs_compressed_item_s {
2017-01-01 16:49:55 +00:00
char dti ; /* ')' */
char name [ 10 ] ; /* Actually variable length 3 - 9 bytes. */
2015-09-07 23:56:20 +00:00
/* DON'T refer to the rest of this structure; */
/* the offsets will be wrong! */
2017-01-01 16:49:55 +00:00
char live_killed__ ; /* ! for live or _ for killed */
compressed_position_t cpos__ ;
char comment__ [ 40 ] ; /* No data extension in this case. */
2015-07-27 00:35:07 +00:00
} * q ;
int i ;
char * ppos ;
p = ( struct aprs_item_s * ) info ;
q = ( struct aprs_compressed_item_s * ) info ;
2015-11-08 01:57:02 +00:00
( void ) ( q ) ;
2015-07-27 00:35:07 +00:00
2015-09-07 23:56:20 +00:00
memset ( A - > g_name , 0 , sizeof ( A - > g_name ) ) ;
2015-07-27 00:35:07 +00:00
i = 0 ;
while ( i < 9 & & p - > name [ i ] ! = ' ! ' & & p - > name [ i ] ! = ' _ ' ) {
2015-07-27 01:05:48 +00:00
A - > g_name [ i ] = p - > name [ i ] ;
2015-07-27 00:35:07 +00:00
i + + ;
2015-07-27 01:05:48 +00:00
A - > g_name [ i ] = ' \0 ' ;
2015-07-27 00:35:07 +00:00
}
if ( p - > name [ i ] = = ' ! ' )
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " Item " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 00:35:07 +00:00
else if ( p - > name [ i ] = = ' _ ' )
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " Killed Item " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 00:35:07 +00:00
else {
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Item name too long or not followed by ! or _. \n " ) ;
}
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " Object - invalid live/killed " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 00:35:07 +00:00
}
ppos = p - > name + i + 1 ;
if ( isdigit ( * ppos ) ) /* Human-readable location. */
{
2015-07-27 01:05:48 +00:00
decode_position ( A , ( position_t * ) ppos ) ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
data_extension_comment ( A , ppos + sizeof ( position_t ) ) ;
2015-07-27 00:35:07 +00:00
}
else /* Compressed location. */
{
2015-07-27 01:05:48 +00:00
decode_compressed_position ( A , ( compressed_position_t * ) ppos ) ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
process_comment ( A , ppos + sizeof ( compressed_position_t ) , - 1 ) ;
2015-07-27 00:35:07 +00:00
}
}
/*------------------------------------------------------------------
*
* Function : aprs_station_capabilities
*
* Purpose : Decode " Station Capabilities "
*
* Inputs : info - Pointer to Information field .
* ilen - Information field length .
*
* Outputs : ? ? ?
*
* Description : Each capability is a TOKEN or TOKEN = VALUE pair .
*
*
* Example : < IGATE , MSG_CNT = 3 , LOC_CNT = 49 < CR >
*
* Bugs : Not implemented yet . Treat whole thing as comment .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:05:48 +00:00
static void aprs_station_capabilities ( decode_aprs_t * A , char * info , int ilen )
2015-07-27 00:35:07 +00:00
{
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " Station Capabilities " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 00:35:07 +00:00
2015-09-07 23:56:20 +00:00
// process_comment() not applicable here because it
// extracts information found in certain formats.
2015-07-27 00:35:07 +00:00
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_comment , info + 1 , sizeof ( A - > g_comment ) ) ;
} /* end aprs_station_capabilities */
2015-07-27 00:35:07 +00:00
/*------------------------------------------------------------------
*
* Function : aprs_status_report
*
* Purpose : Decode " Status Report "
*
* Inputs : info - Pointer to Information field .
* ilen - Information field length .
*
* Outputs : ? ? ?
*
* Description : There are 3 different formats :
*
* ( 1 ) ' > '
* 7 char - timestamp , DHM z format
* 0 - 55 char - status text
*
* ( 3 ) ' > '
* 4 or 6 char - Maidenhead Locator
* 2 char - symbol table & code
* ' ' character
* 0 - 53 char - status text
*
* ( 2 ) ' > '
* 0 - 62 char - status text
*
*
* In all cases , Beam heading and ERP can be at the
* very end by using ' ^ ' and two other characters .
*
*
* Examples from specification :
*
*
* > Net Control Center without timestamp .
* > 092345 zNet Control Center with timestamp .
* > IO91SX / G
* > IO91 / G
* > IO91SX / - My house ( Note the space at the start of the status text ) .
* > IO91SX / - ^ B7 Meteor Scatter beam heading = 110 degrees , ERP = 490 watts .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:05:48 +00:00
static void aprs_status_report ( decode_aprs_t * A , char * info , int ilen )
2015-07-27 00:35:07 +00:00
{
struct aprs_status_time_s {
char dti ; /* > */
char ztime [ 7 ] ; /* Time stamp ddhhmmz */
char comment [ 55 ] ;
} * pt ;
struct aprs_status_m4_s {
char dti ; /* > */
char mhead4 [ 4 ] ; /* 4 character Maidenhead locator. */
char sym_table_id ;
char symbol_code ;
char space ; /* Should be space after symbol code. */
char comment [ 54 ] ;
} * pm4 ;
struct aprs_status_m6_s {
char dti ; /* > */
char mhead6 [ 6 ] ; /* 6 character Maidenhead locator. */
char sym_table_id ;
char symbol_code ;
char space ; /* Should be space after symbol code. */
char comment [ 54 ] ;
} * pm6 ;
struct aprs_status_s {
char dti ; /* > */
char comment [ 62 ] ;
} * ps ;
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " Status Report " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 00:35:07 +00:00
pt = ( struct aprs_status_time_s * ) info ;
pm4 = ( struct aprs_status_m4_s * ) info ;
pm6 = ( struct aprs_status_m6_s * ) info ;
ps = ( struct aprs_status_s * ) info ;
/*
* Do we have format with time ?
*/
if ( isdigit ( pt - > ztime [ 0 ] ) & &
isdigit ( pt - > ztime [ 1 ] ) & &
isdigit ( pt - > ztime [ 2 ] ) & &
isdigit ( pt - > ztime [ 3 ] ) & &
isdigit ( pt - > ztime [ 4 ] ) & &
isdigit ( pt - > ztime [ 5 ] ) & &
pt - > ztime [ 6 ] = = ' z ' ) {
2015-09-07 23:56:20 +00:00
// process_comment() not applicable here because it
// extracts information found in certain formats.
strlcpy ( A - > g_comment , pt - > comment , sizeof ( A - > g_comment ) ) ;
2015-07-27 00:35:07 +00:00
}
/*
* Do we have format with 6 character Maidenhead locator ?
*/
2020-06-21 01:06:00 +00:00
2015-07-27 01:05:48 +00:00
else if ( get_maidenhead ( A , pm6 - > mhead6 ) = = 6 ) {
2015-07-27 00:35:07 +00:00
2015-09-07 23:56:20 +00:00
memset ( A - > g_maidenhead , 0 , sizeof ( A - > g_maidenhead ) ) ;
memcpy ( A - > g_maidenhead , pm6 - > mhead6 , sizeof ( pm6 - > mhead6 ) ) ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
A - > g_symbol_table = pm6 - > sym_table_id ;
A - > g_symbol_code = pm6 - > symbol_code ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
if ( A - > g_symbol_table ! = ' / ' & & A - > g_symbol_table ! = ' \\ '
& & ! isupper ( A - > g_symbol_table ) & & ! isdigit ( A - > g_symbol_table ) )
2015-07-27 00:35:07 +00:00
{
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid symbol table code '%c' not one of / \\ A-Z 0-9 \n " , A - > g_symbol_table ) ;
}
2015-07-27 01:05:48 +00:00
A - > g_symbol_table = ' / ' ;
2015-07-27 00:35:07 +00:00
}
if ( pm6 - > space ! = ' ' & & pm6 - > space ! = ' \0 ' ) {
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Error: Found '%c' instead of space required after symbol code. \n " , pm6 - > space ) ;
}
2015-07-27 00:35:07 +00:00
}
2015-09-07 23:56:20 +00:00
// process_comment() not applicable here because it
// extracts information found in certain formats.
strlcpy ( A - > g_comment , pm6 - > comment , sizeof ( A - > g_comment ) ) ;
2015-07-27 00:35:07 +00:00
}
/*
* Do we have format with 4 character Maidenhead locator ?
*/
2015-07-27 01:05:48 +00:00
else if ( get_maidenhead ( A , pm4 - > mhead4 ) = = 4 ) {
2015-07-27 00:35:07 +00:00
2015-09-07 23:56:20 +00:00
memset ( A - > g_maidenhead , 0 , sizeof ( A - > g_maidenhead ) ) ;
memcpy ( A - > g_maidenhead , pm4 - > mhead4 , sizeof ( pm4 - > mhead4 ) ) ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
A - > g_symbol_table = pm4 - > sym_table_id ;
A - > g_symbol_code = pm4 - > symbol_code ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
if ( A - > g_symbol_table ! = ' / ' & & A - > g_symbol_table ! = ' \\ '
& & ! isupper ( A - > g_symbol_table ) & & ! isdigit ( A - > g_symbol_table ) )
2015-07-27 00:35:07 +00:00
{
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid symbol table code '%c' not one of / \\ A-Z 0-9 \n " , A - > g_symbol_table ) ;
}
2015-07-27 01:05:48 +00:00
A - > g_symbol_table = ' / ' ;
2015-07-27 00:35:07 +00:00
}
if ( pm4 - > space ! = ' ' & & pm4 - > space ! = ' \0 ' ) {
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Error: Found '%c' instead of space required after symbol code. \n " , pm4 - > space ) ;
}
2015-07-27 00:35:07 +00:00
}
2015-09-07 23:56:20 +00:00
// process_comment() not applicable here because it
// extracts information found in certain formats.
strlcpy ( A - > g_comment , pm4 - > comment , sizeof ( A - > g_comment ) ) ;
2015-07-27 00:35:07 +00:00
}
/*
* Whole thing is status text .
*/
else {
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_comment , ps - > comment , sizeof ( A - > g_comment ) ) ;
2015-07-27 00:35:07 +00:00
}
/*
* Last 3 characters can represent beam heading and ERP .
*/
2015-07-27 01:05:48 +00:00
if ( strlen ( A - > g_comment ) > = 3 ) {
char * hp = A - > g_comment + strlen ( A - > g_comment ) - 3 ;
2015-07-27 00:35:07 +00:00
if ( * hp = = ' ^ ' ) {
char h = hp [ 1 ] ;
char p = hp [ 2 ] ;
int beam = - 1 ;
int erp = - 1 ;
if ( h > = ' 0 ' & & h < = ' 9 ' ) {
beam = ( h - ' 0 ' ) * 10 ;
}
else if ( h > = ' A ' & & h < = ' Z ' ) {
beam = ( h - ' A ' ) * 10 + 100 ;
}
if ( p > = ' 1 ' & & p < = ' K ' ) {
erp = ( p - ' 0 ' ) * ( p - ' 0 ' ) * 10 ;
}
2015-11-08 01:57:02 +00:00
// TODO (low): put result somewhere.
2015-07-27 01:05:48 +00:00
// could use A->g_directivity and need new variable for erp.
2015-07-27 00:35:07 +00:00
* hp = ' \0 ' ;
2015-11-08 01:57:02 +00:00
( void ) ( beam ) ;
( void ) ( erp ) ;
2015-07-27 00:35:07 +00:00
}
}
2015-09-07 23:56:20 +00:00
} /* end aprs_status_report */
/*------------------------------------------------------------------
*
* Function : aprs_general_query
*
* Purpose : Decode " General Query " for all stations .
*
* Inputs : info - Pointer to Information field . First character should be " ? " .
* ilen - Information field length .
* quiet - suppress error messages .
*
* Outputs : A - Decoded packet structure
* A - > g_query_type
* A - > g_query_lat ( optional )
* A - > g_query_lon ( optional )
* A - > g_query_radius ( optional )
*
* Description : Formats are :
*
* ? query ?
* ? query ? lat , long , radius
*
* ' query ' is one of APRS , IGATE , WX , . . .
* optional footprint , in degrees and miles radius , means only
* those in the specified circle should respond .
*
* Examples from specification , Chapter 15 :
*
* ? APRS ?
* ? APRS ? 34.02 , - 117.15 , 0200
* ? IGATE ?
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static void aprs_general_query ( decode_aprs_t * A , char * info , int ilen , int quiet )
{
char * q2 ;
char * p ;
char * tok ;
char stemp [ 256 ] ;
double lat , lon ;
float radius ;
strlcpy ( A - > g_msg_type , " General Query " , sizeof ( A - > g_msg_type ) ) ;
/*
* First make a copy because we will modify it while parsing it .
*/
strlcpy ( stemp , info , sizeof ( stemp ) ) ;
/*
* There should be another " ? " after the query type .
*/
q2 = strchr ( stemp + 1 , ' ? ' ) ;
if ( q2 = = NULL ) {
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " General Query must have ? after the query type. \n " ) ;
}
return ;
}
* q2 = ' \0 ' ;
strlcpy ( A - > g_query_type , stemp + 1 , sizeof ( A - > g_query_type ) ) ;
// TODO: remove debug
text_color_set ( DW_COLOR_DEBUG ) ;
dw_printf ( " DEBUG: General Query type = \" %s \" \n " , A - > g_query_type ) ;
p = q2 + 1 ;
if ( strlen ( p ) = = 0 ) {
return ;
}
/*
* Try to extract footprint .
* Spec says positive coordinate would be preceded by space
* and radius must be exactly 4 digits . We are more forgiving .
*/
tok = strsep ( & p , " , " ) ;
if ( tok ! = NULL ) {
lat = atof ( tok ) ;
tok = strsep ( & p , " , " ) ;
if ( tok ! = NULL ) {
lon = atof ( tok ) ;
tok = strsep ( & p , " , " ) ;
if ( tok ! = NULL ) {
radius = atof ( tok ) ;
if ( lat < - 90 | | lat > 90 ) {
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid latitude for General Query footprint. \n " ) ;
}
return ;
}
if ( lon < - 180 | | lon > 180 ) {
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid longitude for General Query footprint. \n " ) ;
}
return ;
}
if ( radius < = 0 | | radius > 9999 ) {
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid radius for General Query footprint. \n " ) ;
}
return ;
}
A - > g_footprint_lat = lat ;
A - > g_footprint_lon = lon ;
A - > g_footprint_radius = radius ;
}
else {
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Can't get radius for General Query footprint. \n " ) ;
}
return ;
}
}
else {
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Can't get longitude for General Query footprint. \n " ) ;
}
return ;
}
}
else {
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Can't get latitude for General Query footprint. \n " ) ;
}
return ;
}
// TODO: remove debug
text_color_set ( DW_COLOR_DEBUG ) ;
dw_printf ( " DEBUG: General Query footprint = %.6f %.6f %.2f \n " , lat , lon , radius ) ;
} /* end aprs_general_query */
/*------------------------------------------------------------------
*
* Function : aprs_directed_station_query
*
* Purpose : Decode " Directed Station Query " aimed at specific station .
* This is actually a special format of the more general " message. "
*
* Inputs : addressee - To whom it is directed .
* Redundant because it is already in A - > addressee .
*
* query - What ' s left over after " :addressee:? " in info part .
*
* quiet - suppress error messages .
*
* Outputs : A - Decoded packet structure
* A - > g_query_type
* A - > g_query_callsign ( optional )
*
* Description : The caller has already removed the : addressee : ? part so we are left
* with a query type of exactly 5 characters and optional " callsign
* of heard station . "
*
* Examples from specification , Chapter 15. Our " query " argument .
*
* : KH2Z : ? APRSD APRSD
* : KH2Z : ? APRSHVN0QBF APRSHVN0QBF
* : KH2Z : ? APRST APRST
* : KH2Z : ? PING ? PING ?
*
* " PING? " contains " ? " only to pad it out to exactly 5 characters .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static void aprs_directed_station_query ( decode_aprs_t * A , char * addressee , char * query , int quiet )
{
2015-11-08 01:57:02 +00:00
//char query_type[20]; /* Does the query type always need to be exactly 5 characters? */
2015-09-07 23:56:20 +00:00
/* If not, how would we know where the extra optional information starts? */
2015-11-08 01:57:02 +00:00
//char callsign[AX25_MAX_ADDR_LEN];
2015-09-07 23:56:20 +00:00
//if (strlen(query) < 5) ...
} /* end aprs_directed_station_query */
2015-07-27 00:35:07 +00:00
/*------------------------------------------------------------------
*
* Function : aprs_Telemetry
*
* Purpose : Decode " Telemetry "
*
* Inputs : info - Pointer to Information field .
* ilen - Information field length .
2015-07-27 01:17:23 +00:00
* quiet - suppress error messages .
2015-07-27 00:35:07 +00:00
*
2015-11-08 01:57:02 +00:00
* Outputs : A - > g_telemetry
* A - > g_comment
2015-07-27 00:35:07 +00:00
*
* Description : TBD .
*
* Examples from specification :
*
*
* TBD
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:17:23 +00:00
static void aprs_telemetry ( decode_aprs_t * A , char * info , int ilen , int quiet )
2015-07-27 00:35:07 +00:00
{
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " Telemetry " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 00:35:07 +00:00
2015-11-08 01:57:02 +00:00
telemetry_data_original ( A - > g_src , info , quiet , A - > g_telemetry , sizeof ( A - > g_telemetry ) , A - > g_comment , sizeof ( A - > g_comment ) ) ;
2015-07-27 00:35:07 +00:00
} /* end aprs_telemetry */
2020-04-19 04:50:18 +00:00
/*------------------------------------------------------------------
*
* Function : aprs_user_defined
*
* Purpose : Decode user defined data .
*
* Inputs : info - Pointer to Information field .
* ilen - Information field length .
*
* Description : APRS Protocol Specification , Chapter 18
* User IDs allocated here : http : //www.aprs.org/aprs11/expfmts.txt
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static void aprs_user_defined ( decode_aprs_t * A , char * info , int ilen )
{
2022-02-25 22:29:07 +00:00
if ( strncmp ( info , " {tt " , 3 ) = = 0 | | // Historical.
strncmp ( info , " {DT " , 3 ) = = 0 ) { // Official after registering {D*
2020-04-19 04:50:18 +00:00
aprs_raw_touch_tone ( A , info , ilen ) ;
}
2022-02-25 22:29:07 +00:00
else if ( strncmp ( info , " {mc " , 3 ) = = 0 | | // Historical.
strncmp ( info , " {DM " , 3 ) = = 0 ) { // Official after registering {D*
2020-04-19 04:50:18 +00:00
aprs_morse_code ( A , info , ilen ) ;
}
else if ( info [ 0 ] = = ' { ' & & info [ 1 ] = = USER_DEF_USER_ID & & info [ 2 ] = = USER_DEF_TYPE_AIS ) {
double lat , lon ;
float knots , course ;
2020-04-25 11:59:06 +00:00
float alt_meters ;
2020-04-19 04:50:18 +00:00
2020-04-25 11:59:06 +00:00
ais_parse ( info + 3 , 0 , A - > g_msg_type , sizeof ( A - > g_msg_type ) , A - > g_name , sizeof ( A - > g_name ) ,
& lat , & lon , & knots , & course , & alt_meters , & ( A - > g_symbol_table ) , & ( A - > g_symbol_code ) ,
A - > g_comment , sizeof ( A - > g_comment ) ) ;
2020-04-19 04:50:18 +00:00
A - > g_lat = lat ;
A - > g_lon = lon ;
A - > g_speed_mph = DW_KNOTS_TO_MPH ( knots ) ;
A - > g_course = course ;
2020-04-25 11:59:06 +00:00
A - > g_altitude_ft = DW_METERS_TO_FEET ( alt_meters ) ;
2020-04-19 04:50:18 +00:00
strcpy ( A - > g_mfr , " " ) ;
}
else if ( strncmp ( info , " {{ " , 2 ) = = 0 ) {
snprintf ( A - > g_msg_type , sizeof ( A - > g_msg_type ) , " User-Defined Experimental " ) ;
}
else {
snprintf ( A - > g_msg_type , sizeof ( A - > g_msg_type ) , " User-Defined Data " ) ;
}
} /* end aprs_user_defined */
2015-07-27 00:35:07 +00:00
/*------------------------------------------------------------------
*
* Function : aprs_raw_touch_tone
*
2015-07-27 01:05:48 +00:00
* Purpose : Decode raw touch tone datA - >
2015-07-27 00:35:07 +00:00
*
* Inputs : info - Pointer to Information field .
* ilen - Information field length .
*
* Description : Touch tone data is converted to a packet format
* so it can be conveyed to an application for processing .
*
* This is not part of the APRS standard .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:05:48 +00:00
static void aprs_raw_touch_tone ( decode_aprs_t * A , char * info , int ilen )
2015-07-27 00:35:07 +00:00
{
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " Raw Touch Tone Data " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 00:35:07 +00:00
/* Just copy the info field without the message type. */
if ( * info = = ' { ' )
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_comment , info + 3 , sizeof ( A - > g_comment ) ) ;
2015-07-27 00:35:07 +00:00
else
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_comment , info + 1 , sizeof ( A - > g_comment ) ) ;
2015-07-27 00:35:07 +00:00
} /* end aprs_raw_touch_tone */
/*------------------------------------------------------------------
*
* Function : aprs_morse_code
*
* Purpose : Convey message in packet format to be transmitted as
* Morse Code .
*
* Inputs : info - Pointer to Information field .
* ilen - Information field length .
*
* Description : This is not part of the APRS standard .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:05:48 +00:00
static void aprs_morse_code ( decode_aprs_t * A , char * info , int ilen )
2015-07-27 00:35:07 +00:00
{
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " Morse Code Data " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 00:35:07 +00:00
/* Just copy the info field without the message type. */
if ( * info = = ' { ' )
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_comment , info + 3 , sizeof ( A - > g_comment ) ) ;
2015-07-27 00:35:07 +00:00
else
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_comment , info + 1 , sizeof ( A - > g_comment ) ) ;
2015-07-27 00:35:07 +00:00
} /* end aprs_morse_code */
/*------------------------------------------------------------------
*
* Function : aprs_ll_pos_time
*
* Purpose : Decode weather report without a position .
*
* Inputs : info - Pointer to Information field .
* ilen - Information field length .
*
2015-07-27 01:05:48 +00:00
* Outputs : A - > g_symbol_table , A - > g_symbol_code .
2015-07-27 00:35:07 +00:00
*
* Description : Type identifier ' _ ' is a weather report without a position .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:05:48 +00:00
static void aprs_positionless_weather_report ( decode_aprs_t * A , unsigned char * info , int ilen )
2015-07-27 00:35:07 +00:00
{
struct aprs_positionless_weather_s {
char dti ; /* _ */
char time_stamp [ 8 ] ; /* MDHM format */
char comment [ 99 ] ;
} * p ;
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " Positionless Weather Report " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 00:35:07 +00:00
2015-11-08 01:57:02 +00:00
//time_t ts = 0;
2015-07-27 00:35:07 +00:00
p = ( struct aprs_positionless_weather_s * ) info ;
2015-07-27 01:05:48 +00:00
// not yet implemented for 8 character format // ts = get_timestamp (A, p->time_stamp);
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
weather_data ( A , p - > comment , FALSE ) ;
2015-07-27 00:35:07 +00:00
}
/*------------------------------------------------------------------
*
* Function : weather_data
*
* Purpose : Decode weather data in position or object report .
*
* Inputs : info - Pointer to first byte after location
* and symbol code .
*
* wind_prefix - Expecting leading wind info
* for human - readable location .
* ( Currently ignored . We are very
* forgiving in what is accepted . )
* TODO : call this context instead and have 3 enumerated values .
*
2015-07-27 01:05:48 +00:00
* Global In : A - > g_course - Wind info for compressed location .
2015-11-08 01:57:02 +00:00
* A - > g_speed_mph
2015-07-27 00:35:07 +00:00
*
2015-07-27 01:05:48 +00:00
* Outputs : A - > g_weather
2015-07-27 00:35:07 +00:00
*
* Description : Extract weather details and format into a comment .
*
* For human - readable locations , we expect wind direction
* and speed in a format like this : 999 / 999.
* For compressed location , this has already been
2015-11-08 01:57:02 +00:00
* processed and put in A - > g_course and A - > g_speed_mph .
2015-07-27 00:35:07 +00:00
* Otherwise , for positionless weather data , the
* wind is in the form c999s999 .
*
* References : APRS Weather specification comments .
* http : //aprs.org/aprs11/spec-wx.txt
*
* Weather updates to the spec .
* http : //aprs.org/aprs12/weather-new.txt
*
* Examples :
*
* _10090556c220s004g005t077r000p000P000h50b09900wRSW
* ! 4903.50 N / 07201.75 W_220 / 004 g005t077r000p000P000h50b09900wRSW
* ! 4903.50 N / 07201.75 W_220 / 004 g005t077r000p000P000h50b . . . . . wRSW
* @ 092345 z4903 .50 N / 07201.75 W_220 / 004 g005t - 07 r000p000P000h50b09900wRSW
* = / 5L ! ! < * e7_7P [ g005t077r000p000P000h50b09900wRSW
* @ 092345 z / 5L ! ! < * e7_7P [ g005t077r000p000P000h50b09900wRSW
* ; BRENDA * 092345 z4903 .50 N / 07201.75 W_220 / 004 g005b0990
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static int getwdata ( char * * wpp , char ch , int dlen , float * val )
{
2015-09-07 23:56:20 +00:00
char stemp [ 8 ] ; // larger than maximum dlen.
2015-07-27 00:35:07 +00:00
int i ;
//dw_printf("debug: getwdata (wp=%p, ch=%c, dlen=%d)\n", *wpp, ch, dlen);
* val = G_UNKNOWN ;
assert ( dlen > = 2 & & dlen < = 6 ) ;
if ( * * wpp ! = ch ) {
/* Not specified element identifier. */
return ( 0 ) ;
}
if ( strncmp ( ( * wpp ) + 1 , " ...... " , dlen ) = = 0 | | strncmp ( ( * wpp ) + 1 , " " , dlen ) = = 0 ) {
/* Field present, unknown value */
* wpp + = 1 + dlen ;
return ( 1 ) ;
}
/* Data field can contain digits, decimal point, leading negative. */
for ( i = 1 ; i < = dlen ; i + + ) {
if ( ! isdigit ( ( * wpp ) [ i ] ) & & ( * wpp ) [ i ] ! = ' . ' & & ( * wpp ) [ i ] ! = ' - ' ) {
return ( 0 ) ;
}
}
2015-09-07 23:56:20 +00:00
memset ( stemp , 0 , sizeof ( stemp ) ) ;
memcpy ( stemp , ( * wpp ) + 1 , dlen ) ;
2015-07-27 00:35:07 +00:00
* val = atof ( stemp ) ;
//dw_printf("debug: getwdata returning %f\n", *val);
* wpp + = 1 + dlen ;
return ( 1 ) ;
}
2015-07-27 01:05:48 +00:00
static void weather_data ( decode_aprs_t * A , char * wdata , int wind_prefix )
2015-07-27 00:35:07 +00:00
{
int n ;
float fval ;
char * wp = wdata ;
int keep_going ;
if ( wp [ 3 ] = = ' / ' )
{
if ( sscanf ( wp , " %3d " , & n ) )
{
// Data Extension format.
// Fine point: Officially, should be values of 001-360.
// "000" or "..." or " " means unknown.
// In practice we see do see "000" here.
2015-07-27 01:05:48 +00:00
A - > g_course = n ;
2015-07-27 00:35:07 +00:00
}
if ( sscanf ( wp + 4 , " %3d " , & n ) )
{
2015-11-08 01:57:02 +00:00
A - > g_speed_mph = DW_KNOTS_TO_MPH ( n ) ; /* yes, in knots */
2015-07-27 00:35:07 +00:00
}
wp + = 7 ;
}
2015-11-08 01:57:02 +00:00
else if ( A - > g_speed_mph = = G_UNKNOWN ) {
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
if ( ! getwdata ( & wp , ' c ' , 3 , & A - > g_course ) ) {
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Didn't find wind direction in form c999. \n " ) ;
}
2015-07-27 00:35:07 +00:00
}
2015-11-08 01:57:02 +00:00
if ( ! getwdata ( & wp , ' s ' , 3 , & A - > g_speed_mph ) ) { /* MPH here */
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Didn't find wind speed in form s999. \n " ) ;
}
2015-07-27 00:35:07 +00:00
}
}
// At this point, we should have the wind direction and speed
// from one of three methods.
2015-11-08 01:57:02 +00:00
if ( A - > g_speed_mph ! = G_UNKNOWN ) {
2015-07-27 00:35:07 +00:00
2015-11-08 01:57:02 +00:00
snprintf ( A - > g_weather , sizeof ( A - > g_weather ) , " wind %.1f mph " , A - > g_speed_mph ) ;
2015-07-27 01:05:48 +00:00
if ( A - > g_course ! = G_UNKNOWN ) {
2015-09-07 23:56:20 +00:00
char ctemp [ 40 ] ;
snprintf ( ctemp , sizeof ( ctemp ) , " , direction %.0f " , A - > g_course ) ;
strlcat ( A - > g_weather , ctemp , sizeof ( A - > g_weather ) ) ;
2015-07-27 00:35:07 +00:00
}
}
/* We don't want this to show up on the location line. */
2015-11-08 01:57:02 +00:00
A - > g_speed_mph = G_UNKNOWN ;
2015-07-27 01:05:48 +00:00
A - > g_course = G_UNKNOWN ;
2015-07-27 00:35:07 +00:00
/*
* After the mandatory wind direction and speed ( in 1 of 3 formats ) , the
* next two must be in fixed positions :
* - gust ( peak in mph last 5 minutes )
* - temperature , degrees F , can be negative e . g . - 01
*/
if ( getwdata ( & wp , ' g ' , 3 , & fval ) ) {
if ( fval ! = G_UNKNOWN ) {
2015-09-07 23:56:20 +00:00
char ctemp [ 40 ] ;
snprintf ( ctemp , sizeof ( ctemp ) , " , gust %.0f " , fval ) ;
strlcat ( A - > g_weather , ctemp , sizeof ( A - > g_weather ) ) ;
2015-07-27 00:35:07 +00:00
}
}
else {
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Didn't find wind gust in form g999. \n " ) ;
}
2015-07-27 00:35:07 +00:00
}
if ( getwdata ( & wp , ' t ' , 3 , & fval ) ) {
if ( fval ! = G_UNKNOWN ) {
2015-09-07 23:56:20 +00:00
char ctemp [ 40 ] ;
snprintf ( ctemp , sizeof ( ctemp ) , " , temperature %.0f " , fval ) ;
strlcat ( A - > g_weather , ctemp , sizeof ( A - > g_weather ) ) ;
2015-07-27 00:35:07 +00:00
}
}
else {
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Didn't find temperature in form t999. \n " ) ;
}
2015-07-27 00:35:07 +00:00
}
/*
* Now pick out other optional fields in any order .
*/
keep_going = 1 ;
while ( keep_going ) {
if ( getwdata ( & wp , ' r ' , 3 , & fval ) ) {
/* r = rainfall, 1/100 inch, last hour */
if ( fval ! = G_UNKNOWN ) {
2015-09-07 23:56:20 +00:00
char ctemp [ 40 ] ;
snprintf ( ctemp , sizeof ( ctemp ) , " , rain %.2f in last hour " , fval / 100. ) ;
strlcat ( A - > g_weather , ctemp , sizeof ( A - > g_weather ) ) ;
2015-07-27 00:35:07 +00:00
}
}
else if ( getwdata ( & wp , ' p ' , 3 , & fval ) ) {
/* p = rainfall, 1/100 inch, last 24 hours */
if ( fval ! = G_UNKNOWN ) {
2015-09-07 23:56:20 +00:00
char ctemp [ 40 ] ;
snprintf ( ctemp , sizeof ( ctemp ) , " , rain %.2f in last 24 hours " , fval / 100. ) ;
strlcat ( A - > g_weather , ctemp , sizeof ( A - > g_weather ) ) ;
2015-07-27 00:35:07 +00:00
}
}
else if ( getwdata ( & wp , ' P ' , 3 , & fval ) ) {
/* P = rainfall, 1/100 inch, since midnight */
if ( fval ! = G_UNKNOWN ) {
2015-09-07 23:56:20 +00:00
char ctemp [ 40 ] ;
snprintf ( ctemp , sizeof ( ctemp ) , " , rain %.2f since midnight " , fval / 100. ) ;
strlcat ( A - > g_weather , ctemp , sizeof ( A - > g_weather ) ) ;
2015-07-27 00:35:07 +00:00
}
}
else if ( getwdata ( & wp , ' h ' , 2 , & fval ) ) {
/* h = humidity %, 00 means 100% */
if ( fval ! = G_UNKNOWN ) {
char ctemp [ 30 ] ;
if ( fval = = 0 ) fval = 100 ;
2015-09-07 23:56:20 +00:00
snprintf ( ctemp , sizeof ( ctemp ) , " , humidity %.0f " , fval ) ;
strlcat ( A - > g_weather , ctemp , sizeof ( A - > g_weather ) ) ;
2015-07-27 00:35:07 +00:00
}
}
else if ( getwdata ( & wp , ' b ' , 5 , & fval ) ) {
/* b = barometric presure (tenths millibars / tenths of hPascal) */
/* Here, display as inches of mercury. */
if ( fval ! = G_UNKNOWN ) {
2015-09-07 23:56:20 +00:00
char ctemp [ 40 ] ;
2015-07-27 01:05:48 +00:00
fval = DW_MBAR_TO_INHG ( fval * 0.1 ) ;
2015-09-07 23:56:20 +00:00
snprintf ( ctemp , sizeof ( ctemp ) , " , barometer %.2f " , fval ) ;
strlcat ( A - > g_weather , ctemp , sizeof ( A - > g_weather ) ) ;
2015-07-27 00:35:07 +00:00
}
}
else if ( getwdata ( & wp , ' L ' , 3 , & fval ) ) {
/* L = Luminosity, watts/ sq meter, 000-999 */
if ( fval ! = G_UNKNOWN ) {
2015-09-07 23:56:20 +00:00
char ctemp [ 40 ] ;
snprintf ( ctemp , sizeof ( ctemp ) , " , %.0f watts/m^2 " , fval ) ;
strlcat ( A - > g_weather , ctemp , sizeof ( A - > g_weather ) ) ;
2015-07-27 00:35:07 +00:00
}
}
else if ( getwdata ( & wp , ' l ' , 3 , & fval ) ) {
/* l = Luminosity, watts/ sq meter, 1000-1999 */
if ( fval ! = G_UNKNOWN ) {
2015-09-07 23:56:20 +00:00
char ctemp [ 40 ] ;
snprintf ( ctemp , sizeof ( ctemp ) , " , %.0f watts/m^2 " , fval + 1000 ) ;
strlcat ( A - > g_weather , ctemp , sizeof ( A - > g_weather ) ) ;
2015-07-27 00:35:07 +00:00
}
}
else if ( getwdata ( & wp , ' s ' , 3 , & fval ) ) {
/* s = Snowfall in last 24 hours, inches */
/* Data can have decimal point so we don't have to worry about scaling. */
/* 's' is also used by wind speed but that must be in a fixed */
/* position in the message so there is no confusion. */
if ( fval ! = G_UNKNOWN ) {
2015-09-07 23:56:20 +00:00
char ctemp [ 40 ] ;
snprintf ( ctemp , sizeof ( ctemp ) , " , %.1f snow in 24 hours " , fval ) ;
strlcat ( A - > g_weather , ctemp , sizeof ( A - > g_weather ) ) ;
2015-07-27 00:35:07 +00:00
}
}
else if ( getwdata ( & wp , ' s ' , 3 , & fval ) ) {
/* # = Raw rain counter */
if ( fval ! = G_UNKNOWN ) {
2015-09-07 23:56:20 +00:00
char ctemp [ 40 ] ;
snprintf ( ctemp , sizeof ( ctemp ) , " , raw rain counter %.f " , fval ) ;
strlcat ( A - > g_weather , ctemp , sizeof ( A - > g_weather ) ) ;
2015-07-27 00:35:07 +00:00
}
}
else if ( getwdata ( & wp , ' X ' , 3 , & fval ) ) {
/* X = Nuclear Radiation. */
/* Encoded as two significant digits and order of magnitude */
/* like resistor color code. */
// TODO: decode this properly
if ( fval ! = G_UNKNOWN ) {
2015-09-07 23:56:20 +00:00
char ctemp [ 40 ] ;
snprintf ( ctemp , sizeof ( ctemp ) , " , nuclear Radiation %.f " , fval ) ;
strlcat ( A - > g_weather , ctemp , sizeof ( A - > g_weather ) ) ;
2015-07-27 00:35:07 +00:00
}
}
// TODO: add new flood level, battery voltage, etc.
else {
keep_going = 0 ;
}
}
/*
* We should be left over with :
* - one character for software .
* - two to four characters for weather station type .
* Examples : tU2k , wRSW
*
* But few people follow the protocol spec here . Instead more often we see things like :
* sunny / WX
* / { UIV32N }
*/
2015-09-07 23:56:20 +00:00
strlcat ( A - > g_weather , " , \" " , sizeof ( A - > g_weather ) ) ;
strlcat ( A - > g_weather , wp , sizeof ( A - > g_weather ) ) ;
2015-07-27 00:35:07 +00:00
/*
* Drop any CR / LF character at the end .
*/
2015-07-27 01:05:48 +00:00
n = strlen ( A - > g_weather ) ;
if ( n > = 1 & & A - > g_weather [ n - 1 ] = = ' \n ' ) {
A - > g_weather [ n - 1 ] = ' \0 ' ;
2015-07-27 00:35:07 +00:00
}
2015-07-27 01:05:48 +00:00
n = strlen ( A - > g_weather ) ;
if ( n > = 1 & & A - > g_weather [ n - 1 ] = = ' \r ' ) {
A - > g_weather [ n - 1 ] = ' \0 ' ;
2015-07-27 00:35:07 +00:00
}
2015-09-07 23:56:20 +00:00
strlcat ( A - > g_weather , " \" " , sizeof ( A - > g_weather ) ) ;
2015-07-27 00:35:07 +00:00
return ;
2015-09-07 23:56:20 +00:00
} /* end weather_data */
2015-07-27 00:35:07 +00:00
/*------------------------------------------------------------------
*
* Function : aprs_ultimeter
*
* Purpose : Decode Peet Brothers ULTIMETER Weather Station Info .
*
* Inputs : info - Pointer to Information field .
* ilen - Information field length .
*
2015-07-27 01:05:48 +00:00
* Outputs : A - > g_weather
2015-07-27 00:35:07 +00:00
*
* Description : http : //www.peetbros.com/shop/custom.aspx?recid=7
*
* There are two different data formats in use .
* One begins with $ ULTW and is called " Packet Mode. " Example :
*
* $ ULTW009400DC00E21B8027730008890200010309001E02100000004C < CR > < LF >
*
* The other begins with ! ! and is called " logging mode. " Example :
*
* ! ! 000000 A600B50000 - - - - - - - - - - - - - - - - 001 C01D500000017 < CR > < LF >
*
*
* Bugs : Implementation is incomplete .
* The example shown in the APRS protocol spec has a couple " ---- "
* fields in the $ ULTW message . This should be rewritten to handle
* each field separately to deal with missing pieces .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:05:48 +00:00
static void aprs_ultimeter ( decode_aprs_t * A , char * info , int ilen )
2015-07-27 00:35:07 +00:00
{
// Header = $ULTW
// Data Fields
short h_windpeak ; // 1. Wind Speed Peak over last 5 min. (0.1 kph)
short h_wdir ; // 2. Wind Direction of Wind Speed Peak (0-255)
short h_otemp ; // 3. Current Outdoor Temp (0.1 deg F)
short h_totrain ; // 4. Rain Long Term Total (0.01 in.)
short h_baro ; // 5. Current Barometer (0.1 mbar)
short h_barodelta ; // 6. Barometer Delta Value(0.1 mbar)
short h_barocorrl ; // 7. Barometer Corr. Factor(LSW)
short h_barocorrm ; // 8. Barometer Corr. Factor(MSW)
short h_ohumid ; // 9. Current Outdoor Humidity (0.1%)
short h_date ; // 10. Date (day of year)
short h_time ; // 11. Time (minute of day)
short h_raintoday ; // 12. Today's Rain Total (0.01 inches)*
short h_windave ; // 13. 5 Minute Wind Speed Average (0.1kph)*
// Carriage Return & Line Feed
// *Some instruments may not include field 13, some may
// not include 12 or 13.
// Total size: 44, 48 or 52 characters (hex digits) +
// header, carriage return and line feed.
int n ;
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_msg_type , " Ultimeter " , sizeof ( A - > g_msg_type ) ) ;
2015-07-27 00:35:07 +00:00
if ( * info = = ' $ ' )
{
n = sscanf ( info + 5 , " %4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx " ,
& h_windpeak ,
& h_wdir ,
& h_otemp ,
& h_totrain ,
& h_baro ,
& h_barodelta ,
& h_barocorrl ,
& h_barocorrm ,
& h_ohumid ,
& h_date ,
& h_time ,
& h_raintoday , // not on some models.
& h_windave ) ; // not on some models.
if ( n > = 11 & & n < = 13 ) {
float windpeak , wdir , otemp , baro , ohumid ;
2015-07-27 01:05:48 +00:00
windpeak = DW_KM_TO_MILES ( h_windpeak * 0.1 ) ;
2015-07-27 00:35:07 +00:00
wdir = ( h_wdir & 0xff ) * 360. / 256. ;
otemp = h_otemp * 0.1 ;
2015-07-27 01:05:48 +00:00
baro = DW_MBAR_TO_INHG ( h_baro * 0.1 ) ;
2015-07-27 00:35:07 +00:00
ohumid = h_ohumid * 0.1 ;
2015-09-07 23:56:20 +00:00
snprintf ( A - > g_weather , sizeof ( A - > g_weather ) , " wind %.1f mph, direction %.0f, temperature %.1f, barometer %.2f, humidity %.0f " ,
2015-07-27 00:35:07 +00:00
windpeak , wdir , otemp , baro , ohumid ) ;
}
}
// Header = !!
// Data Fields
// 1. Wind Speed (0.1 kph)
// 2. Wind Direction (0-255)
// 3. Outdoor Temp (0.1 deg F)
// 4. Rain* Long Term Total (0.01 inches)
// 5. Barometer (0.1 mbar) [ can be ---- ]
// 6. Indoor Temp (0.1 deg F) [ can be ---- ]
// 7. Outdoor Humidity (0.1%) [ can be ---- ]
// 8. Indoor Humidity (0.1%) [ can be ---- ]
// 9. Date (day of year)
// 10. Time (minute of day)
// 11. Today's Rain Total (0.01 inches)*
// 12. 1 Minute Wind Speed Average (0.1kph)*
// Carriage Return & Line Feed
//
// *Some instruments may not include field 12, some may not include 11 or 12.
// Total size: 40, 44 or 48 characters (hex digits) + header, carriage return and line feed
if ( * info = = ' ! ' )
{
n = sscanf ( info + 2 , " %4hx%4hx%4hx%4hx " ,
& h_windpeak ,
& h_wdir ,
& h_otemp ,
& h_totrain ) ;
if ( n = = 4 ) {
float windpeak , wdir , otemp ;
2015-07-27 01:05:48 +00:00
windpeak = DW_KM_TO_MILES ( h_windpeak * 0.1 ) ;
2015-07-27 00:35:07 +00:00
wdir = ( h_wdir & 0xff ) * 360. / 256. ;
otemp = h_otemp * 0.1 ;
2015-09-07 23:56:20 +00:00
snprintf ( A - > g_weather , sizeof ( A - > g_weather ) , " wind %.1f mph, direction %.0f, temperature %.1f \n " ,
2015-07-27 00:35:07 +00:00
windpeak , wdir , otemp ) ;
}
}
} /* end aprs_ultimeter */
/*------------------------------------------------------------------
*
* Function : decode_position
*
* Purpose : Decode the position & symbol information common to many message formats .
*
* Inputs : ppos - Pointer to position & symbol fields .
*
2015-07-27 01:05:48 +00:00
* Returns : A - > g_lat
* A - > g_lon
* A - > g_symbol_table
* A - > g_symbol_code
2015-07-27 00:35:07 +00:00
*
* Description : This provides resolution of about 60 feet .
* This can be improved by using ! DAO ! in the comment .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:05:48 +00:00
static void decode_position ( decode_aprs_t * A , position_t * ppos )
2015-07-27 00:35:07 +00:00
{
2015-07-27 01:17:23 +00:00
A - > g_lat = get_latitude_8 ( ppos - > lat , A - > g_quiet ) ;
A - > g_lon = get_longitude_9 ( ppos - > lon , A - > g_quiet ) ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
A - > g_symbol_table = ppos - > sym_table_id ;
A - > g_symbol_code = ppos - > symbol_code ;
2015-07-27 00:35:07 +00:00
}
/*------------------------------------------------------------------
*
* Function : decode_compressed_position
*
* Purpose : Decode the compressed position & symbol information common to many message formats .
*
* Inputs : ppos - Pointer to compressed position & symbol fields .
*
2015-07-27 01:05:48 +00:00
* Returns : A - > g_lat
* A - > g_lon
* A - > g_symbol_table
* A - > g_symbol_code
2015-07-27 00:35:07 +00:00
*
* One of the following :
2015-07-27 01:05:48 +00:00
* A - > g_course & A - > g_speeed
2015-11-08 01:57:02 +00:00
* A - > g_altitude_ft
2015-07-27 01:05:48 +00:00
* A - > g_range
2015-07-27 00:35:07 +00:00
*
* Description : The compressed position provides resolution of around ? ? ?
* This also includes course / speed or altitude .
*
* It contains 13 bytes of the format :
*
* symbol table / , \ , or overlay A - Z , a - j is mapped into 0 - 9
*
* yyyy Latitude , base 91.
*
* xxxx Longitude , base 91.
*
* symbol code
*
* cs Course / Speed or altitude .
*
* t Various " type " info .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:05:48 +00:00
static void decode_compressed_position ( decode_aprs_t * A , compressed_position_t * pcpos )
2015-07-27 00:35:07 +00:00
{
2015-07-27 01:05:48 +00:00
if ( isdigit91 ( pcpos - > y [ 0 ] ) & & isdigit91 ( pcpos - > y [ 1 ] ) & & isdigit91 ( pcpos - > y [ 2 ] ) & & isdigit91 ( pcpos - > y [ 3 ] ) )
2015-07-27 00:35:07 +00:00
{
2015-07-27 01:05:48 +00:00
A - > g_lat = 90 - ( ( pcpos - > y [ 0 ] - 33 ) * 91 * 91 * 91 + ( pcpos - > y [ 1 ] - 33 ) * 91 * 91 + ( pcpos - > y [ 2 ] - 33 ) * 91 + ( pcpos - > y [ 3 ] - 33 ) ) / 380926.0 ;
2015-07-27 00:35:07 +00:00
}
else
{
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid character in compressed latitude. Must be in range of '!' to '{'. \n " ) ;
}
2015-07-27 01:05:48 +00:00
A - > g_lat = G_UNKNOWN ;
2015-07-27 00:35:07 +00:00
}
2015-07-27 01:05:48 +00:00
if ( isdigit91 ( pcpos - > x [ 0 ] ) & & isdigit91 ( pcpos - > x [ 1 ] ) & & isdigit91 ( pcpos - > x [ 2 ] ) & & isdigit91 ( pcpos - > x [ 3 ] ) )
2015-07-27 00:35:07 +00:00
{
2015-07-27 01:05:48 +00:00
A - > g_lon = - 180 + ( ( pcpos - > x [ 0 ] - 33 ) * 91 * 91 * 91 + ( pcpos - > x [ 1 ] - 33 ) * 91 * 91 + ( pcpos - > x [ 2 ] - 33 ) * 91 + ( pcpos - > x [ 3 ] - 33 ) ) / 190463.0 ;
2015-07-27 00:35:07 +00:00
}
else
{
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid character in compressed longitude. Must be in range of '!' to '{'. \n " ) ;
}
2015-07-27 01:05:48 +00:00
A - > g_lon = G_UNKNOWN ;
2015-07-27 00:35:07 +00:00
}
if ( pcpos - > sym_table_id = = ' / ' | | pcpos - > sym_table_id = = ' \\ ' | | isupper ( ( int ) ( pcpos - > sym_table_id ) ) ) {
/* primary or alternate or alternate with upper case overlay. */
2015-07-27 01:05:48 +00:00
A - > g_symbol_table = pcpos - > sym_table_id ;
2015-07-27 00:35:07 +00:00
}
else if ( pcpos - > sym_table_id > = ' a ' & & pcpos - > sym_table_id < = ' j ' ) {
/* Lower case a-j are used to represent overlay characters 0-9 */
/* because a digit here would mean normal (non-compressed) location. */
2015-07-27 01:05:48 +00:00
A - > g_symbol_table = pcpos - > sym_table_id - ' a ' + ' 0 ' ;
2015-07-27 00:35:07 +00:00
}
else {
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid symbol table id for compressed position. \n " ) ;
}
2015-07-27 01:05:48 +00:00
A - > g_symbol_table = ' / ' ;
2015-07-27 00:35:07 +00:00
}
2015-07-27 01:05:48 +00:00
A - > g_symbol_code = pcpos - > symbol_code ;
2015-07-27 00:35:07 +00:00
if ( pcpos - > c = = ' ' ) {
; /* ignore other two bytes */
}
else if ( ( ( pcpos - > t - 33 ) & 0x18 ) = = 0x10 ) {
2015-11-08 01:57:02 +00:00
A - > g_altitude_ft = pow ( 1.002 , ( pcpos - > c - 33 ) * 91 + pcpos - > s - 33 ) ;
2015-07-27 00:35:07 +00:00
}
else if ( pcpos - > c = = ' { ' )
{
2015-07-27 01:05:48 +00:00
A - > g_range = 2.0 * pow ( 1.08 , pcpos - > s - 33 ) ;
2015-07-27 00:35:07 +00:00
}
else if ( pcpos - > c > = ' ! ' & & pcpos - > c < = ' z ' )
{
/* For a weather station, this is wind information. */
2015-07-27 01:05:48 +00:00
A - > g_course = ( pcpos - > c - 33 ) * 4 ;
2015-11-08 01:57:02 +00:00
A - > g_speed_mph = DW_KNOTS_TO_MPH ( pow ( 1.08 , pcpos - > s - 33 ) - 1.0 ) ;
2015-07-27 00:35:07 +00:00
}
}
/*------------------------------------------------------------------
*
* Function : get_latitude_8
*
* Purpose : Convert 8 byte latitude encoding to degrees .
*
* Inputs : plat - Pointer to first byte .
*
* Returns : Double precision value in degrees . Negative for South .
*
* Description : Latitude is expressed as a fixed 8 - character field , in degrees
* and decimal minutes ( to two decimal places ) , followed by the
* letter N for north or S for south .
* The protocol spec specifies upper case but I ' ve seen lower
* case so this will accept either one .
* Latitude degrees are in the range 00 to 90. Latitude minutes
* are expressed as whole minutes and hundredths of a minute ,
* separated by a decimal point .
* For example :
* 4903.50 N is 49 degrees 3 minutes 30 seconds north .
* In generic format examples , the latitude is shown as the 8 - character
* string ddmm . hhN ( i . e . degrees , minutes and hundredths of a minute north ) .
*
* Bug : We don ' t properly deal with position ambiguity where trailing
* digits might be replaced by spaces . We simply treat them like zeros .
*
* Errors : Return G_UNKNOWN for any type of error .
*
* Should probably print an error message .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:17:23 +00:00
double get_latitude_8 ( char * p , int quiet )
2015-07-27 00:35:07 +00:00
{
struct lat_s {
unsigned char deg [ 2 ] ;
unsigned char minn [ 2 ] ;
char dot ;
unsigned char hmin [ 2 ] ;
char ns ;
} * plat ;
double result = 0 ;
plat = ( void * ) p ;
if ( isdigit ( plat - > deg [ 0 ] ) )
result + = ( ( plat - > deg [ 0 ] ) - ' 0 ' ) * 10 ;
else {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid character in latitude. Found '%c' when expecting 0-9 for tens of degrees. \n " , plat - > deg [ 0 ] ) ;
}
2015-07-27 00:35:07 +00:00
return ( G_UNKNOWN ) ;
}
if ( isdigit ( plat - > deg [ 1 ] ) )
result + = ( ( plat - > deg [ 1 ] ) - ' 0 ' ) * 1 ;
else {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid character in latitude. Found '%c' when expecting 0-9 for degrees. \n " , plat - > deg [ 1 ] ) ;
}
2015-07-27 00:35:07 +00:00
return ( G_UNKNOWN ) ;
}
2016-12-17 01:12:38 +00:00
if ( plat - > minn [ 0 ] > = ' 0 ' & & plat - > minn [ 0 ] < = ' 5 ' )
2015-07-27 00:35:07 +00:00
result + = ( ( plat - > minn [ 0 ] ) - ' 0 ' ) * ( 10. / 60. ) ;
else if ( plat - > minn [ 0 ] = = ' ' )
;
else {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid character in latitude. Found '%c' when expecting 0-5 for tens of minutes. \n " , plat - > minn [ 0 ] ) ;
}
2015-07-27 00:35:07 +00:00
return ( G_UNKNOWN ) ;
}
if ( isdigit ( plat - > minn [ 1 ] ) )
result + = ( ( plat - > minn [ 1 ] ) - ' 0 ' ) * ( 1. / 60. ) ;
else if ( plat - > minn [ 1 ] = = ' ' )
;
else {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid character in latitude. Found '%c' when expecting 0-9 for minutes. \n " , plat - > minn [ 1 ] ) ;
}
2015-07-27 00:35:07 +00:00
return ( G_UNKNOWN ) ;
}
if ( plat - > dot ! = ' . ' ) {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Unexpected character \" %c \" found where period expected in latitude. \n " , plat - > dot ) ;
}
2015-07-27 00:35:07 +00:00
return ( G_UNKNOWN ) ;
}
if ( isdigit ( plat - > hmin [ 0 ] ) )
result + = ( ( plat - > hmin [ 0 ] ) - ' 0 ' ) * ( 0.1 / 60. ) ;
else if ( plat - > hmin [ 0 ] = = ' ' )
;
else {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid character in latitude. Found '%c' when expecting 0-9 for tenths of minutes. \n " , plat - > hmin [ 0 ] ) ;
}
2015-07-27 00:35:07 +00:00
return ( G_UNKNOWN ) ;
}
if ( isdigit ( plat - > hmin [ 1 ] ) )
result + = ( ( plat - > hmin [ 1 ] ) - ' 0 ' ) * ( 0.01 / 60. ) ;
else if ( plat - > hmin [ 1 ] = = ' ' )
;
else {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid character in latitude. Found '%c' when expecting 0-9 for hundredths of minutes. \n " , plat - > hmin [ 1 ] ) ;
}
2015-07-27 00:35:07 +00:00
return ( G_UNKNOWN ) ;
}
// The spec requires upper case for hemisphere. Accept lower case but warn.
if ( plat - > ns = = ' N ' ) {
return ( result ) ;
}
else if ( plat - > ns = = ' n ' ) {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Warning: Lower case n found for latitude hemisphere. Specification requires upper case N or S. \n " ) ;
}
2015-07-27 00:35:07 +00:00
return ( result ) ;
}
else if ( plat - > ns = = ' S ' ) {
return ( - result ) ;
}
else if ( plat - > ns = = ' s ' ) {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Warning: Lower case s found for latitude hemisphere. Specification requires upper case N or S. \n " ) ;
}
2015-07-27 00:35:07 +00:00
return ( - result ) ;
}
else {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
2017-06-01 00:07:52 +00:00
dw_printf ( " Error: '%c' found for latitude hemisphere. Specification requires upper case N or S. \n " , plat - > ns ) ;
2015-07-27 01:17:23 +00:00
}
2015-07-27 00:35:07 +00:00
return ( G_UNKNOWN ) ;
}
}
/*------------------------------------------------------------------
*
* Function : get_longitude_9
*
* Purpose : Convert 9 byte longitude encoding to degrees .
*
* Inputs : plat - Pointer to first byte .
*
* Returns : Double precision value in degrees . Negative for West .
*
* Description : Longitude is expressed as a fixed 9 - character field , in degrees and
* decimal minutes ( to two decimal places ) , followed by the letter E
* for east or W for west .
* Longitude degrees are in the range 000 to 180. Longitude minutes are
* expressed as whole minutes and hundredths of a minute , separated by a
* decimal point .
* For example :
* 07201.75 W is 72 degrees 1 minute 45 seconds west .
* In generic format examples , the longitude is shown as the 9 - character
* string dddmm . hhW ( i . e . degrees , minutes and hundredths of a minute west ) .
*
* Bug : We don ' t properly deal with position ambiguity where trailing
* digits might be replaced by spaces . We simply treat them like zeros .
*
* Errors : Return G_UNKNOWN for any type of error .
*
* Example :
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:17:23 +00:00
double get_longitude_9 ( char * p , int quiet )
2015-07-27 00:35:07 +00:00
{
struct lat_s {
unsigned char deg [ 3 ] ;
unsigned char minn [ 2 ] ;
char dot ;
unsigned char hmin [ 2 ] ;
char ew ;
} * plon ;
double result = 0 ;
plon = ( void * ) p ;
if ( plon - > deg [ 0 ] = = ' 0 ' | | plon - > deg [ 0 ] = = ' 1 ' )
result + = ( ( plon - > deg [ 0 ] ) - ' 0 ' ) * 100 ;
else {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid character in longitude. Found '%c' when expecting 0 or 1 for hundreds of degrees. \n " , plon - > deg [ 0 ] ) ;
}
2015-07-27 00:35:07 +00:00
return ( G_UNKNOWN ) ;
}
if ( isdigit ( plon - > deg [ 1 ] ) )
result + = ( ( plon - > deg [ 1 ] ) - ' 0 ' ) * 10 ;
else {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid character in longitude. Found '%c' when expecting 0-9 for tens of degrees. \n " , plon - > deg [ 1 ] ) ;
}
2015-07-27 00:35:07 +00:00
return ( G_UNKNOWN ) ;
}
if ( isdigit ( plon - > deg [ 2 ] ) )
result + = ( ( plon - > deg [ 2 ] ) - ' 0 ' ) * 1 ;
else {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid character in longitude. Found '%c' when expecting 0-9 for degrees. \n " , plon - > deg [ 2 ] ) ;
}
2015-07-27 00:35:07 +00:00
return ( G_UNKNOWN ) ;
}
2016-12-17 01:12:38 +00:00
if ( plon - > minn [ 0 ] > = ' 0 ' & & plon - > minn [ 0 ] < = ' 5 ' )
2015-07-27 00:35:07 +00:00
result + = ( ( plon - > minn [ 0 ] ) - ' 0 ' ) * ( 10. / 60. ) ;
else if ( plon - > minn [ 0 ] = = ' ' )
;
else {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid character in longitude. Found '%c' when expecting 0-5 for tens of minutes. \n " , plon - > minn [ 0 ] ) ;
}
2015-07-27 00:35:07 +00:00
return ( G_UNKNOWN ) ;
}
if ( isdigit ( plon - > minn [ 1 ] ) )
result + = ( ( plon - > minn [ 1 ] ) - ' 0 ' ) * ( 1. / 60. ) ;
else if ( plon - > minn [ 1 ] = = ' ' )
;
else {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid character in longitude. Found '%c' when expecting 0-9 for minutes. \n " , plon - > minn [ 1 ] ) ;
}
2015-07-27 00:35:07 +00:00
return ( G_UNKNOWN ) ;
}
if ( plon - > dot ! = ' . ' ) {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Unexpected character \" %c \" found where period expected in longitude. \n " , plon - > dot ) ;
}
2015-07-27 00:35:07 +00:00
return ( G_UNKNOWN ) ;
}
if ( isdigit ( plon - > hmin [ 0 ] ) )
result + = ( ( plon - > hmin [ 0 ] ) - ' 0 ' ) * ( 0.1 / 60. ) ;
else if ( plon - > hmin [ 0 ] = = ' ' )
;
else {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid character in longitude. Found '%c' when expecting 0-9 for tenths of minutes. \n " , plon - > hmin [ 0 ] ) ;
}
2015-07-27 00:35:07 +00:00
return ( G_UNKNOWN ) ;
}
if ( isdigit ( plon - > hmin [ 1 ] ) )
result + = ( ( plon - > hmin [ 1 ] ) - ' 0 ' ) * ( 0.01 / 60. ) ;
else if ( plon - > hmin [ 1 ] = = ' ' )
;
else {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Invalid character in longitude. Found '%c' when expecting 0-9 for hundredths of minutes. \n " , plon - > hmin [ 1 ] ) ;
}
2015-07-27 00:35:07 +00:00
return ( G_UNKNOWN ) ;
}
// The spec requires upper case for hemisphere. Accept lower case but warn.
if ( plon - > ew = = ' E ' ) {
return ( result ) ;
}
else if ( plon - > ew = = ' e ' ) {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Warning: Lower case e found for longitude hemisphere. Specification requires upper case E or W. \n " ) ;
}
2015-07-27 00:35:07 +00:00
return ( result ) ;
}
else if ( plon - > ew = = ' W ' ) {
return ( - result ) ;
}
else if ( plon - > ew = = ' w ' ) {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Warning: Lower case w found for longitude hemisphere. Specification requires upper case E or W. \n " ) ;
}
2015-07-27 00:35:07 +00:00
return ( - result ) ;
}
else {
2015-07-27 01:17:23 +00:00
if ( ! quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Error: '%c' found for longitude hemisphere. Specification requires upper case E or W. \n " , plon - > ew ) ;
}
2015-07-27 00:35:07 +00:00
return ( G_UNKNOWN ) ;
}
}
/*------------------------------------------------------------------
*
* Function : get_timestamp
*
* Purpose : Convert 7 byte timestamp to unix time value .
*
* Inputs : p - Pointer to first byte .
*
* Returns : time_t data type . ( UTC )
*
* Description :
*
* Day / Hours / Minutes ( DHM ) format is a fixed 7 - character field , consisting of
* a 6 - digit day / time group followed by a single time indicator character ( z or
2015-09-07 23:56:20 +00:00
* / ) . The day / time group consists of a two - digit day - of - the - month ( 01 - 31 ) and
2015-07-27 00:35:07 +00:00
* a four - digit time in hours and minutes .
* Times can be expressed in zulu ( UTC / GMT ) or local time . For example :
*
* 092345 z is 2345 hours zulu time on the 9 th day of the month .
* 092345 / is 2345 hours local time on the 9 th day of the month .
*
* It is recommended that future APRS implementations only transmit zulu
* format on the air .
*
* Note : The time in Status Reports may only be in zulu format .
*
* Hours / Minutes / Seconds ( HMS ) format is a fixed 7 - character field ,
* consisting of a 6 - digit time in hours , minutes and seconds , followed by the h
* time - indicator character . For example :
*
* 234517 h is 23 hours 45 minutes and 17 seconds zulu .
*
* Note : This format may not be used in Status Reports .
*
* Month / Day / Hours / Minutes ( MDHM ) format is a fixed 8 - character field ,
2015-09-07 23:56:20 +00:00
* consisting of the month ( 01 - 12 ) and day - of - the - month ( 01 - 31 ) , followed by
2015-07-27 00:35:07 +00:00
* the time in hours and minutes zulu . For example :
*
* 10092345 is 23 hours 45 minutes zulu on October 9 th .
*
2015-09-07 23:56:20 +00:00
* This format is only used in reports from stand - alone " positionless " weather
2015-07-27 00:35:07 +00:00
* stations ( i . e . reports that do not contain station position information ) .
*
*
* Bugs : Local time not implemented yet .
* 8 character form not implemented yet .
*
* Boundary conditions are not handled properly .
* For example , suppose it is 00 : 00 : 03 on January 1.
* We receive a timestamp of 23 : 59 : 58 ( which was December 31 ) .
* If we simply replace the time , and leave the current date alone ,
* the result is about a day into the future .
*
*
* Example :
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:05:48 +00:00
time_t get_timestamp ( decode_aprs_t * A , char * p )
2015-07-27 00:35:07 +00:00
{
struct dhm_s {
char day [ 2 ] ;
char hours [ 2 ] ;
char minutes [ 2 ] ;
char tic ; /* Time indicator character. */
/* z = UTC. */
/* / = local - not implemented yet. */
} * pdhm ;
struct hms_s {
char hours [ 2 ] ;
char minutes [ 2 ] ;
char seconds [ 2 ] ;
char tic ; /* Time indicator character. */
/* h = UTC. */
} * phms ;
struct tm * ptm ;
time_t ts ;
ts = time ( NULL ) ;
2022-01-02 02:55:11 +00:00
// FIXME: use gmtime_r instead.
// Besides not being thread safe, gmtime could possibly return null.
2015-07-27 00:35:07 +00:00
ptm = gmtime ( & ts ) ;
pdhm = ( void * ) p ;
phms = ( void * ) p ;
if ( pdhm - > tic = = ' z ' | | pdhm - > tic = = ' / ' ) /* Wrong! */
{
int j ;
j = ( pdhm - > day [ 0 ] - ' 0 ' ) * 10 + pdhm - > day [ 1 ] - ' 0 ' ;
//text_color_set(DW_COLOR_DECODED);
//dw_printf("Changing day from %d to %d\n", ptm->tm_mday, j);
ptm - > tm_mday = j ;
j = ( pdhm - > hours [ 0 ] - ' 0 ' ) * 10 + pdhm - > hours [ 1 ] - ' 0 ' ;
//dw_printf("Changing hours from %d to %d\n", ptm->tm_hour, j);
ptm - > tm_hour = j ;
j = ( pdhm - > minutes [ 0 ] - ' 0 ' ) * 10 + pdhm - > minutes [ 1 ] - ' 0 ' ;
//dw_printf("Changing minutes from %d to %d\n", ptm->tm_min, j);
ptm - > tm_min = j ;
}
else if ( phms - > tic = = ' h ' )
{
int j ;
j = ( phms - > hours [ 0 ] - ' 0 ' ) * 10 + phms - > hours [ 1 ] - ' 0 ' ;
//text_color_set(DW_COLOR_DECODED);
//dw_printf("Changing hours from %d to %d\n", ptm->tm_hour, j);
ptm - > tm_hour = j ;
j = ( phms - > minutes [ 0 ] - ' 0 ' ) * 10 + phms - > minutes [ 1 ] - ' 0 ' ;
//dw_printf("Changing minutes from %d to %d\n", ptm->tm_min, j);
ptm - > tm_min = j ;
j = ( phms - > seconds [ 0 ] - ' 0 ' ) * 10 + phms - > seconds [ 1 ] - ' 0 ' ;
//dw_printf("%sChanging seconds from %d to %d\n", ptm->tm_sec, j);
ptm - > tm_sec = j ;
}
return ( mktime ( ptm ) ) ;
}
/*------------------------------------------------------------------
*
* Function : get_maidenhead
*
* Purpose : See if we have a maidenhead locator .
*
* Inputs : p - Pointer to first byte .
*
* Returns : 0 = not found .
* 4 = possible 4 character locator found .
* 6 = possible 6 character locator found .
*
* It is not stored anywhere or processed .
*
* Description :
*
* The maidenhead locator system is sometimes used as a more compact ,
* and less precise , alternative to numeric latitude and longitude .
*
* It is composed of :
* a pair of letters in range A to R .
* a pair of digits in range of 0 to 9.
* a pair of letters in range of A to X .
*
* The APRS spec says that all letters must be transmitted in upper case .
*
*
* Examples from APRS spec :
*
* IO91SX
* IO91
*
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:05:48 +00:00
int get_maidenhead ( decode_aprs_t * A , char * p )
2015-07-27 00:35:07 +00:00
{
if ( toupper ( p [ 0 ] ) > = ' A ' & & toupper ( p [ 0 ] ) < = ' R ' & &
toupper ( p [ 1 ] ) > = ' A ' & & toupper ( p [ 1 ] ) < = ' R ' & &
isdigit ( p [ 2 ] ) & & isdigit ( p [ 3 ] ) ) {
/* We have 4 characters matching the rule. */
if ( islower ( p [ 0 ] ) | | islower ( p [ 1 ] ) ) {
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Warning: Lower case letter in Maidenhead locator. Specification requires upper case. \n " ) ;
}
2015-07-27 00:35:07 +00:00
}
if ( toupper ( p [ 4 ] ) > = ' A ' & & toupper ( p [ 4 ] ) < = ' X ' & &
toupper ( p [ 5 ] ) > = ' A ' & & toupper ( p [ 5 ] ) < = ' X ' ) {
/* We have 6 characters matching the rule. */
if ( islower ( p [ 4 ] ) | | islower ( p [ 5 ] ) ) {
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Warning: Lower case letter in Maidenhead locator. Specification requires upper case. \n " ) ;
}
2015-07-27 00:35:07 +00:00
}
return 6 ;
}
return 4 ;
}
return 0 ;
}
/*------------------------------------------------------------------
*
* Function : data_extension_comment
*
2015-07-27 01:05:48 +00:00
* Purpose : A fixed length 7 - byte field may follow APRS position datA - >
2015-07-27 00:35:07 +00:00
*
* Inputs : pdext - Pointer to optional data extension and comment .
*
* Returns : true if a data extension was found .
*
* Outputs : One or more of the following , depending the data found :
*
2015-07-27 01:05:48 +00:00
* A - > g_course
2015-11-08 01:57:02 +00:00
* A - > g_speed_mph
2015-07-27 01:05:48 +00:00
* A - > g_power
* A - > g_height
* A - > g_gain
* A - > g_directivity
* A - > g_range
2015-07-27 00:35:07 +00:00
*
* Anything left over will be put in
*
2015-07-27 01:05:48 +00:00
* A - > g_comment
2015-07-27 00:35:07 +00:00
*
* Description :
*
*
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
const char * dir [ 9 ] = { " omni " , " NE " , " E " , " SE " , " S " , " SW " , " W " , " NW " , " N " } ;
2015-07-27 01:05:48 +00:00
static int data_extension_comment ( decode_aprs_t * A , char * pdext )
2015-07-27 00:35:07 +00:00
{
int n ;
if ( strlen ( pdext ) < 7 ) {
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_comment , pdext , sizeof ( A - > g_comment ) ) ;
2015-07-27 00:35:07 +00:00
return 0 ;
}
/* Tyy/Cxx - Area object descriptor. */
if ( pdext [ 0 ] = = ' T ' & &
pdext [ 3 ] = = ' / ' & &
pdext [ 4 ] = = ' C ' )
{
/* not decoded at this time */
2015-07-27 01:05:48 +00:00
process_comment ( A , pdext + 7 , - 1 ) ;
2015-07-27 00:35:07 +00:00
return 1 ;
}
/* CSE/SPD */
/* For a weather station (symbol code _) this is wind. */
/* For others, it would be course and speed. */
if ( pdext [ 3 ] = = ' / ' )
{
if ( sscanf ( pdext , " %3d " , & n ) )
{
2015-07-27 01:05:48 +00:00
A - > g_course = n ;
2015-07-27 00:35:07 +00:00
}
if ( sscanf ( pdext + 4 , " %3d " , & n ) )
{
2015-11-08 01:57:02 +00:00
A - > g_speed_mph = DW_KNOTS_TO_MPH ( n ) ;
2015-07-27 00:35:07 +00:00
}
/* Bearing and Number/Range/Quality? */
if ( pdext [ 7 ] = = ' / ' & & pdext [ 11 ] = = ' / ' )
{
2015-07-27 01:05:48 +00:00
process_comment ( A , pdext + 7 + 8 , - 1 ) ;
2015-07-27 00:35:07 +00:00
}
else {
2015-07-27 01:05:48 +00:00
process_comment ( A , pdext + 7 , - 1 ) ;
2015-07-27 00:35:07 +00:00
}
return 1 ;
}
/* check for Station power, height, gain. */
if ( strncmp ( pdext , " PHG " , 3 ) = = 0 )
{
2015-07-27 01:05:48 +00:00
A - > g_power = ( pdext [ 3 ] - ' 0 ' ) * ( pdext [ 3 ] - ' 0 ' ) ;
A - > g_height = ( 1 < < ( pdext [ 4 ] - ' 0 ' ) ) * 10 ;
A - > g_gain = pdext [ 5 ] - ' 0 ' ;
2015-07-27 00:35:07 +00:00
if ( pdext [ 6 ] > = ' 0 ' & & pdext [ 6 ] < = ' 8 ' ) {
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_directivity , dir [ pdext [ 6 ] - ' 0 ' ] , sizeof ( A - > g_directivity ) ) ;
2015-07-27 00:35:07 +00:00
}
2015-07-27 01:05:48 +00:00
process_comment ( A , pdext + 7 , - 1 ) ;
2015-07-27 00:35:07 +00:00
return 1 ;
}
/* check for precalculated radio range. */
if ( strncmp ( pdext , " RNG " , 3 ) = = 0 )
{
if ( sscanf ( pdext + 3 , " %4d " , & n ) )
{
2015-07-27 01:05:48 +00:00
A - > g_range = n ;
2015-07-27 00:35:07 +00:00
}
2015-07-27 01:05:48 +00:00
process_comment ( A , pdext + 7 , - 1 ) ;
2015-07-27 00:35:07 +00:00
return 1 ;
}
/* DF signal strength, */
if ( strncmp ( pdext , " DFS " , 3 ) = = 0 )
{
2015-07-27 01:05:48 +00:00
//A->g_strength = pdext[3] - '0';
A - > g_height = ( 1 < < ( pdext [ 4 ] - ' 0 ' ) ) * 10 ;
A - > g_gain = pdext [ 5 ] - ' 0 ' ;
2015-07-27 00:35:07 +00:00
if ( pdext [ 6 ] > = ' 0 ' & & pdext [ 6 ] < = ' 8 ' ) {
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_directivity , dir [ pdext [ 6 ] - ' 0 ' ] , sizeof ( A - > g_directivity ) ) ;
2015-07-27 00:35:07 +00:00
}
2015-07-27 01:05:48 +00:00
process_comment ( A , pdext + 7 , - 1 ) ;
2015-07-27 00:35:07 +00:00
return 1 ;
}
2015-07-27 01:05:48 +00:00
process_comment ( A , pdext , - 1 ) ;
2015-07-27 00:35:07 +00:00
return 0 ;
}
/*------------------------------------------------------------------
*
* Function : decode_tocall
*
* Purpose : Extract application from the destination .
*
* Inputs : dest - Destination address .
* Don ' t care if SSID is present or not .
*
2015-07-27 01:05:48 +00:00
* Outputs : A - > g_mfr
2015-07-27 00:35:07 +00:00
*
* 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 .
*
2017-12-16 22:00:56 +00:00
* 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
2015-07-27 00:35:07 +00:00
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2016-12-17 01:12:38 +00:00
// 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.
2020-10-26 02:01:09 +00:00
# define MAX_TOCALLS 250
2015-07-27 00:35:07 +00:00
static struct tocalls_s {
unsigned char len ;
char prefix [ 7 ] ;
char * description ;
} tocalls [ MAX_TOCALLS ] ;
static int num_tocalls = 0 ;
2015-09-07 23:56:20 +00:00
// Make sure the array is null terminated.
2020-06-21 01:06:00 +00:00
// If search order is changed, do the same in symbols.c for consistency.
2017-12-16 22:00:56 +00:00
2015-09-07 23:56:20 +00:00
static const char * search_locations [ ] = {
2020-06-21 01:06:00 +00:00
( const char * ) " tocalls.txt " , // CWD
( const char * ) " data/tocalls.txt " , // Windows with CMake
( const char * ) " ../data/tocalls.txt " , // ?
2015-09-07 23:56:20 +00:00
# ifndef __WIN32__
( const char * ) " /usr/local/share/direwolf/tocalls.txt " ,
2017-12-16 22:00:56 +00:00
( 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 " ,
2015-09-07 23:56:20 +00:00
# endif
2020-06-21 01:06:00 +00:00
( const char * ) NULL // Important - Indicates end of list.
2015-09-07 23:56:20 +00:00
} ;
2015-11-08 01:57:02 +00:00
static int tocall_cmp ( const void * px , const void * py )
2015-07-27 00:35:07 +00:00
{
2015-11-08 01:57:02 +00:00
const struct tocalls_s * x = ( struct tocalls_s * ) px ;
const struct tocalls_s * y = ( struct tocalls_s * ) py ;
2015-07-27 00:35:07 +00:00
if ( x - > len ! = y - > len ) return ( y - > len - x - > len ) ;
return ( strcmp ( x - > prefix , y - > prefix ) ) ;
}
2015-07-27 01:05:48 +00:00
static void decode_tocall ( decode_aprs_t * A , char * dest )
2015-07-27 00:35:07 +00:00
{
2015-09-07 23:56:20 +00:00
FILE * fp = 0 ;
int n = 0 ;
2015-07-27 00:35:07 +00:00
static int first_time = 1 ;
char stuff [ 100 ] ;
2015-09-07 23:56:20 +00:00
char * p = NULL ;
char * r = NULL ;
2015-07-27 00:35:07 +00:00
//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 ) {
2015-09-07 23:56:20 +00:00
n = 0 ;
fp = NULL ;
do {
if ( search_locations [ n ] = = NULL ) break ;
fp = fopen ( search_locations [ n + + ] , " r " ) ;
} while ( fp = = NULL ) ;
2015-07-27 00:35:07 +00:00
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 ' ;
}
2015-11-08 01:57:02 +00:00
// dw_printf("debug: %s\n", stuff);
2015-07-27 00:35:07 +00:00
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 ) ;
2016-12-17 01:12:38 +00:00
// dw_printf("debug %d: %d '%s' -> '%s'\n", num_tocalls, tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description);
2015-07-27 00:35:07 +00:00
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 ) ;
2016-12-17 01:12:38 +00:00
// dw_printf("debug %d: %d '%s' -> '%s'\n", num_tocalls, tocalls[num_tocalls].len, tocalls[num_tocalls].prefix, tocalls[num_tocalls].description);
2015-07-27 00:35:07 +00:00
num_tocalls + + ;
}
}
2016-12-17 01:12:38 +00:00
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 ) ;
}
2015-07-27 00:35:07 +00:00
}
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 .
*/
2015-09-07 23:56:20 +00:00
# if defined(__WIN32__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__APPLE__)
2015-07-27 00:35:07 +00:00
qsort ( tocalls , num_tocalls , sizeof ( struct tocalls_s ) , tocall_cmp ) ;
# else
qsort ( tocalls , num_tocalls , sizeof ( struct tocalls_s ) , ( __compar_fn_t ) tocall_cmp ) ;
# endif
}
else {
2015-07-27 01:17:23 +00:00
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 " ) ;
}
2015-07-27 00:35:07 +00:00
}
first_time = 0 ;
2016-12-17 01:12:38 +00:00
//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);
//}
2015-07-27 00:35:07 +00:00
}
for ( n = 0 ; n < num_tocalls ; n + + ) {
if ( strncmp ( dest , tocalls [ n ] . prefix , tocalls [ n ] . len ) = = 0 ) {
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_mfr , tocalls [ n ] . description , sizeof ( A - > g_mfr ) ) ;
2015-07-27 00:35:07 +00:00
return ;
}
}
} /* end decode_tocall */
2015-07-27 01:05:48 +00:00
/*------------------------------------------------------------------
*
* Function : substr_se
*
* Purpose : Extract substring given start and end + 1 offset .
*
* Inputs : src - Source string
*
* start - Start offset .
*
* endp1 - End offset + 1 for ease of use with regexec result .
*
* Outputs : dest - Destination for substring .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-09-07 23:56:20 +00:00
// TODO: potential for buffer overflow here.
2015-07-27 01:05:48 +00:00
static void substr_se ( char * dest , const char * src , int start , int endp1 )
{
int len = endp1 - start ;
if ( start < 0 | | endp1 < 0 | | len < = 0 ) {
2015-09-07 23:56:20 +00:00
dest [ 0 ] = ' \0 ' ;
2015-07-27 01:05:48 +00:00
return ;
}
memcpy ( dest , src + start , len ) ;
dest [ len ] = ' \0 ' ;
} /* end substr_se */
2015-07-27 00:35:07 +00:00
/*------------------------------------------------------------------
*
* Function : process_comment
*
* Purpose : Extract optional items from the comment .
*
* Inputs : pstart - Pointer to start of left over information field .
*
* clen - Length of comment or - 1 to take it all .
*
2015-07-27 01:05:48 +00:00
* Outputs : A - > g_telemetry - Base 91 telemetry | ss1122 |
2017-03-05 20:15:33 +00:00
* A - > g_altitude_ft - from / A = 123456
2015-07-27 01:05:48 +00:00
* A - > g_lat - Might be adjusted from ! DAO !
* A - > g_lon - Might be adjusted from ! DAO !
* A - > g_aprstt_loc - Private extension to ! DAO !
* A - > g_freq
* A - > g_tone
* A - > g_offset
* A - > g_comment - Anything left over after extracting above .
2015-07-27 00:35:07 +00:00
*
* Description : After processing fixed and possible optional parts
* of the message , everything left over is a comment .
*
* Except ! ! !
*
* There are could be some other pieces of data , with
* particular formats , buried in there .
* Pull out those special items and put everything
2015-07-27 01:05:48 +00:00
* else into A - > g_comment .
2015-07-27 00:35:07 +00:00
*
* References : http : //www.aprs.org/info/freqspec.txt
*
* 999.999 MHz T100 + 060 Voice frequency .
*
* http : //www.aprs.org/datum.txt
*
* ! DAO ! APRS precision and Datum option .
*
2021-09-19 18:51:18 +00:00
* Protocol reference , end of chapter 6.
2015-07-27 00:35:07 +00:00
*
* / A = 123456 Altitude
*
2017-03-05 20:15:33 +00:00
* What can appear in a comment ?
*
* Chapter 5 of the APRS spec ( http : //www.aprs.org/doc/APRS101.PDF ) says:
*
* " The comment may contain any printable ASCII characters (except | and ~,
* which are reserved for TNC channel switching ) . "
*
* " Printable " would exclude character values less than space ( 00100000 ) , e . g .
* tab , carriage return , line feed , nul . Sometimes we see carriage return
* ( 00001010 ) at the end of APRS packets . This would be in violation of the
* specification .
*
* The base 91 telemetry format ( http : //he.fi/doc/aprs-base91-comment-telemetry.txt ),
* which is not part of the APRS spec , uses the | character in the comment to delimit encoded
* telemetry data . This would be in violation of the original spec .
*
* The APRS Spec Addendum 1.2 Proposals ( http : //www.aprs.org/aprs12/datum.txt)
* adds use of UTF - 8 ( https : //en.wikipedia.org/wiki/UTF-8 )for the free form text in
* messages and comments . It can ' t be used in the fixed width fields .
*
* Non - ASCII characters are represented by multi - byte sequences . All bytes in these
* multi - byte sequences have the most significant bit set to 1. Using UTF - 8 would not
* add any nul ( 00000000 ) bytes to the stream .
*
* There are two known cases where we can have a nul character value .
*
* * The Kenwood TM - D710A sometimes sends packets like this :
*
* VA3AJ - 9 > T2QU6X , VE3WRC , WIDE1 , K8UNS , WIDE2 * : 4 P < 0x00 > < 0x0f > 4 T < 0x00 > < 0x0f > 4 X < 0x00 > < 0x0f > 4 \ < 0x00 > ` nW < 0x1f > oS8 > / ] " 6M}driving fast=
* K4JH - 9 > S5UQ6X , WR4AGC - 3 * , WIDE1 * : 4 P < 0x00 > < 0x0f > 4 T < 0x00 > < 0x0f > 4 X < 0x00 > < 0x0f > 4 \ < 0x00 > ` jP } l " &>/] " 47 } QRV from the EV =
*
* Notice that the data type indicator of " 4 " is not valid . If we remove
* 4 P < 0x00 > < 0x0f > 4 T < 0x00 > < 0x0f > 4 X < 0x00 > < 0x0f > 4 \ < 0x00 > we are left with a good MIC - E format .
* This same thing has been observed from others and is intermittent .
*
* * AGW Tracker can send UTF - 16 if an option is selected . This can introduce nul bytes .
2021-09-19 18:51:18 +00:00
* This is wrong . It should be using UTF - 8 and I ' m not going to accommodate it here .
2017-03-05 20:15:33 +00:00
*
*
* The digipeater and IGate functions should pass along anything exactly the
* we received it , even if it is invalid . If different implementations try to fix it up
* somehow , like changing unprintable characters to spaces , we will only make things
* worse and thwart the duplicate detection .
2015-07-27 00:35:07 +00:00
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-07-27 01:05:48 +00:00
/* CTCSS tones in various formats to avoid conversions every time. */
# define NUM_CTCSS 50
static const int i_ctcss [ NUM_CTCSS ] = {
67 , 69 , 71 , 74 , 77 , 79 , 82 , 85 , 88 , 91 ,
94 , 97 , 100 , 103 , 107 , 110 , 114 , 118 , 123 , 127 ,
131 , 136 , 141 , 146 , 151 , 156 , 159 , 162 , 165 , 167 ,
171 , 173 , 177 , 179 , 183 , 186 , 189 , 192 , 196 , 199 ,
203 , 206 , 210 , 218 , 225 , 229 , 233 , 241 , 250 , 254 } ;
static const float f_ctcss [ NUM_CTCSS ] = {
67.0 , 69.3 , 71.9 , 74.4 , 77.0 , 79.7 , 82.5 , 85.4 , 88.5 , 91.5 ,
94.8 , 97.4 , 100.0 , 103.5 , 107.2 , 110.9 , 114.8 , 118.8 , 123.0 , 127.3 ,
131.8 , 136.5 , 141.3 , 146.2 , 151.4 , 156.7 , 159.8 , 162.2 , 165.5 , 167.9 ,
171.3 , 173.8 , 177.3 , 179.9 , 183.5 , 186.2 , 189.9 , 192.8 , 196.6 , 199.5 ,
203.5 , 206.5 , 210.7 , 218.1 , 225.7 , 229.1 , 233.6 , 241.8 , 250.3 , 254.1 } ;
static const char * s_ctcss [ NUM_CTCSS ] = {
" 67.0 " , " 69.3 " , " 71.9 " , " 74.4 " , " 77.0 " , " 79.7 " , " 82.5 " , " 85.4 " , " 88.5 " , " 91.5 " ,
" 94.8 " , " 97.4 " , " 100.0 " , " 103.5 " , " 107.2 " , " 110.9 " , " 114.8 " , " 118.8 " , " 123.0 " , " 127.3 " ,
" 131.8 " , " 136.5 " , " 141.3 " , " 146.2 " , " 151.4 " , " 156.7 " , " 159.8 " , " 162.2 " , " 165.5 " , " 167.9 " ,
" 171.3 " , " 173.8 " , " 177.3 " , " 179.9 " , " 183.5 " , " 186.2 " , " 189.9 " , " 192.8 " , " 196.6 " , " 199.5 " ,
" 203.5 " , " 206.5 " , " 210.7 " , " 218.1 " , " 225.7 " , " 229.1 " , " 233.6 " , " 241.8 " , " 250.3 " , " 254.1 " } ;
2015-07-27 00:35:07 +00:00
# define sign(x) (((x)>=0)?1:(-1))
2015-07-27 01:05:48 +00:00
static void process_comment ( decode_aprs_t * A , char * pstart , int clen )
2015-07-27 00:35:07 +00:00
{
static int first_time = 1 ;
2015-07-27 01:05:48 +00:00
static regex_t std_freq_re ; /* Frequency in standard format. */
static regex_t std_tone_re ; /* Tone in standard format. */
static regex_t std_toff_re ; /* Explicitly no tone. */
static regex_t std_dcs_re ; /* Digital codes squelch in standard format. */
static regex_t std_offset_re ; /* Xmit freq offset in standard format. */
static regex_t std_range_re ; /* Range in standard format. */
static regex_t dao_re ; /* DAO */
static regex_t alt_re ; /* /A= altitude */
static regex_t bad_freq_re ; /* Likely frequency, not standard format */
static regex_t bad_tone_re ; /* Likely tone, not standard format */
static regex_t base91_tel_re ; /* Base 91 compressed telemetry data. */
2022-01-17 22:10:47 +00:00
2015-07-27 00:35:07 +00:00
int e ;
char emsg [ 100 ] ;
2015-07-27 01:05:48 +00:00
# define MAXMATCH 4
2015-07-27 00:35:07 +00:00
regmatch_t match [ MAXMATCH ] ;
2015-09-07 23:56:20 +00:00
char temp [ sizeof ( A - > g_comment ) ] ;
2015-07-27 01:05:48 +00:00
int keep_going ;
2015-07-27 00:35:07 +00:00
/*
* No sense in recompiling the patterns and freeing every time .
*/
if ( first_time )
{
/*
2015-07-27 01:05:48 +00:00
* Frequency must be at the at the beginning .
2015-07-27 00:35:07 +00:00
* Others can be anywhere in the comment .
*/
2015-07-27 01:05:48 +00:00
//e = regcomp (&freq_re, "^[0-9A-O][0-9][0-9]\\.[0-9][0-9][0-9 ]MHz( [TCDtcd][0-9][0-9][0-9]| Toff)?( [+-][0-9][0-9][0-9])?", REG_EXTENDED);
// Freq optionally preceded by space or /.
// Third fractional digit can be space instead.
// "MHz" should be exactly that capitalization.
// Print warning later it not.
e = regcomp ( & std_freq_re , " ^[/ ]?([0-9A-O][0-9][0-9] \\ .[0-9][0-9][0-9 ])([Mm][Hh][Zz]) " , REG_EXTENDED ) ;
if ( e ) {
regerror ( e , & std_freq_re , emsg , sizeof ( emsg ) ) ;
dw_printf ( " %s:%d: %s \n " , __FILE__ , __LINE__ , emsg ) ;
}
// If no tone, we might gobble up / after any data extension,
// We could also have a space but it's not required.
// I don't understand the difference between T and C so treat the same for now.
// We can also have "off" instead of number to explicitly mean none.
e = regcomp ( & std_tone_re , " ^[/ ]?([TtCc][012][0-9][0-9]) " , REG_EXTENDED ) ;
if ( e ) {
regerror ( e , & std_tone_re , emsg , sizeof ( emsg ) ) ;
dw_printf ( " %s:%d: %s \n " , __FILE__ , __LINE__ , emsg ) ;
}
e = regcomp ( & std_toff_re , " ^[/ ]?[TtCc][Oo][Ff][Ff] " , REG_EXTENDED ) ;
if ( e ) {
regerror ( e , & std_toff_re , emsg , sizeof ( emsg ) ) ;
dw_printf ( " %s:%d: %s \n " , __FILE__ , __LINE__ , emsg ) ;
}
e = regcomp ( & std_dcs_re , " ^[/ ]?[Dd]([0-7][0-7][0-7]) " , REG_EXTENDED ) ;
if ( e ) {
regerror ( e , & std_dcs_re , emsg , sizeof ( emsg ) ) ;
dw_printf ( " %s:%d: %s \n " , __FILE__ , __LINE__ , emsg ) ;
}
e = regcomp ( & std_offset_re , " ^[/ ]?([+-][0-9][0-9][0-9]) " , REG_EXTENDED ) ;
if ( e ) {
regerror ( e , & std_offset_re , emsg , sizeof ( emsg ) ) ;
dw_printf ( " %s:%d: %s \n " , __FILE__ , __LINE__ , emsg ) ;
}
e = regcomp ( & std_range_re , " ^[/ ]?[Rr]([0-9][0-9])([mk]) " , REG_EXTENDED ) ;
2015-07-27 00:35:07 +00:00
if ( e ) {
2015-07-27 01:05:48 +00:00
regerror ( e , & std_range_re , emsg , sizeof ( emsg ) ) ;
2015-07-27 00:35:07 +00:00
dw_printf ( " %s:%d: %s \n " , __FILE__ , __LINE__ , emsg ) ;
}
2015-07-27 01:05:48 +00:00
e = regcomp ( & dao_re , " !([A-Z][0-9 ][0-9 ]|[a-z][!-{ ][!-{ ]|T[0-9 B][0-9 ])! " , REG_EXTENDED ) ;
2015-07-27 00:35:07 +00:00
if ( e ) {
regerror ( e , & dao_re , emsg , sizeof ( 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 ) ;
if ( e ) {
regerror ( e , & alt_re , emsg , sizeof ( emsg ) ) ;
dw_printf ( " %s:%d: %s \n " , __FILE__ , __LINE__ , emsg ) ;
}
2015-07-27 01:05:48 +00:00
e = regcomp ( & bad_freq_re , " [0-9][0-9][0-9] \\ .[0-9][0-9][0-9]? " , REG_EXTENDED ) ;
if ( e ) {
regerror ( e , & bad_freq_re , emsg , sizeof ( emsg ) ) ;
dw_printf ( " %s:%d: %s \n " , __FILE__ , __LINE__ , emsg ) ;
}
e = regcomp ( & bad_tone_re , " (^|[^0-9.])([6789][0-9] \\ .[0-9]|[12][0-9][0-9] \\ .[0-9]|67|77|100|123)($|[^0-9.]) " , REG_EXTENDED ) ;
if ( e ) {
regerror ( e , & bad_tone_re , emsg , sizeof ( emsg ) ) ;
dw_printf ( " %s:%d: %s \n " , __FILE__ , __LINE__ , emsg ) ;
}
2022-01-02 02:55:11 +00:00
e = regcomp ( & base91_tel_re , " \\ |(([!-{][!-{]){2,7}) \\ | " , REG_EXTENDED ) ;
2015-07-27 01:05:48 +00:00
if ( e ) {
regerror ( e , & base91_tel_re , emsg , sizeof ( emsg ) ) ;
dw_printf ( " %s:%d: %s \n " , __FILE__ , __LINE__ , emsg ) ;
}
2015-07-27 00:35:07 +00:00
first_time = 0 ;
}
2015-09-07 23:56:20 +00:00
/*
* If clen is > = 0 , take only specified number of characters .
* Otherwise , take it all .
*/
if ( clen < 0 ) {
clen = strlen ( pstart ) ;
}
/*
* Watch out for buffer overflow .
* KG6AZZ reports that there is a local digipeater that seems to
2021-09-19 18:51:18 +00:00
* malfunction occasionally . It corrupts the packet , as it is
2015-09-07 23:56:20 +00:00
* digipeated , causing the comment to be hundreds of characters long .
*/
2016-12-23 16:28:39 +00:00
if ( clen > ( int ) ( sizeof ( A - > g_comment ) - 1 ) ) {
2015-09-07 23:56:20 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Comment is extremely long, %d characters. \n " , clen ) ;
dw_printf ( " Please report this, along with surrounding lines, so we can find the cause. \n " ) ;
}
clen = sizeof ( A - > g_comment ) - 1 ;
}
if ( clen > 0 ) {
2015-07-27 01:05:48 +00:00
memcpy ( A - > g_comment , pstart , ( size_t ) clen ) ;
A - > g_comment [ clen ] = ' \0 ' ;
2015-07-27 00:35:07 +00:00
}
else {
2015-09-07 23:56:20 +00:00
A - > g_comment [ 0 ] = ' \0 ' ;
2015-07-27 00:35:07 +00:00
}
/*
2015-07-27 01:05:48 +00:00
* Look for frequency in the standard format at start of comment .
* If that fails , try to obtain from object name .
2015-07-27 00:35:07 +00:00
*/
2015-07-27 01:05:48 +00:00
if ( regexec ( & std_freq_re , A - > g_comment , MAXMATCH , match , 0 ) = = 0 )
2015-07-27 00:35:07 +00:00
{
2015-07-27 01:05:48 +00:00
char sftemp [ 30 ] ;
char smtemp [ 10 ] ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
//dw_printf("matches= %d - %d, %d - %d, %d - %d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo),
// (int)(match[1].rm_so), (int)(match[1].rm_eo),
// (int)(match[2].rm_so), (int)(match[2].rm_eo) );
substr_se ( sftemp , A - > g_comment , match [ 1 ] . rm_so , match [ 1 ] . rm_eo ) ;
substr_se ( smtemp , A - > g_comment , match [ 2 ] . rm_so , match [ 2 ] . rm_eo ) ;
switch ( sftemp [ 0 ] ) {
case ' A ' : A - > g_freq = 1200 + atof ( sftemp + 1 ) ; break ;
case ' B ' : A - > g_freq = 2300 + atof ( sftemp + 1 ) ; break ;
case ' C ' : A - > g_freq = 2400 + atof ( sftemp + 1 ) ; break ;
case ' D ' : A - > g_freq = 3400 + atof ( sftemp + 1 ) ; break ;
case ' E ' : A - > g_freq = 5600 + atof ( sftemp + 1 ) ; break ;
case ' F ' : A - > g_freq = 5700 + atof ( sftemp + 1 ) ; break ;
case ' G ' : A - > g_freq = 5800 + atof ( sftemp + 1 ) ; break ;
case ' H ' : A - > g_freq = 10100 + atof ( sftemp + 1 ) ; break ;
case ' I ' : A - > g_freq = 10200 + atof ( sftemp + 1 ) ; break ;
case ' J ' : A - > g_freq = 10300 + atof ( sftemp + 1 ) ; break ;
case ' K ' : A - > g_freq = 10400 + atof ( sftemp + 1 ) ; break ;
case ' L ' : A - > g_freq = 10500 + atof ( sftemp + 1 ) ; break ;
case ' M ' : A - > g_freq = 24000 + atof ( sftemp + 1 ) ; break ;
case ' N ' : A - > g_freq = 24100 + atof ( sftemp + 1 ) ; break ;
case ' O ' : A - > g_freq = 24200 + atof ( sftemp + 1 ) ; break ;
default : A - > g_freq = atof ( sftemp ) ; break ;
}
if ( strncmp ( smtemp , " MHz " , 3 ) ! = 0 ) {
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Warning: \" %s \" has non-standard capitalization and might not be recognized by some systems. \n " , smtemp ) ;
dw_printf ( " For best compatibility, it should be exactly like this: \" MHz \" (upper,upper,lower case) \n " ) ;
}
2015-07-27 01:05:48 +00:00
}
2015-09-07 23:56:20 +00:00
strlcpy ( temp , A - > g_comment + match [ 0 ] . rm_eo , sizeof ( temp ) ) ;
strlcpy ( A - > g_comment + match [ 0 ] . rm_so , temp , sizeof ( A - > g_comment ) ) ;
2015-07-27 01:05:48 +00:00
}
else if ( strlen ( A - > g_name ) > 0 ) {
// Try to extract sensible number from object/item name.
double x = atof ( A - > g_name ) ;
if ( ( x > = 144 & & x < = 148 ) | |
( x > = 222 & & x < = 225 ) | |
( x > = 420 & & x < = 450 ) | |
( x > = 902 & & x < = 928 ) ) {
A - > g_freq = x ;
}
}
/*
* Next , look for tone , DCS code , and range .
* Examples always have them in same order but it ' s not clear
* whether any order is allowed after possible frequency .
*
* TODO : Convert integer tone to original value for display .
* TODO : samples in zfreq - test3 . txt
*/
keep_going = 1 ;
while ( keep_going ) {
if ( regexec ( & std_tone_re , A - > g_comment , MAXMATCH , match , 0 ) = = 0 ) {
char sttemp [ 10 ] ; /* includes leading letter */
int f ;
int i ;
substr_se ( sttemp , A - > g_comment , match [ 1 ] . rm_so , match [ 1 ] . rm_eo ) ;
// Try to convert from integer to proper value.
f = atoi ( sttemp + 1 ) ;
for ( i = 0 ; i < NUM_CTCSS ; i + + ) {
if ( f = = i_ctcss [ i ] ) {
A - > g_tone = f_ctcss [ i ] ;
break ;
}
}
if ( A - > g_tone = = G_UNKNOWN ) {
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Bad CTCSS/PL specification: \" %s \" \n " , sttemp ) ;
dw_printf ( " Integer does not correspond to standard tone. \n " ) ;
}
2015-07-27 01:05:48 +00:00
}
2015-09-07 23:56:20 +00:00
strlcpy ( temp , A - > g_comment + match [ 0 ] . rm_eo , sizeof ( temp ) ) ;
strlcpy ( A - > g_comment + match [ 0 ] . rm_so , temp , sizeof ( A - > g_comment ) ) ;
2015-07-27 01:05:48 +00:00
}
else if ( regexec ( & std_toff_re , A - > g_comment , MAXMATCH , match , 0 ) = = 0 ) {
2015-11-08 01:57:02 +00:00
dw_printf ( " NO tone \n " ) ;
2015-07-27 01:05:48 +00:00
A - > g_tone = 0 ;
2015-09-07 23:56:20 +00:00
strlcpy ( temp , A - > g_comment + match [ 0 ] . rm_eo , sizeof ( temp ) ) ;
strlcpy ( A - > g_comment + match [ 0 ] . rm_so , temp , sizeof ( A - > g_comment ) ) ;
2015-07-27 01:05:48 +00:00
}
else if ( regexec ( & std_dcs_re , A - > g_comment , MAXMATCH , match , 0 ) = = 0 ) {
char sttemp [ 10 ] ; /* three octal digits */
substr_se ( sttemp , A - > g_comment , match [ 1 ] . rm_so , match [ 1 ] . rm_eo ) ;
A - > g_dcs = strtoul ( sttemp , NULL , 8 ) ;
2015-09-07 23:56:20 +00:00
strlcpy ( temp , A - > g_comment + match [ 0 ] . rm_eo , sizeof ( temp ) ) ;
strlcpy ( A - > g_comment + match [ 0 ] . rm_so , temp , sizeof ( A - > g_comment ) - match [ 0 ] . rm_so ) ;
2015-07-27 01:05:48 +00:00
}
else if ( regexec ( & std_offset_re , A - > g_comment , MAXMATCH , match , 0 ) = = 0 ) {
char sttemp [ 10 ] ; /* includes leading sign */
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
substr_se ( sttemp , A - > g_comment , match [ 1 ] . rm_so , match [ 1 ] . rm_eo ) ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
A - > g_offset = 10 * atoi ( sttemp ) ;
2015-07-27 00:35:07 +00:00
2015-09-07 23:56:20 +00:00
strlcpy ( temp , A - > g_comment + match [ 0 ] . rm_eo , sizeof ( temp ) ) ;
strlcpy ( A - > g_comment + match [ 0 ] . rm_so , temp , sizeof ( A - > g_comment ) - match [ 0 ] . rm_so ) ;
2015-07-27 01:05:48 +00:00
}
else if ( regexec ( & std_range_re , A - > g_comment , MAXMATCH , match , 0 ) = = 0 ) {
char sttemp [ 10 ] ; /* should be two digits */
char sutemp [ 10 ] ; /* m for miles or k for km */
substr_se ( sttemp , A - > g_comment , match [ 1 ] . rm_so , match [ 1 ] . rm_eo ) ;
substr_se ( sutemp , A - > g_comment , match [ 2 ] . rm_so , match [ 2 ] . rm_eo ) ;
if ( strcmp ( sutemp , " m " ) = = 0 ) {
A - > g_range = atoi ( sttemp ) ;
}
else {
A - > g_range = DW_KM_TO_MILES ( atoi ( sttemp ) ) ;
}
2015-09-07 23:56:20 +00:00
strlcpy ( temp , A - > g_comment + match [ 0 ] . rm_eo , sizeof ( temp ) ) ;
strlcpy ( A - > g_comment + match [ 0 ] . rm_so , temp , sizeof ( A - > g_comment ) - match [ 0 ] . rm_so ) ;
2015-07-27 01:05:48 +00:00
}
else {
keep_going = 0 ;
}
2015-07-27 00:35:07 +00:00
}
2015-07-27 01:05:48 +00:00
/*
* Telemetry data , in base 91 compressed format appears as 2 to 7 pairs
2022-01-02 02:55:11 +00:00
* of base 91 digits , ! thru { , surrounded by | at start and end .
2015-07-27 01:05:48 +00:00
*/
if ( regexec ( & base91_tel_re , A - > g_comment , MAXMATCH , match , 0 ) = = 0 )
{
2022-01-02 02:55:11 +00:00
char tdata [ 30 ] ; /* Should be even number of 4 to 14 characters. */
2015-07-27 01:05:48 +00:00
2022-01-02 02:55:11 +00:00
//dw_printf("compressed telemetry start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo));
2015-07-27 01:05:48 +00:00
substr_se ( tdata , A - > g_comment , match [ 1 ] . rm_so , match [ 1 ] . rm_eo ) ;
2022-01-02 02:55:11 +00:00
//dw_printf("compressed telemetry data = \"%s\"\n", tdata);
2015-07-27 01:05:48 +00:00
2015-11-08 01:57:02 +00:00
telemetry_data_base91 ( A - > g_src , tdata , A - > g_telemetry , sizeof ( A - > g_telemetry ) ) ;
2015-07-27 01:05:48 +00:00
2015-09-07 23:56:20 +00:00
strlcpy ( temp , A - > g_comment + match [ 0 ] . rm_eo , sizeof ( temp ) ) ;
strlcpy ( A - > g_comment + match [ 0 ] . rm_so , temp , sizeof ( A - > g_comment ) - match [ 0 ] . rm_so ) ;
2015-07-27 01:05:48 +00:00
}
2015-07-27 00:35:07 +00:00
/*
* Latitude and Longitude in the form DD MM . HH has a resolution of about 60 feet .
2015-07-27 01:05:48 +00:00
* The ! DAO ! option allows another digit or almost two for greater resolution .
*
* This would not make sense to use this with a compressed location which
* already has much greater resolution .
*
2021-09-19 18:51:18 +00:00
* It surprised me to see this in a MIC - E message .
2015-07-27 01:05:48 +00:00
* MIC - E has resolution of .01 minute so it would make sense to have it as an option .
2022-01-02 02:55:11 +00:00
* We also find an example in http : //www.aprs.org/aprs12/mic-e-examples.txt
* ' abc123R / ' 123 } FFF . FFFMHztext . . . / A = 123456. . . ! DAO ! Mv
2015-07-27 00:35:07 +00:00
*/
2015-07-27 01:05:48 +00:00
if ( regexec ( & dao_re , A - > g_comment , MAXMATCH , match , 0 ) = = 0 )
2015-07-27 00:35:07 +00:00
{
2015-07-27 01:05:48 +00:00
int d = A - > g_comment [ match [ 0 ] . rm_so + 1 ] ;
int a = A - > g_comment [ match [ 0 ] . rm_so + 2 ] ;
int o = A - > g_comment [ match [ 0 ] . rm_so + 3 ] ;
2015-07-27 00:35:07 +00:00
2022-01-02 02:55:11 +00:00
dw_printf ( " DAO start=%d, end=%d \n " , ( int ) ( match [ 0 ] . rm_so ) , ( int ) ( match [ 0 ] . rm_eo ) ) ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
/*
* Private extension for APRStt
*/
if ( d = = ' T ' ) {
if ( a = = ' ' & & o = = ' ' ) {
2015-09-07 23:56:20 +00:00
snprintf ( A - > g_aprstt_loc , sizeof ( A - > g_aprstt_loc ) , " APRStt corral location " ) ;
2015-07-27 01:05:48 +00:00
}
else if ( isdigit ( a ) & & o = = ' ' ) {
2015-09-07 23:56:20 +00:00
snprintf ( A - > g_aprstt_loc , sizeof ( A - > g_aprstt_loc ) , " APRStt location %c of 10 " , a ) ;
2015-07-27 01:05:48 +00:00
}
else if ( isdigit ( a ) & & isdigit ( o ) ) {
2015-09-07 23:56:20 +00:00
snprintf ( A - > g_aprstt_loc , sizeof ( A - > g_aprstt_loc ) , " APRStt location %c%c of 100 " , a , o ) ;
2015-07-27 01:05:48 +00:00
}
else if ( a = = ' B ' & & isdigit ( o ) ) {
2015-09-07 23:56:20 +00:00
snprintf ( A - > g_aprstt_loc , sizeof ( A - > g_aprstt_loc ) , " APRStt location %c%c... " , a , o ) ;
2015-07-27 01:05:48 +00:00
}
}
else if ( isupper ( d ) )
2015-07-27 00:35:07 +00:00
{
/*
* This adds one extra digit to each . Dao adds extra digit like :
*
* Lat : DD MM . HHa
* Lon : DDD HH . HHo
*/
if ( isdigit ( a ) ) {
2015-07-27 01:05:48 +00:00
A - > g_lat + = ( a - ' 0 ' ) / 60000.0 * sign ( A - > g_lat ) ;
2015-07-27 00:35:07 +00:00
}
if ( isdigit ( o ) ) {
2015-07-27 01:05:48 +00:00
A - > g_lon + = ( o - ' 0 ' ) / 60000.0 * sign ( A - > g_lon ) ;
2015-07-27 00:35:07 +00:00
}
}
else if ( islower ( d ) )
{
/*
* This adds almost two extra digits to each like this :
*
* Lat : DD MM . HHxx
* Lon : DDD HH . HHxx
*
2015-07-27 01:05:48 +00:00
* The original character range ' ! ' to ' { ' is first converted
2015-07-27 00:35:07 +00:00
* to an integer in range of 0 to 90. It is multiplied by 1.1
* to stretch the numeric range to be 0 to 99.
*/
2015-07-27 01:05:48 +00:00
2017-07-20 02:03:40 +00:00
/*
2022-01-02 02:55:11 +00:00
* Here are a couple situations where it is seen .
2017-07-20 02:03:40 +00:00
*
* W8SAT - 1 > T2UV0P : ` qC < 0x1f > l ! Xu \ ' " 69}WMNI EDS Response Unit #1|+/%0'n|!w:X!|3
*
* Let ' s break that down into pieces .
*
* W8SAT - 1 > T2UV0P : ` qC < 0x1f > l ! Xu \ ' " 69} MIC-E format
* N 42 56.0000 , W 085 39.0300 ,
* 0 MPH , course 160 , alt 709 ft
* WMNI EDS Response Unit # 1 comment
* | + / % 0 ' n | base 91 telemetry
* ! w : X ! DAO
* | 3 Tiny Track 3
*
* Comment earlier points out that MIC - E format has resolution of 0.01 minute ,
* same as non - compressed format , so the DAO does work out , after thinking
* about it for a while .
2022-01-02 02:55:11 +00:00
* We also find a MIC - E example with ! DAO ! here : http : //www.aprs.org/aprs12/mic-e-examples.txt
*
* Another one :
*
* KS4FUN - 12 > 3 X0PRU , W6CX - 3 , BKELEY , WIDE2 * : ` 2 ^ = l ! < 0x1c > + / ' " 48}MT-RTG|%B%p'a|!wqR!|3
*
* MIC - E , Red Cross , Special
* N 38 00.2588 , W 122 06.3354
* 0 MPH , course 100 , alt 108 ft
* MT - RTG comment
* | % B % p ' a | Seq = 397 , A1 = 443 , A2 = 610
* ! wqR ! DAO
* | 3 Byonics TinyTrack3
*
2017-07-20 02:03:40 +00:00
*/
2015-07-27 01:05:48 +00:00
/*
* The spec appears to be wrong . It says ' } ' is the maximum value when it should be ' { ' .
*/
if ( isdigit91 ( a ) ) {
A - > g_lat + = ( a - B91_MIN ) * 1.1 / 600000.0 * sign ( A - > g_lat ) ;
2015-07-27 00:35:07 +00:00
}
2015-07-27 01:05:48 +00:00
if ( isdigit91 ( o ) ) {
A - > g_lon + = ( o - B91_MIN ) * 1.1 / 600000.0 * sign ( A - > g_lon ) ;
2015-07-27 00:35:07 +00:00
}
}
2015-09-07 23:56:20 +00:00
strlcpy ( temp , A - > g_comment + match [ 0 ] . rm_eo , sizeof ( temp ) ) ;
strlcpy ( A - > g_comment + match [ 0 ] . rm_so , temp , sizeof ( A - > g_comment ) - match [ 0 ] . rm_so ) ;
2015-07-27 00:35:07 +00:00
}
/*
* Altitude in feet . / A = 123456
*/
2015-07-27 01:05:48 +00:00
if ( regexec ( & alt_re , A - > g_comment , MAXMATCH , match , 0 ) = = 0 )
2015-07-27 00:35:07 +00:00
{
//dw_printf("start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo));
2015-09-07 23:56:20 +00:00
strlcpy ( temp , A - > g_comment + match [ 0 ] . rm_eo , sizeof ( temp ) ) ;
2015-07-27 00:35:07 +00:00
2015-07-27 01:05:48 +00:00
A - > g_comment [ match [ 0 ] . rm_eo ] = ' \0 ' ;
2015-11-08 01:57:02 +00:00
A - > g_altitude_ft = atoi ( A - > g_comment + match [ 0 ] . rm_so + 3 ) ;
2015-07-27 00:35:07 +00:00
2015-09-07 23:56:20 +00:00
strlcpy ( A - > g_comment + match [ 0 ] . rm_so , temp , sizeof ( A - > g_comment ) - match [ 0 ] . rm_so ) ;
2015-07-27 00:35:07 +00:00
}
2015-07-27 01:05:48 +00:00
//dw_printf("Final comment='%s'\n", A->g_comment);
/*
* Finally look for something that looks like frequency or CTCSS tone
* in the remaining comment . Point this out and suggest the
* standardized format .
2015-09-07 23:56:20 +00:00
* Don ' t complain if we have already found a valid value .
2015-07-27 01:05:48 +00:00
*/
2015-09-07 23:56:20 +00:00
if ( A - > g_freq = = G_UNKNOWN & & regexec ( & bad_freq_re , A - > g_comment , MAXMATCH , match , 0 ) = = 0 )
2015-07-27 01:05:48 +00:00
{
char bad [ 30 ] ;
char good [ 30 ] ;
double x ;
substr_se ( bad , A - > g_comment , match [ 0 ] . rm_so , match [ 0 ] . rm_eo ) ;
x = atof ( bad ) ;
if ( ( x > = 144 & & x < = 148 ) | |
( x > = 222 & & x < = 225 ) | |
( x > = 420 & & x < = 450 ) | |
( x > = 902 & & x < = 928 ) ) {
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
2015-11-08 01:57:02 +00:00
snprintf ( good , sizeof ( good ) , " %07.3fMHz " , x ) ;
2015-07-27 01:17:23 +00:00
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " \" %s \" in comment looks like a frequency in non-standard format. \n " , bad ) ;
dw_printf ( " For most systems to recognize it, use exactly this form \" %s \" at beginning of comment. \n " , good ) ;
}
2015-07-27 01:05:48 +00:00
if ( A - > g_freq = = G_UNKNOWN ) {
A - > g_freq = x ;
}
}
}
2015-09-07 23:56:20 +00:00
if ( A - > g_tone = = G_UNKNOWN & & regexec ( & bad_tone_re , A - > g_comment , MAXMATCH , match , 0 ) = = 0 )
2015-07-27 01:05:48 +00:00
{
char bad1 [ 30 ] ; /* original 99.9 or 999.9 format or one of 67 77 100 123 */
char bad2 [ 30 ] ; /* 99.9 or 999.9 format. ".0" appended for special cases. */
char good [ 30 ] ;
int i ;
substr_se ( bad1 , A - > g_comment , match [ 2 ] . rm_so , match [ 2 ] . rm_eo ) ;
2015-09-07 23:56:20 +00:00
strlcpy ( bad2 , bad1 , sizeof ( bad2 ) ) ;
2015-07-27 01:05:48 +00:00
if ( strcmp ( bad2 , " 67 " ) = = 0 | | strcmp ( bad2 , " 77 " ) = = 0 | | strcmp ( bad2 , " 100 " ) = = 0 | | strcmp ( bad2 , " 123 " ) = = 0 ) {
2015-09-07 23:56:20 +00:00
strlcat ( bad2 , " .0 " , sizeof ( bad2 ) ) ;
2015-07-27 01:05:48 +00:00
}
// TODO: Why wasn't freq/PL recognized here?
// Should we recognize some cases of single decimal place as frequency?
//DECODED[194] N8VIM audio level = 27 [NONE]
//[0] N8VIM>BEACON,WIDE2-2:!4240.85N/07133.99W_PHG72604/ Pepperell, MA-> WX. 442.9+ PL100<0x0d>
//Didn't find wind direction in form c999.
//Didn't find wind speed in form s999.
//Didn't find wind gust in form g999.
//Didn't find temperature in form t999.
//Weather Report, WEATHER Station (blue)
//N 42 40.8500, W 071 33.9900
//, "PHG72604/ Pepperell, MA-> WX. 442.9+ PL100"
for ( i = 0 ; i < NUM_CTCSS ; i + + ) {
if ( strcmp ( s_ctcss [ i ] , bad2 ) = = 0 ) {
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
2015-09-07 23:56:20 +00:00
snprintf ( good , sizeof ( good ) , " T%03d " , i_ctcss [ i ] ) ;
2015-07-27 01:17:23 +00:00
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " \" %s \" in comment looks like it might be a CTCSS tone in non-standard format. \n " , bad1 ) ;
dw_printf ( " For most systems to recognize it, use exactly this form \" %s \" at near beginning of comment, after any frequency. \n " , good ) ;
}
2015-07-27 01:05:48 +00:00
if ( A - > g_tone = = G_UNKNOWN ) {
A - > g_tone = atof ( bad2 ) ;
}
break ;
}
}
}
if ( ( A - > g_offset = = 6000 | | A - > g_offset = = - 6000 ) & & A - > g_freq > = 144 & & A - > g_freq < = 148 ) {
2015-07-27 01:17:23 +00:00
if ( ! A - > g_quiet ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " A transmit offset of 6 MHz on the 2 meter band doesn't seem right. \n " ) ;
dw_printf ( " Each unit is 10 kHz so you should probably be using \" -060 \" or \" +060 \" \n " ) ;
}
2015-07-27 01:05:48 +00:00
}
/*
* TODO : samples in zfreq - test4 . txt
*/
2015-07-27 00:35:07 +00:00
}
/* end process_comment */
2015-07-27 01:05:48 +00:00
2015-07-27 00:35:07 +00:00
/*------------------------------------------------------------------
*
* Function : main
*
* Purpose : Main program for standalone test program .
*
* Inputs : stdin for raw data to decode .
* This is in the usual display format either from
* a TNC , findu . com , aprs . fi , etc . e . g .
*
* N1EDF - 9 > T2QT8Y , W1CLA - 1 , WIDE1 * , WIDE2 - 2 , 00000 : ` bSbl ! Mv / ` " 4%}_ <0x0d>
*
* WB2OSZ - 1 > APN383 , qAR , N1EDU - 2 : ! 4237.14 NS07120 .83 W # PHG7130Chelmsford , MA
*
2017-05-24 23:25:08 +00:00
* New for 1.5 :
*
* Also allow hexadecimal bytes for raw AX .25 or KISS . e . g .
*
* 00 82 a0 ae ae 62 60 e0 82 96 68 84 40 40 60 9 c 68 b0 ae 86 40 e0 40 ae 92 88 8 a 64 63 03 f0 3 e 45 4 d 36 34 6 e 65 2f 23 20 45 63 68 6f 6 c 69 6 e 6 b 20 31 34 35 2 e 33 31 30 2f 31 30 30 68 7 a 20 54 6f 6 e 65
*
* If it begins with 00 or C0 ( which would be impossible for AX .25 address ) process as KISS .
* Also print these formats .
2015-07-27 00:35:07 +00:00
*
* Outputs : stdout
*
* Description : Compile like this to make a standalone test program .
2019-06-30 23:46:00 +00:00
* Just run " make " and this will be built along with everything else .
* The point I ' m trying to make is that DECAMAIN must be defined
* to enable the main program here for a standalone application .
2015-07-27 00:35:07 +00:00
*
2015-11-08 01:57:02 +00:00
* gcc - o decode_aprs - DDECAMAIN decode_aprs . c ax25_pad . c . . .
2015-07-27 00:35:07 +00:00
*
* . / decode_aprs < decode_aprs . txt
*
* aprs . fi precedes raw data with a time stamp which you
* would need to remove first .
*
* cut - c26 - 999 tmp / kj4etp - 9. txt | decode_aprs . exe
*
*
* Restriction : MIC - E message type can be problematic because it
* it can use unprintable characters in the information field .
*
* Dire Wolf and aprs . fi print it in hexadecimal . Example :
*
* KB1KTR - 8 > TR3U6T , KB1KTR - 9 * , WB2OSZ - 1 * , WIDE2 * , qAR , W1XM : ` c1 < 0x1f > l ! t > / > " 4^}
* ^ ^ ^ ^ ^ ^
* | | | | | |
* What does findu . com do in this case ?
*
* ax25_from_text recognizes this representation so it can be used
* to decode raw data later .
*
2015-07-27 01:17:23 +00:00
* TODO : To make it more useful ,
* - Remove any leading timestamp .
* - Remove any " qA* " and following from the path .
2017-05-24 23:25:08 +00:00
* - Handle non - APRS frames properly .
2015-07-27 00:35:07 +00:00
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2015-11-08 01:57:02 +00:00
# if DECAMAIN
2015-07-27 00:35:07 +00:00
2017-05-24 23:25:08 +00:00
# include "kiss_frame.h"
2015-07-27 01:05:48 +00:00
/* Stub for stand-alone decoder. */
void nmea_send_waypoint ( char * wname_in , double dlat , double dlong , char symtab , char symbol ,
float alt , float course , float speed , char * comment )
{
return ;
}
2017-05-24 23:25:08 +00:00
// TODO: hex_dump is currently in server.c and we don't want to drag that in.
// Someday put it in a more reasonable place, with other general utilities, and remove the private copy here.
static void hex_dump ( unsigned char * p , int len )
{
int n , i , offset ;
offset = 0 ;
while ( len > 0 ) {
n = len < 16 ? len : 16 ;
dw_printf ( " %03x: " , offset ) ;
for ( i = 0 ; i < n ; i + + ) {
dw_printf ( " %02x " , p [ i ] ) ;
}
for ( i = n ; i < 16 ; i + + ) {
dw_printf ( " " ) ;
}
dw_printf ( " " ) ;
for ( i = 0 ; i < n ; i + + ) {
dw_printf ( " %c " , isprint ( p [ i ] ) ? p [ i ] : ' . ' ) ;
}
dw_printf ( " \n " ) ;
p + = 16 ;
offset + = 16 ;
len - = 16 ;
}
}
// Do we have two hexadecimal digits followed by whitespace or end of line?
2015-07-27 01:05:48 +00:00
2017-05-24 23:25:08 +00:00
# define ISHEX2(x) (isxdigit(x[0]) && isxdigit(x[1]) && (x[2] == '\0' || isspace(x[2])))
# define MAXLINE 9000
# define MAXBYTES 3000
2015-07-27 00:35:07 +00:00
int main ( int argc , char * argv [ ] )
{
2017-05-24 23:25:08 +00:00
char stuff [ MAXLINE ] ;
unsigned char bytes [ MAXBYTES ] ;
int num_bytes ;
2015-07-27 00:35:07 +00:00
char * p ;
packet_t pp ;
# if __WIN32__
// Select UTF-8 code page for console output.
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686036(v=vs.85).aspx
// This is the default I see for windows terminal:
// >chcp
// Active code page: 437
//Restore on exit? oldcp = GetConsoleOutputCP();
SetConsoleOutputCP ( CP_UTF8 ) ;
# else
/*
* Default on Raspian & Ubuntu Linux is fine . Don ' t know about others .
*
* Should we look at LANG environment variable and issue a warning
* if it doesn ' t look something like en_US . UTF - 8 ?
*/
# endif
if ( argc > = 2 ) {
if ( freopen ( argv [ 1 ] , " r " , stdin ) = = NULL ) {
fprintf ( stderr , " Can't open %s for read. \n " , argv [ 1 ] ) ;
exit ( 1 ) ;
}
}
2019-06-30 23:46:00 +00:00
// If you don't like the text colors, use 0 instead of 1 here.
2015-07-27 00:35:07 +00:00
text_color_init ( 1 ) ;
text_color_set ( DW_COLOR_INFO ) ;
while ( fgets ( stuff , sizeof ( stuff ) , stdin ) ! = NULL )
{
p = stuff + strlen ( stuff ) - 1 ;
while ( p > = stuff & & ( * p = = ' \r ' | | * p = = ' \n ' ) ) {
* p - - = ' \0 ' ;
}
if ( strlen ( stuff ) = = 0 | | stuff [ 0 ] = = ' # ' )
{
/* comment or blank line */
text_color_set ( DW_COLOR_INFO ) ;
dw_printf ( " %s \n " , stuff ) ;
continue ;
}
else
{
/* Try to process it. */
text_color_set ( DW_COLOR_REC ) ;
dw_printf ( " \n %s \n " , stuff ) ;
2015-07-27 01:05:48 +00:00
2017-05-24 23:25:08 +00:00
// Do we have monitor format, KISS, or AX.25 frame?
p = stuff ;
while ( isspace ( * p ) ) p + + ;
if ( ISHEX2 ( p ) ) {
// Collect a bunch of hexadecimal numbers.
num_bytes = 0 ;
while ( ISHEX2 ( p ) & & num_bytes < MAXBYTES ) {
bytes [ num_bytes + + ] = strtoul ( p , NULL , 16 ) ;
p + = 2 ;
while ( isspace ( * p ) ) p + + ;
}
if ( num_bytes = = 0 | | * p ! = ' \0 ' ) {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Parse error around column %d. \n " , ( int ) ( long ) ( p - stuff ) + 1 ) ;
dw_printf ( " Was expecting only space separated 2 digit hexadecimal numbers. \n \n " ) ;
continue ; // next line
}
// If we have 0xC0 at start, remove it and expect same at end.
if ( bytes [ 0 ] = = FEND ) {
2018-01-02 02:38:50 +00:00
if ( num_bytes < 2 | | bytes [ 1 ] ! = 0 ) {
2017-05-24 23:25:08 +00:00
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Was expecting to find 00 after the initial C0. \n " ) ;
continue ;
}
if ( bytes [ num_bytes - 1 ] = = FEND ) {
text_color_set ( DW_COLOR_INFO ) ;
dw_printf ( " Removing KISS FEND characters at beginning and end. \n " ) ;
int n ;
for ( n = 0 ; n < num_bytes - 1 ; n + + ) {
bytes [ n ] = bytes [ n + 1 ] ;
}
num_bytes - = 2 ;
}
else {
text_color_set ( DW_COLOR_INFO ) ;
dw_printf ( " Removing KISS FEND character at beginning. Was expecting another at end. \n " ) ;
int n ;
for ( n = 0 ; n < num_bytes - 1 ; n + + ) {
bytes [ n ] = bytes [ n + 1 ] ;
}
num_bytes - = 1 ;
}
}
2015-07-27 01:05:48 +00:00
2017-05-24 23:25:08 +00:00
if ( bytes [ 0 ] = = 0 ) {
2015-07-27 01:05:48 +00:00
2017-05-24 23:25:08 +00:00
// Treat as KISS. Undo any KISS encoding.
2015-11-18 13:09:45 +00:00
2017-05-24 23:25:08 +00:00
unsigned char kiss_frame [ MAXBYTES ] ;
int kiss_len = num_bytes ;
2015-07-27 01:05:48 +00:00
2017-05-24 23:25:08 +00:00
memcpy ( kiss_frame , bytes , num_bytes ) ;
2015-07-27 01:05:48 +00:00
2017-05-24 23:25:08 +00:00
text_color_set ( DW_COLOR_DEBUG ) ;
dw_printf ( " --- KISS frame --- \n " ) ;
hex_dump ( kiss_frame , kiss_len ) ;
// Put FEND at end to keep kiss_unwrap happy.
2021-09-19 18:51:18 +00:00
// Having one at the beginning is optional.
2017-05-24 23:25:08 +00:00
kiss_frame [ kiss_len + + ] = FEND ;
// In the more general case, we would need to include
// the command byte because it could be escaped.
// Here we know it is 0, so we take a short cut and
// remove it before, rather than after, the conversion.
num_bytes = kiss_unwrap ( kiss_frame + 1 , kiss_len - 1 , bytes ) ;
}
// Treat as AX.25.
alevel_t alevel ;
memset ( & alevel , 0 , sizeof ( alevel ) ) ;
pp = ax25_from_frame ( bytes , num_bytes , alevel ) ;
if ( pp ! = NULL ) {
char addrs [ 120 ] ;
unsigned char * pinfo ;
int info_len ;
decode_aprs_t A ;
text_color_set ( DW_COLOR_DEBUG ) ;
dw_printf ( " --- AX.25 frame --- \n " ) ;
ax25_hex_dump ( pp ) ;
dw_printf ( " ------------------- \n " ) ;
ax25_format_addrs ( pp , addrs ) ;
text_color_set ( DW_COLOR_DECODED ) ;
2017-05-26 23:49:44 +00:00
dw_printf ( " %s " , addrs ) ;
2017-05-24 23:25:08 +00:00
info_len = ax25_get_info ( pp , & pinfo ) ;
ax25_safe_print ( ( char * ) pinfo , info_len , 1 ) ; // Display non-ASCII to hexadecimal.
dw_printf ( " \n " ) ;
2022-02-25 22:29:07 +00:00
decode_aprs ( & A , pp , 0 , 0 ) ; // Extract information into structure.
2017-05-24 23:25:08 +00:00
decode_aprs_print ( & A ) ; // Now print it in human readable format.
( void ) ax25_check_addresses ( pp ) ; // Errors for invalid addresses.
ax25_delete ( pp ) ;
}
else {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " Could not construct AX.25 frame from bytes supplied! \n \n " ) ;
}
}
else {
// Normal monitoring format.
pp = ax25_from_text ( stuff , 1 ) ;
if ( pp ! = NULL ) {
decode_aprs_t A ;
2022-02-25 22:29:07 +00:00
decode_aprs ( & A , pp , 0 , 0 ) ; // Extract information into structure.
2017-05-24 23:25:08 +00:00
decode_aprs_print ( & A ) ; // Now print it in human readable format.
2017-07-21 02:04:23 +00:00
// This seems to be redundant because we used strict option
// when parsing the monitoring format text.
//(void)ax25_check_addresses(pp); // Errors for invalid addresses.
2017-05-24 23:25:08 +00:00
// Future? Add -d option to include hex dump and maybe KISS?
ax25_delete ( pp ) ;
}
else {
text_color_set ( DW_COLOR_ERROR ) ;
dw_printf ( " ERROR - Could not parse monitoring format input! \n \n " ) ;
}
2015-07-27 00:35:07 +00:00
}
}
}
return ( 0 ) ;
}
2015-11-08 01:57:02 +00:00
# endif /* DECAMAIN */
2015-07-27 00:35:07 +00:00
/* end decode_aprs.c */