/*
*
* This file is part of Maemo Mapper.
*
* Maemo Mapper 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 3 of the License, or
* (at your option) any later version.
*
* Maemo Mapper 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 Maemo Mapper. If not, see .
*
*
* Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
*
* * XASTIR, Amateur Station Tracking and Information Reporting
* Copyright (C) 1999,2000 Frank Giannandrea
* Copyright (C) 2000-2007 The Xastir Group
*
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef INCLUDE_APRS
#include "aprs_decode.h"
#include "data.h"
#include "aprs.h"
#include "types.h"
#include "math.h"
#include
#include
#include
#include "hashtable.h"
#include "poi.h"
#include "display.h"
#include "aprs_message.h"
//#include "aprs_weather.h"
#define WX_TYPE 'X'
float gust[60]; // High wind gust for each min. of last hour
int gust_write_ptr = 0;
int gust_read_ptr = 0;
int gust_last_write = 0;
float rain_base[24]; // hundredths of an inch
float rain_minute[60]; // Total rain for each min. of last hour, hundredths of an inch
int rain_check = 0; // Flag for re-checking rain_total each hour
int redraw_on_new_data; // Station redraw request
int wait_to_redraw; /* wait to redraw until system is up */
time_t posit_next_time;
/////////////
float rain_minute_total = 0.0; // Total for last hour, hundredths of an inch
int rain_minute_last_write = -1; // Write pointer for rain_minute[] array, set to an invalid number
float rain_00 = 0.0; // hundredths of an inch
float rain_24 = 0.0; // hundredths of an inch
int station_count; // number of stored stations
int station_count_save = 0; // old copy of above
AprsDataRow *n_first; // pointer to first element in name sorted station list
AprsDataRow *n_last; // pointer to last element in name sorted station list
AprsDataRow *t_oldest; // pointer to first element in time sorted station list (oldest)
AprsDataRow *t_newest; // pointer to last element in time sorted station list (newest)
time_t last_station_remove; // last time we did a check for station removing
time_t last_sec,curr_sec; // for comparing if seconds in time have changed
int next_time_sn; // time serial number for unique time index
int stations_heard = 0;
////////////
double cvt_m2len = 3.28084; // m to ft // from meter
double cvt_kn2len = 1.0; // knots to knots; // from knots
double cvt_mi2len = 0.8689607; // mph to knots / mi to nm; // from miles
double cvt_dm2len = 0.328084; // dm to ft // from decimeter
double cvt_hm2len = 0.0539957; // hm to nm; // from hectometer
int emergency_distance_check = 1;
float emergency_range = 280.0; // Default is 4hrs @ 70mph distance
int decoration_offset_x = 0;
int decoration_offset_y = 0;
int last_station_info_x = 0;
int last_station_info_y = 0;
int fcc_lookup_pushed = 0;
int rac_lookup_pushed = 0;
time_t last_object_check = 0; // Used to determine when to re-transmit objects/items
time_t last_emergency_time = 0;
char last_emergency_callsign[MAX_CALLSIGN+1];
int st_direct_timeout = 60 * 60; // 60 minutes.
// Used in search_station_name() function. Shortcuts into the
// station list based on the least-significant 7 bits of the first
// two letters of the callsign/object name.
AprsDataRow *station_shortcuts[16384];
// calculate every half hour, display in status line every 5 minutes
#define ALOHA_CALC_INTERVAL 1800
#define ALOHA_STATUS_INTERVAL 300
int process_emergency_packet_again = 0;
////////////
int altnet;
char altnet_call[MAX_CALLSIGN+1];
static struct hashtable *tactical_hash = NULL;
char echo_digis[6][MAX_CALLSIGN+1];
int tracked_stations = 0; // A count variable used in debug code only
int trail_segment_distance; // Segment missing if greater distance
int trail_segment_time; // Segment missing if above this time (mins)
int skip_dupe_checking;
void station_shortcuts_update_function(int hash_key_in, AprsDataRow *p_rem);
void delete_station_memory(AprsDataRow *p_del);
void decode_Peet_Bros(int from, unsigned char *data, AprsWeatherRow *weather, int type);
void decode_U2000_P(int from, unsigned char *data, AprsWeatherRow *weather);
void decode_U2000_L(int from, unsigned char *data, AprsWeatherRow *weather);
void init_weather(AprsWeatherRow *weather) { // clear weather data
weather->wx_sec_time = (time_t)0;
weather->wx_storm = 0;
weather->wx_time[0] = '\0';
weather->wx_course[0] = '\0';
weather->wx_speed[0] = '\0';
weather->wx_speed_sec_time = 0; // ??
weather->wx_gust[0] = '\0';
weather->wx_hurricane_radius[0] = '\0';
weather->wx_trop_storm_radius[0] = '\0';
weather->wx_whole_gale_radius[0] = '\0';
weather->wx_temp[0] = '\0';
weather->wx_rain[0] = '\0';
weather->wx_rain_total[0] = '\0';
weather->wx_snow[0] = '\0';
weather->wx_prec_24[0] = '\0';
weather->wx_prec_00[0] = '\0';
weather->wx_hum[0] = '\0';
weather->wx_baro[0] = '\0';
weather->wx_fuel_temp[0] = '\0';
weather->wx_fuel_moisture[0] = '\0';
weather->wx_type = '\0';
weather->wx_station[0] = '\0';
}
int tactical_keys_equal(void *key1, void *key2) {
if (strlen((char *)key1) == strlen((char *)key2)
&& strncmp((char *)key1,(char *)key2,strlen((char *)key1))==0) {
return(1);
}
else {
return(0);
}
}
// Multiply all the characters in the callsign, truncated to
// TACTICAL_HASH_SIZE
//
unsigned int tactical_hash_from_key(void *key) {
unsigned char *jj = key;
unsigned int tac_hash = 1;
while (*jj != '\0') {
tac_hash = tac_hash * (unsigned int)*jj++;
}
tac_hash = tac_hash % TACTICAL_HASH_SIZE;
return (tac_hash);
}
/* valid characters for APRS weather data fields */
int is_aprs_chr(char ch) {
if (g_ascii_isdigit(ch) || ch==' ' || ch=='.' || ch=='-')
return(1);
else
return(0);
}
//
// Search station record by callsign
// Returns a station with a call equal or after the searched one
//
// We use a doubly-linked list for the stations, so we can traverse
// in either direction. We also use a 14-bit hash table created
// from the first two letters of the call to dump us into the
// beginning of the correct area that may hold the callsign, which
// reduces search time quite a bit. We end up doing a linear search
// only through a small area of the linked list.
//
// DK7IN: I don't look at case, objects and internet names could
// have lower case.
//
int search_station_name(AprsDataRow **p_name, char *call, int exact) {
int kk;
int hash_key;
int result;
int ok = 1;
(*p_name) = n_first; // start of alphabet
if (call[0] == '\0') {
// If call we're searching for is empty, return n_first as
// the pointer.
return(0);
}
// We create the hash key out of the lower 7 bits of the first
// two characters, creating a 14-bit key (1 of 16384)
//
hash_key = (int)((call[0] & 0x7f) << 7);
hash_key = hash_key | (int)(call[1] & 0x7f);
// Look for a match using hash table lookup
//
(*p_name) = station_shortcuts[hash_key];
if ((*p_name) == NULL) { // No hash-table entry found.
int mm;
// No index found for that letter. Walk the array until
// we find an entry that is filled. That'll be our
// potential insertion point (insertion into the list will
// occur just ahead of the hash entry).
for (mm = hash_key+1; mm < 16384; mm++) {
if (station_shortcuts[mm] != NULL)
{
(*p_name) = station_shortcuts[mm];
break;
}
}
}
// If we got to this point, we either have a NULL pointer or a
// real hash-table pointer entry. A non-NULL pointer means that
// we have a match for the lower seven bits of the first two
// characters of the callsign. Check the rest of the callsign,
// and jump out of the loop if we get outside the linear search
// area (if first two chars are different).
kk = (int)strlen(call);
// Search linearly through list. Stop at end of list or break.
while ( (*p_name) != NULL) {
if (exact) {
// Check entire string for exact match
result = strcmp( call, (*p_name)->call_sign );
}
else {
// Check first part of string for match
result = strncmp( call, (*p_name)->call_sign, kk );
}
if (result < 0) { // We went past the right location.
// We're done.
ok = 0;
break;
}
else if (result == 0) { // Found a possible match
break;
}
else { // Result > 0. We haven't found it yet.
(*p_name) = (*p_name)->n_next; // Next element in list
}
}
// Did we find anything?
if ( (*p_name) == NULL) {
ok = 0;
return(ok); // Nope. No match found.
}
// If "exact" is set, check that the string lengths match as
// well. If not, we didn't find it.
if (exact && ok && strlen((*p_name)->call_sign) != strlen(call))
ok = 0;
return(ok); // if not ok: p_name points to correct insert position in name list
}
/*
* Check if current packet is a delayed echo
*/
int is_trailpoint_echo(AprsDataRow *p_station) {
int packets = 1;
time_t checktime;
AprsTrackRow *ptr;
// Check whether we're to skip checking for dupes (reading in
// objects/items from file is one such case).
//
if (skip_dupe_checking) {
return(0); // Say that it isn't an echo
}
// Start at newest end of linked list and compare. Return if we're
// beyond the checktime.
ptr = p_station->newest_trackpoint;
if (ptr == NULL)
return(0); // first point couldn't be an echo
checktime = p_station->sec_heard - TRAIL_ECHO_TIME*60;
while (ptr != NULL) {
if (ptr->sec < checktime)
return(0); // outside time frame, no echo found
if ((p_station->coord_lon == ptr->trail_long_pos)
&& (p_station->coord_lat == ptr->trail_lat_pos)
&& (p_station->speed == '\0' || ptr->speed < 0
|| (long)(atof(p_station->speed)*18.52) == ptr->speed)
// current: char knots, trail: long 0.1m (-1 is undef)
&& (p_station->course == '\0' || ptr->course <= 0
|| atoi(p_station->course) == ptr->course)
// current: char, trail: int (-1 is undef)
&& (p_station->altitude == '\0' || ptr->altitude <= -99999l
|| atoi(p_station->altitude)*10 == ptr->altitude)) {
// current: char, trail: int (-99999l is undef)
return(1); // we found a delayed echo
}
ptr = ptr->prev;
packets++;
}
return(0); // no echo found
}
/*
* Keep track of last six digis that echo my transmission
*/
void upd_echo(char *path) {
int i,j,len;
if (echo_digis[5][0] != '\0') {
for (i=0;i<5;i++) {
xastir_snprintf(echo_digis[i],
MAX_CALLSIGN+1,
"%s",
echo_digis[i+1]);
}
echo_digis[5][0] = '\0';
}
for (i=0,j=0;i < (int)strlen(path);i++) {
if (path[i] == '*')
break;
if (path[i] == ',')
j=i;
}
if (j > 0)
j++; // first char of call
if (i > 0 && i-j <= 9) {
len = i-j;
for (i=0;i<5;i++) { // look for free entry
if (echo_digis[i][0] == '\0')
break;
}
substr(echo_digis[i],path+j,len);
}
}
//
// Store one trail point. Allocate storage for the new data.
//
// We now store track data in a doubly-linked list. Each record has a
// pointer to the previous and the next record in the list. The main
// station record has a pointer to the oldest and the newest end of the
// chain, and the chain can be traversed in either order.
//
int store_trail_point(AprsDataRow *p_station,
long lon,
long lat,
time_t sec,
char *alt,
char *speed,
char *course,
short stn_flag) {
// TODO - trails are currently disabled
// This seems to fall over!!!
return 1;
char flag;
AprsTrackRow *ptr;
// Allocate storage for the new track point
ptr = malloc(sizeof(AprsTrackRow));
if (ptr == NULL) {
return(0); // Failed due to malloc
}
// Check whether we have any track data saved
if (p_station->newest_trackpoint == NULL) {
// new trail, do initialization
tracked_stations++;
// Assign a new trail color 'cuz it's a new trail
p_station->trail_color = new_trail_color(p_station->call_sign);
}
// Start linking the record to the new end of the chain
ptr->prev = p_station->newest_trackpoint; // Link to record or NULL
ptr->next = NULL; // Newest end of chain
// Have an older record already?
if (p_station->newest_trackpoint != NULL) { // Yes
p_station->newest_trackpoint->next = ptr;
}
else { // No, this is our first record
p_station->oldest_trackpoint = ptr;
}
// Link it in as our newest record
p_station->newest_trackpoint = ptr;
ptr->trail_long_pos = lon;
ptr->trail_lat_pos = lat;
ptr->sec = sec;
if (alt[0] != '\0')
ptr->altitude = atoi(alt)*10;
else
ptr->altitude = -99999l;
if (speed[0] != '\0')
ptr->speed = (long)(atof(speed)*18.52);
else
ptr->speed = -1;
if (course[0] != '\0')
ptr->course = (int)(atof(course) + 0.5); // Poor man's rounding
else
ptr->course = -1;
flag = '\0'; // init flags
if ((stn_flag & ST_DIRECT) != 0)
flag |= TR_LOCAL; // set "local" flag
if (ptr->prev != NULL) { // we have at least two points...
// Check whether distance between points is too far. We
// must convert from degrees to the Xastir coordinate system
// units, which are 100th of a second.
if ( abs(lon - ptr->prev->trail_long_pos) > (trail_segment_distance * 60*60*100) ||
abs(lat - ptr->prev->trail_lat_pos) > (trail_segment_distance * 60*60*100) ) {
// Set "new track" flag if there's
// "trail_segment_distance" degrees or more between
// points. Originally was hard-coded to one degree, now
// set by a slider in the timing dialog.
flag |= TR_NEWTRK;
}
else {
// Check whether trail went above our maximum time
// between points. If so, don't draw segment.
if (abs(sec - ptr->prev->sec) > (trail_segment_time *60)) {
// Set "new track" flag if long delay between
// reception of two points. Time is set by a slider
// in the timing dialog.
flag |= TR_NEWTRK;
}
}
// Since we have more then 1 previous track point, ensure we don't go over the configured max
// (_aprs_std_pos_hist)
AprsTrackRow *ptr_tmp = ptr;
AprsTrackRow *ptr_tmp_previous = ptr;
gint trackCount = 0;
while( (ptr_tmp = ptr_tmp->prev) )
{
if(trackCount > _aprs_std_pos_hist)
{
fprintf(stderr, "DEBUG: Deleting old track point\n");
// Remove track
ptr_tmp->prev->next = ptr_tmp->next;
if(ptr_tmp->next)
{
ptr_tmp->next->prev = ptr_tmp->prev;
}
ptr_tmp_previous = ptr_tmp->prev;
free( ptr_tmp);
ptr_tmp = ptr_tmp_previous;
}
else
{
trackCount ++;
}
ptr_tmp = ptr_tmp->prev;
}
}
else {
// Set "new track" flag for first point received.
flag |= TR_NEWTRK;
}
ptr->flag = flag;
return(1); // We succeeded
}
/*
* Substring function WITH a terminating NULL char, needs a string of at least size+1
*/
void substr(char *dest, char *src, int size)
{
snprintf(dest, size+1, "%s", src);
}
/*
* Remove element from name ordered list
*/
void remove_name(AprsDataRow *p_rem) { // todo: return pointer to next element
int update_shortcuts = 0;
int hash_key; // We use a 14-bit hash key
// Do a quick check to see if we're removing a station record
// that is pointed to by our pointer shortcuts array.
// If so, update our pointer shortcuts after we're done.
//
// We create the hash key out of the lower 7 bits of the first
// two characters, creating a 14-bit key (1 of 16384)
//
hash_key = (int)((p_rem->call_sign[0] & 0x7f) << 7);
hash_key = hash_key | (int)(p_rem->call_sign[1] & 0x7f);
if (station_shortcuts[hash_key] == p_rem) {
// Yes, we're trying to remove a record that a hash key
// directly points to. We'll need to redo that hash key
// after we remove the record.
update_shortcuts++;
}
// Proceed to the station record removal
//
if (p_rem->n_prev == NULL) { // Appears to be first element in list
if (n_first == p_rem) { // Yes, head of list
// Make list head point to 2nd element in list (or NULL)
// so that we can delete the current record.
n_first = p_rem->n_next;
}
else { // No, not first element in list. Problem! The
// list pointers are inconsistent for some reason.
// The chain has been broken and we have dangling
// pointers.
fprintf(stderr,
"remove_name(): ERROR: p->n_prev == NULL but p != n_first\n");
abort(); // Cause a core dump at this point
// Perhaps we could do some repair to the list pointers here? Start
// at the other end of the chain and navigate back to this end, then
// fix up n_first to point to it? This is at the risk of a memory
// leak, but at least Xastir might continue to run.
}
}
else { // Not the first element in the list. Fix up pointers
// to skip the current record.
p_rem->n_prev->n_next = p_rem->n_next;
}
if (p_rem->n_next == NULL) { // Appears to be last element in list
if (n_last == p_rem) { // Yes, tail of list
// Make list tail point to previous element in list (or
// NULL) so that we can delete the current record.
n_last = p_rem->n_prev;
}
else { // No, not last element in list. Problem! The list
// pointers are inconsistent for some reason. The
// chain has been broken and we have dangling
// pointers.
fprintf(stderr,
"remove_name(): ERROR: p->n_next == NULL but p != n_last\n");
abort(); // Cause a core dump at this point
// Perhaps we could do some repair to the list pointers here? Start
// at the other end of the chain and navigate back to this end, then
// fix up n_last to point to it? This is at the risk of a memory
// leak, but at least Xastir might continue to run.
}
}
else { // Not the last element in the list. Fix up pointers to
// skip the current record.
p_rem->n_next->n_prev = p_rem->n_prev;
}
// Update our pointer shortcuts. Pass the removed hash_key to
// the function so that we can try to redo just that hash_key
// pointer.
if (update_shortcuts) {
//fprintf(stderr,"\t\t\t\t\t\tRemoval of hash key: %i\n", hash_key);
// The -1 tells the function to redo all of the hash table
// pointers because we deleted one of them. Later we could
// optimize this so that only the specific pointer is fixed
// up.
station_shortcuts_update_function(-1, NULL);
}
}
/*
* Station Capabilities, Queries and Responses [APRS Reference, chapter 15]
*/
//
// According to Bob Bruninga we should wait a random time between 0
// and 120 seconds before responding to a general query. We use the
// delayed-ack mechanism to add this randomness.
//
// NOTE: We may end up sending these to RF when the query came in
// over the internet. We should check that.
//
int process_query( /*@unused@*/ char *call_sign, /*@unused@*/ char *path,char *message,TAprsPort port, /*@unused@*/ int third_party) {
char temp[100];
int ok = 0;
float randomize;
// Generate a random number between 0.0 and 1.0
randomize = rand() / (float)RAND_MAX;
// Convert to between 0 and 120 seconds
randomize = randomize * 120.0;
// Check for proper usage of the ?APRS? query
//
// NOTE: We need to add support in here for the radius circle as
// listed in the spec for general queries. Right now we respond to
// all queries, whether we're inside the circle or not. Spec says
// this:
//
// ?Query?Lat,Long,Radius
// 1 n 1 n 1 n 1 4 Bytes
//
// i.e. ?APRS? 34.02,-117.15,0200
//
// Note leading space in latitude as its value is positive.
// Lat/long are floating point degrees. N/E are positive, indicated
// by a leading space. S/W are negative. Radius is in miles
// expressed as a fixed 4-digit number in whole miles. All stations
// inside the specified circle should respond with a position report
// and a status report.
//
if (!ok && strncmp(message,"APRS?",5)==0) {
//
// Initiate a delayed transmit of our own posit.
// UpdateTime() uses posit_next_time to decide when to
// transmit, so we'll just muck with that.
//
if ( posit_next_time - sec_now() < randomize ) {
// Skip setting it, as we'll transmit soon anyway
}
else {
posit_next_time = (size_t)(sec_now() + randomize);
}
ok = 1;
}
// Check for illegal case for the ?APRS? query
if (!ok && g_strncasecmp(message,"APRS?",5)==0) {
ok = 1;
// fprintf(stderr,
// "%s just queried us with an illegal query: %s\n",
// call_sign,
// message),
// fprintf(stderr,
// "Consider sending a message, asking them to follow the spec\n");
}
// Check for proper usage of the ?WX? query
if (!ok && strncmp(message,"WX?",3)==0) {
// WX is not supported
ok = 1;
}
// Check for illegal case for the ?WX? query
if (!ok && g_strncasecmp(message,"WX?",3)==0) {
ok = 1;
// fprintf(stderr,
// "%s just queried us with an illegal query: %s\n",
// call_sign,
// message),
// fprintf(stderr,
// "Consider sending a message, asking them to follow the spec\n");
}
return(ok);
}
/*
* Get new trail color for a call
*/
int new_trail_color(char *call) {
// TODO - stub
return(0);
}
/*
* Insert existing element into time ordered list before p_time
* The p_new record ends up being on the "older" side of p_time when
* all done inserting (closer in the list to the t_oldest pointer).
* If p_time == NULL, insert at newest end of list.
*/
void insert_time(AprsDataRow *p_new, AprsDataRow *p_time) {
// Set up pointer to next record (or NULL), sorted by time
p_new->t_newer = p_time;
if (p_time == NULL) { // add to end of list (becomes newest station)
p_new->t_older = t_newest; // connect to previous end of list
if (t_newest == NULL) // if list empty, create list
t_oldest = p_new; // it's now our only station on the list
else
t_newest->t_newer = p_new; // list not empty, link original last record to our new one
t_newest = p_new; // end of list (newest record pointer) points to our new record
}
else { // Else we're inserting into the middle of the list somewhere
p_new->t_older = p_time->t_older;
if (p_time->t_older == NULL) // add to end of list (new record becomes oldest station)
t_oldest = p_new;
else
p_time->t_older->t_newer = p_new; // else
p_time->t_older = p_new;
}
// TODO - this may need implementing?
/*
if(_aprs_max_stations > 0 && stations_heard > _aprs_max_stations)
{
// Delete the oldest
if(t_oldest != NULL)
{
// fprintf(stderr, "DEBUG: oldest station deleted\n");
delete_station_memory(t_oldest);
}
else
{
stations_heard++;
}
}
else
{
// fprintf(stderr, "DEBUG: Stations in memory: %d\n", stations_heard);
stations_heard++; // Should really be done in the new station method
}
*/
}
/*
* Remove element from time ordered list
*/
void remove_time(AprsDataRow *p_rem) { // todo: return pointer to next element
if (p_rem->t_older == NULL) { // Appears to be first element in list
if (t_oldest == p_rem) { // Yes, head of list (oldest)
// Make oldest list head point to 2nd element in list (or NULL)
// so that we can delete the current record.
t_oldest = p_rem->t_newer;
}
else { // No, not first (oldest) element in list. Problem!
// The list pointers are inconsistent for some
// reason. The chain has been broken and we have
// dangling pointers.
fprintf(stderr,
"remove_time(): ERROR: p->t_older == NULL but p != t_oldest\n");
abort(); // Cause a core dump at this point
// Perhaps we could do some repair to the list pointers here? Start
// at the other end of the chain and navigate back to this end, then
// fix up t_oldest to point to it? This is at the risk of a memory
// leak, but at least Xastir might continue to run.
}
}
else { // Not the first (oldest) element in the list. Fix up
// pointers to skip the current record.
p_rem->t_older->t_newer = p_rem->t_newer;
}
if (p_rem->t_newer == NULL) { // Appears to be last (newest) element in list
if (t_newest == p_rem) { // Yes, head of list (newest)
// Make newest list head point to previous element in
// list (or NULL) so that we can delete the current
// record.
t_newest = p_rem->t_older;
}
else { // No, not newest element in list. Problem! The
// list pointers are inconsistent for some reason.
// The chain has been broken and we have dangling
// pointers.
fprintf(stderr,
"remove_time(): ERROR: p->t_newer == NULL but p != t_newest\n");
abort(); // Cause a core dump at this point
// Perhaps we could do some repair to the list pointers here? Start
// at the other end of the chain and navigate back to this end, then
// fix up t_newest to point to it? This is at the risk of a memory
// leak, but at least Xastir might continue to run.
}
}
else { // Not the newest element in the list. Fix up pointers
// to skip the current record.
p_rem->t_newer->t_older = p_rem->t_older;
}
stations_heard--;
}
/*
* Move station record before p_time in time ordered list
*/
void move_station_time(AprsDataRow *p_curr, AprsDataRow *p_time) {
if (p_curr != NULL) { // need a valid record
remove_time(p_curr);
insert_time(p_curr,p_time);
}
}
/*
* Extract powergain and/or range from APRS info field:
* "PHG1234/", "PHG1234", or "RNG1234" from APRS data extension.
*/
int extract_powergain_range(char *info, char *phgd) {
int i,found,len;
char *info2;
//fprintf(stderr,"Info:%s\n",info);
// Check whether two strings of interest are present and snag a
// pointer to them.
info2 = strstr(info,"RNG");
if (!info2)
info2 = strstr(info,"PHG");
if (!info2) {
phgd[0] = '\0';
return(0);
}
found=0;
len = (int)strlen(info2);
if (len >= 9 && strncmp(info2,"PHG",3)==0
&& info2[7]=='/'
&& info2[8]!='A' // trailing '/' not defined in Reference...
&& g_ascii_isdigit(info2[3])
&& g_ascii_isdigit(info2[4])
&& g_ascii_isdigit(info2[5])
&& g_ascii_isdigit(info2[6])) {
substr(phgd,info2,7);
found = 1;
for (i=0;i<=len-8;i++) // delete powergain from data extension field
info2[i] = info2[i+8];
}
else {
if (len >= 7 && strncmp(info2,"PHG",3)==0
&& g_ascii_isdigit(info2[3])
&& g_ascii_isdigit(info2[4])
&& g_ascii_isdigit(info2[5])
&& g_ascii_isdigit(info2[6])) {
substr(phgd,info2,7);
found = 1;
for (i=0;i<=len-7;i++) // delete powergain from data extension field
info2[i] = info2[i+7];
}
else if (len >= 7 && strncmp(info2,"RNG",3)==0
&& g_ascii_isdigit(info2[3])
&& g_ascii_isdigit(info2[4])
&& g_ascii_isdigit(info2[5])
&& g_ascii_isdigit(info2[6])) {
substr(phgd,info2,7);
found = 1;
for (i=0;i<=len-7;i++) // delete powergain from data extension field
info2[i] = info2[i+7];
}
else {
phgd[0] = '\0';
}
}
return(found);
}
/*
* Extract omnidf from APRS info field "DFS1234/" from APRS data extension
*/
int extract_omnidf(char *info, char *phgd) {
int i,found,len;
found=0;
len = (int)strlen(info);
if (len >= 8 && strncmp(info,"DFS",3)==0 && info[7]=='/' // trailing '/' not defined in Reference...
&& g_ascii_isdigit(info[3]) && g_ascii_isdigit(info[5]) && g_ascii_isdigit(info[6])) {
substr(phgd,info,7);
for (i=0;i<=len-8;i++) // delete omnidf from data extension field
info[i] = info[i+8];
return(1);
}
else {
phgd[0] = '\0';
return(0);
}
}
//
// Extract speed and/or course from beginning of info field
//
// Returns course in degrees, speed in KNOTS.
//
int extract_speed_course(char *info, char *speed, char *course) {
int i,found,len;
len = (int)strlen(info);
found = 0;
if (len >= 7) {
found = 1;
for(i=0; found && i<7; i++) { // check data format
if (i==3) { // check separator
if (info[i]!='/')
found = 0;
}
else {
if( !( g_ascii_isdigit(info[i])
|| (info[i] == ' ') // Spaces and periods are allowed. Need these
|| (info[i] == '.') ) ) // here so that we can get the field deleted
found = 0;
}
}
}
if (found) {
substr(course,info,3);
substr(speed,info+4,3);
for (i=0;i<=len-7;i++) // delete speed/course from info field
info[i] = info[i+7];
}
if (!found || atoi(course) < 1) { // course 0 means undefined
// speed[0] ='\0'; // Don't do this! We can have a valid
// speed without a valid course.
course[0]='\0';
}
else { // recheck data format looking for undefined fields
for(i=0; i<2; i++) {
if( !(g_ascii_isdigit(speed[i]) ) )
speed[0] = '\0';
if( !(g_ascii_isdigit(course[i]) ) )
course[0] = '\0';
}
}
return(found);
}
/*
* Extract Area Object
*/
void extract_area(AprsDataRow *p_station, char *data) {
int i, val, len;
unsigned int uval;
AprsAreaObject temp_area;
/* NOTE: If we are here, the symbol was the area symbol. But if this
is a slightly corrupted packet, we shouldn't blow away the area info
for this station, since it could be from a previously received good
packet. So we will work on temp_area and only copy to p_station at
the end, returning on any error as we parse. N7TAP */
//fprintf(stderr,"Area Data: %s\n", data);
len = (int)strlen(data);
val = data[0] - '0';
if (val >= 0 && val <= AREA_MAX) {
temp_area.type = val;
val = data[4] - '0';
if (data[3] == '/') {
if (val >=0 && val <= 9) {
temp_area.color = val;
}
else {
return;
}
}
else if (data[3] == '1') {
if (val >=0 && val <= 5) {
temp_area.color = 10 + val;
}
else {
return;
}
}
val = 0;
if (isdigit((int)data[1]) && isdigit((int)data[2])) {
val = (10 * (data[1] - '0')) + (data[2] - '0');
}
else {
return;
}
temp_area.sqrt_lat_off = val;
val = 0;
if (isdigit((int)data[5]) && isdigit((int)data[6])) {
val = (10 * (data[5] - '0')) + (data[6] - '0');
}
else {
return;
}
temp_area.sqrt_lon_off = val;
for (i = 0; i <= len-7; i++) // delete area object from data extension field
data[i] = data[i+7];
len -= 7;
if (temp_area.type == AREA_LINE_RIGHT || temp_area.type == AREA_LINE_LEFT) {
if (data[0] == '{') {
if (sscanf(data, "{%u}", &uval) == 1) {
temp_area.corridor_width = uval & 0xffff;
for (i = 0; i <= len; i++)
if (data[i] == '}')
break;
uval = i+1;
for (i = 0; i <= (int)(len-uval); i++)
data[i] = data[i+uval]; // delete corridor width
}
else {
temp_area.corridor_width = 0;
return;
}
}
else {
temp_area.corridor_width = 0;
}
}
else {
temp_area.corridor_width = 0;
}
}
else {
return;
}
memcpy(&(p_station->aprs_symbol.area_object), &temp_area, sizeof(AprsAreaObject));
}
/*
* Extract probability_max data from APRS info field: "Pmax1.23,"
* Please note the ending comma. We use it to delimit the field.
*/
int extract_probability_max(char *info, char *prob_max, int prob_max_size) {
int len,done;
char *c;
char *d;
len = (int)strlen(info);
if (len < 6) { // Too short
prob_max[0] = '\0';
return(0);
}
c = strstr(info,"Pmax");
if (c == NULL) { // Pmax not found
prob_max[0] = '\0';
return(0);
}
c = c+4; // Skip the Pmax part
// Find the ending comma
d = c;
done = 0;
while (!done) {
if (*d == ',') { // We're done
done++;
}
else {
d++;
}
// Check for string too long
if ( ((d-c) > 10) && !done) { // Something is wrong, we should be done by now
prob_max[0] = '\0';
return(0);
}
}
// Copy the substring across
snprintf(prob_max,
prob_max_size,
"%s",
c);
prob_max[d-c] = '\0';
prob_max[10] = '\0'; // Just to make sure
// Delete data from data extension field
d++; // Skip the comma
done = 0;
while (!done) {
*(c-4) = *d;
if (*d == '\0')
done++;
c++;
d++;
}
return(1);
}
/*
* Extract probability_min data from APRS info field: "Pmin1.23,"
* Please note the ending comma. We use it to delimit the field.
*/
int extract_probability_min(char *info, char *prob_min, int prob_min_size) {
int len,done;
char *c;
char *d;
len = (int)strlen(info);
if (len < 6) { // Too short
prob_min[0] = '\0';
return(0);
}
c = strstr(info,"Pmin");
if (c == NULL) { // Pmin not found
prob_min[0] = '\0';
return(0);
}
c = c+4; // Skip the Pmin part
// Find the ending comma
d = c;
done = 0;
while (!done) {
if (*d == ',') { // We're done
done++;
}
else {
d++;
}
// Check for string too long
if ( ((d-c) > 10) && !done) { // Something is wrong, we should be done by now
prob_min[0] = '\0';
return(0);
}
}
// Copy the substring across
xastir_snprintf(prob_min,
prob_min_size,
"%s",
c);
prob_min[d-c] = '\0';
prob_min[10] = '\0'; // Just to make sure
// Delete data from data extension field
d++; // Skip the comma
done = 0;
while (!done) {
*(c-4) = *d;
if (*d == '\0')
done++;
c++;
d++;
}
return(1);
}
/*
* Extract signpost data from APRS info field: "{123}", an APRS data extension
* Format can be {1}, {12}, or {123}. Letters or digits are ok.
*/
int extract_signpost(char *info, char *signpost) {
int i,found,len,done;
//0123456
//{1}
//{12}
//{121}
found=0;
len = (int)strlen(info);
if ( (len > 2)
&& (info[0] == '{')
&& ( (info[2] == '}' ) || (info[3] == '}' ) || (info[4] == '}' ) ) ) {
i = 1;
done = 0;
while (!done) { // Snag up to three digits
if (info[i] == '}') { // We're done
found = i; // found = position of '}' character
done++;
}
else {
signpost[i-1] = info[i];
}
i++;
if ( (i > 4) && !done) { // Something is wrong, we should be done by now
done++;
signpost[0] = '\0';
return(0);
}
}
substr(signpost,info+1,found-1);
found++;
for (i=0;i<=len-found;i++) { // delete omnidf from data extension field
info[i] = info[i+found];
}
return(1);
}
else {
signpost[0] = '\0';
return(0);
}
}
// is_altnet()
//
// Returns true if station fits the current altnet description.
//
int is_altnet(AprsDataRow *p_station) {
char temp_altnet_call[20+1];
char temp2[20+1];
char *net_ptr;
int altnet_match;
int result;
// Snag a possible altnet call out of the record for later use
if (p_station->node_path_ptr != NULL)
substr(temp_altnet_call, p_station->node_path_ptr, MAX_CALLSIGN);
else
temp_altnet_call[0] = '\0';
// Save for later
snprintf(temp2,
sizeof(temp2),
"%s",
temp_altnet_call);
if ((net_ptr = strchr(temp_altnet_call, ',')))
*net_ptr = '\0'; // Chop the string at the first ',' character
for (altnet_match = (int)strlen(altnet_call); altnet && altnet_call[altnet_match-1] == '*'; altnet_match--);
result = (!strncmp(temp_altnet_call, altnet_call, (size_t)altnet_match)
|| !strcmp(temp_altnet_call, "local")
|| !strncmp(temp_altnet_call, "SPC", 3)
|| !strcmp(temp_altnet_call, "SPECL")
|| ( is_my_station(p_station) ) ) ; // It's my callsign/SSID
return(result);
}
int is_num_or_sp(char ch)
{
return((int)((ch >= '0' && ch <= '9') || ch == ' '));
}
char *get_time(char *time_here) {
struct tm *time_now;
time_t timenw;
(void)time(&timenw);
time_now = localtime(&timenw);
(void)strftime(time_here,MAX_TIME,"%m%d%Y%H%M%S",time_now);
return(time_here);
}
static void clear_area(AprsDataRow *p_station) {
p_station->aprs_symbol.area_object.type = AREA_NONE;
p_station->aprs_symbol.area_object.color = AREA_GRAY_LO;
p_station->aprs_symbol.area_object.sqrt_lat_off = 0;
p_station->aprs_symbol.area_object.sqrt_lon_off = 0;
p_station->aprs_symbol.area_object.corridor_width = 0;
}
// Check for valid overlay characters: 'A-Z', '0-9', and 'a-j'. If
// 'a-j', it's from a compressed posit, and we need to convert it to
// '0-9'.
void overlay_symbol(char symbol, char data, AprsDataRow *fill) {
if ( data != '/' && data !='\\') { // Symbol overlay
if (data >= 'a' && data <= 'j') {
// Found a compressed posit numerical overlay
data = data - 'a'+'0'; // Convert to a digit
}
if ( (data >= '0' && data <= '9')
|| (data >= 'A' && data <= 'Z') ) {
// Found normal overlay character
fill->aprs_symbol.aprs_type = '\\';
fill->aprs_symbol.special_overlay = data;
}
else {
// Bad overlay character. Don't use it. Insert the
// normal alternate table character instead.
fill->aprs_symbol.aprs_type = '\\';
fill->aprs_symbol.special_overlay='\0';
}
}
else { // No overlay character
fill->aprs_symbol.aprs_type = data;
fill->aprs_symbol.special_overlay='\0';
}
fill->aprs_symbol.aprs_symbol = symbol;
}
/*
* Extract Time from begin of line [APRS Reference, chapter 6]
*
* If a time string is found in "data", it is deleted from the
* beginning of the string.
*/
int extract_time(AprsDataRow *p_station, char *data, int type) {
int len, i;
int ok = 0;
// todo: better check of time data ranges
len = (int)strlen(data);
if (type == APRS_WX2) {
// 8 digit time from stand-alone positionless weather stations...
if (len > 8) {
// MMDDHHMM zulu time
// MM 01-12 todo: better check of time data ranges
// DD 01-31
// HH 01-23
// MM 01-59
ok = 1;
for (i=0;ok && i<8;i++)
if (!isdigit((int)data[i]))
ok = 0;
if (ok) {
// substr(p_station->station_time,data+2,6);
// p_station->station_time_type = 'z';
for (i=0;i<=len-8;i++) // delete time from data
data[i] = data[i+8];
}
}
}
else {
if (len > 6) {
// Status messages only with optional zulu format
// DK7IN: APRS ref says one of 'z' '/' 'h', but I found 'c' at HB9TJM-8 ???
if (toupper(data[6])=='Z' || data[6]=='/' || toupper(data[6])=='H')
ok = 1;
for (i=0;ok && i<6;i++)
if (!isdigit((int)data[i]))
ok = 0;
if (ok) {
// substr(p_station->station_time,data,6);
// p_station->station_time_type = data[6];
for (i=0;i<=len-7;i++) // delete time from data
data[i] = data[i+7];
}
}
}
return(ok);
}
// Breaks up a string into substrings using comma as the delimiter.
// Makes each entry in the array of char ptrs point to one
// substring. Modifies incoming string and cptr[] array. Send a
// character constant string to it and you'll get an instant
// segfault (the function can't modify a char constant string).
//
void split_string( char *data, char *cptr[], int max ) {
int ii;
char *temp;
char *current = data;
// NULL each char pointer
for (ii = 0; ii < max; ii++) {
cptr[ii] = NULL;
}
// Save the beginning substring address
cptr[0] = current;
for (ii = 1; ii < max; ii++) {
temp = strchr(current,','); // Find next comma
if(!temp) { // No commas found
return; // All done with string
}
// Store pointer to next substring in array
cptr[ii] = &temp[1];
current = &temp[1];
// Overwrite comma with end-of-string char and bump pointer by
// one.
temp[0] = '\0';
}
}
/* check data format 123 ___ ... */
// We wish to count how many ' ' or '.' characters we find. If it
// equals zero or the field width, it might be a weather field. If
// not, then it might be part of a comment field or something else.
//
int is_weather_data(char *data, int len) {
int ok = 1;
int i;
int count = 0;
for (i=0;ok && i,")) != NULL)
*ptr = '\0';
if (strlen(hold) >= 2) {
switch (hold[0]) {
case 'A':
symbol.aprs_type = '\\';
case 'P':
if (('0' <= hold[1] && hold[1] <= '9') || ('A' <= hold[1] && hold[1] <= 'Z'))
symbol.aprs_symbol = hold[1];
break;
case 'O':
symbol.aprs_type = '\\';
case 'B':
switch (hold[1]) {
case 'B':
symbol.aprs_symbol = '!';
break;
case 'C':
symbol.aprs_symbol = '"';
break;
case 'D':
symbol.aprs_symbol = '#';
break;
case 'E':
symbol.aprs_symbol = '$';
break;
case 'F':
symbol.aprs_symbol = '%';
break;
case 'G':
symbol.aprs_symbol = '&';
break;
case 'H':
symbol.aprs_symbol = '\'';
break;
case 'I':
symbol.aprs_symbol = '(';
break;
case 'J':
symbol.aprs_symbol = ')';
break;
case 'K':
symbol.aprs_symbol = '*';
break;
case 'L':
symbol.aprs_symbol = '+';
break;
case 'M':
symbol.aprs_symbol = ',';
break;
case 'N':
symbol.aprs_symbol = '-';
break;
case 'O':
symbol.aprs_symbol = '.';
break;
case 'P':
symbol.aprs_symbol = '/';
break;
}
break;
case 'D':
symbol.aprs_type = '\\';
case 'H':
switch (hold[1]) {
case 'S':
symbol.aprs_symbol = '[';
break;
case 'T':
symbol.aprs_symbol = '\\';
break;
case 'U':
symbol.aprs_symbol = ']';
break;
case 'V':
symbol.aprs_symbol = '^';
break;
case 'W':
symbol.aprs_symbol = '_';
break;
case 'X':
symbol.aprs_symbol = '`';
break;
}
break;
case 'N':
symbol.aprs_type = '\\';
case 'M':
switch (hold[1]) {
case 'R':
symbol.aprs_symbol = ':';
break;
case 'S':
symbol.aprs_symbol = ';';
break;
case 'T':
symbol.aprs_symbol = '<';
break;
case 'U':
symbol.aprs_symbol = '=';
break;
case 'V':
symbol.aprs_symbol = '>';
break;
case 'W':
symbol.aprs_symbol = '?';
break;
case 'X':
symbol.aprs_symbol = '@';
break;
}
break;
case 'Q':
symbol.aprs_type = '\\';
case 'J':
switch (hold[1]) {
case '1':
symbol.aprs_symbol = '{';
break;
case '2':
symbol.aprs_symbol = '|';
break;
case '3':
symbol.aprs_symbol = '}';
break;
case '4':
symbol.aprs_symbol = '~';
break;
}
break;
case 'S':
symbol.aprs_type = '\\';
case 'L':
if ('A' <= hold[1] && hold[1] <= 'Z')
symbol.aprs_symbol = tolower((int)hold[1]);
break;
}
if (hold[2]) {
if (hold[2] >= 'a' && hold[2] <= 'j') {
// Compressed mode numeric overlay
symbol.special_overlay = hold[2] - 'a';
}
else if ( (hold[2] >= '0' && hold[2] <= '9')
|| (hold[2] >= 'A' && hold[2] <= 'Z') ) {
// Normal overlay character
symbol.special_overlay = hold[2];
}
else {
// Bad overlay character found
symbol.special_overlay = '\0';
}
}
else {
// No overlay character found
symbol.special_overlay = '\0';
}
}
}
return(&symbol);
}
/*
* See if position is defined
* 90°N 180°W (0,0 in internal coordinates) is our undefined position
* 0N/0E is excluded from trails, could be excluded from map (#define ACCEPT_0N_0E)
*/
int position_defined(long lat, long lon, int strict) {
if (lat == 0l && lon == 0l)
return(0); // undefined location
#ifndef ACCEPT_0N_0E
if (strict)
#endif // ACCEPT_0N_0E
if (lat == 90*60*60*100l && lon == 180*60*60*100l) // 0N/0E
return(0); // undefined location
return(1);
}
//
// Extract data for $GPRMC, it fails if there is no position!!
//
// GPRMC,UTC-Time,status(A/V),lat,N/S,lon,E/W,SOG,COG,UTC-Date,Mag-Var,E/W,Fix-Quality[*CHK]
// GPRMC,hhmmss[.sss],{A|V},ddmm.mm[mm],{N|S},dddmm.mm[mm],{E|W},[dd]d.d[ddddd],[dd]d.d[d],ddmmyy,[ddd.d],[{E|W}][,A|D|E|N|S][*CHK]
//
// The last field before the checksum is entirely optional, and in
// fact first appeared in NMEA 2.3 (fairly recently). Most GPS's do
// not currently put out that field. The field may be null or
// nonexistent including the comma. Only "A" or "D" are considered
// to be active and reliable fixes if this field is present.
// Fix-Quality:
// A: Autonomous
// D: Differential
// E: Estimated
// N: Not Valid
// S: Simulator
//
// $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
// $GPRMC,104748.821,A,4301.1492,N,08803.0374,W,0.085048,102.36,010605,,*1A
// $GPRMC,104749.821,A,4301.1492,N,08803.0377,W,0.054215,74.60,010605,,*2D
//
int extract_RMC(AprsDataRow *p_station, char *data, char *call_sign, char *path, int *num_digits) {
char temp_data[40]; // short term string storage, MAX_CALLSIGN, ... ???
char lat_s[20];
char long_s[20];
int ok;
char *Substring[12]; // Pointers to substrings parsed by split_string()
char temp_string[MAX_MESSAGE_LENGTH+1];
char temp_char;
// should we copy it before processing? it changes data: ',' gets substituted by '\0' !!
ok = 0; // Start out as invalid. If we get enough info, we change this to a 1.
if ( (data == NULL) || (strlen(data) < 34) ) { // Not enough data to parse position from.
return(ok);
}
p_station->record_type = NORMAL_GPS_RMC;
// Create a timestamp from the current time
// get_time saves the time in temp_data
xastir_snprintf(p_station->pos_time,
sizeof(p_station->pos_time),
"%s",
get_time(temp_data));
p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
/* check aprs type on call sign */
p_station->aprs_symbol = *id_callsign(call_sign, path);
// Make a copy of the incoming data. The string passed to
// split_string() gets destroyed.
xastir_snprintf(temp_string,
sizeof(temp_string),
"%s",
data);
split_string(temp_string, Substring, 12);
// The Substring[] array contains pointers to each substring in
// the original data string.
// GPRMC,034728,A,5101.016,N,11359.464,W,000.0,284.9,110701,018.0,E*7D
// 0 1 2 3 4 5 6 7 8 9 10 11
if (Substring[0] == NULL) // No GPRMC string
return(ok);
if (Substring[1] == NULL) // No time string
return(ok);
if (Substring[2] == NULL) // No valid fix char
return(ok);
if (Substring[2][0] != 'A' && Substring[2][0] != 'V')
return(ok);
// V is a warning but we can get good data still ?
// DK7IN: got no position with 'V' !
if (Substring[3] == NULL) // No latitude string
return(ok);
if (Substring[4] == NULL) // No latitude N/S
return(ok);
// Need to check lat_s for validity here. Note that some GPS's put out another digit of precision
// (4801.1234) or leave one out (4801.12). Next character after digits should be a ','
// Count digits after the decimal point for latitude
if (strchr(Substring[3],'.')) {
*num_digits = strlen(Substring[3]) - (int)(strchr(Substring[3],'.') - Substring[3]) - 1;
}
else {
*num_digits = 0;
}
temp_char = toupper((int)Substring[4][0]);
if (temp_char != 'N' && temp_char != 'S') // Bad N/S
return(ok);
xastir_snprintf(lat_s,
sizeof(lat_s),
"%s%c",
Substring[3],
temp_char);
if (Substring[5] == NULL) // No longitude string
return(ok);
if (Substring[6] == NULL) // No longitude E/W
return(ok);
// Need to check long_s for validity here. Should be all digits. Note that some GPS's put out another
// digit of precision. (12201.1234). Next character after digits should be a ','
temp_char = toupper((int)Substring[6][0]);
if (temp_char != 'E' && temp_char != 'W') // Bad E/W
return(ok);
xastir_snprintf(long_s,
sizeof(long_s),
"%s%c",
Substring[5],
temp_char);
p_station->coord_lat = convert_lat_s2l(lat_s);
p_station->coord_lon = convert_lon_s2l(long_s);
// If we've made it this far, We have enough for a position now!
ok = 1;
// Now that we have a basic position, let's see what other data
// can be parsed from the packet. The rest of it can still be
// corrupt, so we're proceeding carefully under yellow alert on
// impulse engines only.
// GPRMC,034728,A,5101.016,N,11359.464,W,000.0,284.9,110701,018.0,E*7D
// 0 1 2 3 4 5 6 7 8 9 10 11
if (Substring[7] == NULL) { // No speed string
p_station->speed[0] = '\0'; // No speed available
return(ok);
}
else {
xastir_snprintf(p_station->speed,
MAX_SPEED,
"%s",
Substring[7]);
// Is it always knots, otherwise we need a conversion!
}
if (Substring[8] == NULL) { // No course string
xastir_snprintf(p_station->course,
sizeof(p_station->course),
"000.0"); // No course available
return(ok);
}
else {
xastir_snprintf(p_station->course,
MAX_COURSE,
"%s",
Substring[8]);
}
return(ok);
}
//
// Extract data for $GPGLL
//
// $GPGLL,4748.811,N,12219.564,W,033850,A*3C
// lat, long, UTCtime in hhmmss, A=Valid, checksum
//
// GPGLL,4748.811,N,12219.564,W,033850,A*3C
// 0 1 2 3 4 5 6
//
int extract_GLL(AprsDataRow *p_station,char *data,char *call_sign, char *path, int *num_digits) {
char temp_data[40]; // short term string storage, MAX_CALLSIGN, ... ???
char lat_s[20];
char long_s[20];
int ok;
char *Substring[7]; // Pointers to substrings parsed by split_string()
char temp_string[MAX_MESSAGE_LENGTH+1];
char temp_char;
ok = 0; // Start out as invalid. If we get enough info, we change this to a 1.
if ( (data == NULL) || (strlen(data) < 28) ) // Not enough data to parse position from.
return(ok);
p_station->record_type = NORMAL_GPS_GLL;
// Create a timestamp from the current time
// get_time saves the time in temp_data
xastir_snprintf(p_station->pos_time,
sizeof(p_station->pos_time),
"%s",
get_time(temp_data));
p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
/* check aprs type on call sign */
p_station->aprs_symbol = *id_callsign(call_sign, path);
// Make a copy of the incoming data. The string passed to
// split_string() gets destroyed.
xastir_snprintf(temp_string,
sizeof(temp_string),
"%s",
data);
split_string(temp_string, Substring, 7);
// The Substring[] array contains pointers to each substring in
// the original data string.
if (Substring[0] == NULL) // No GPGGA string
return(ok);
if (Substring[1] == NULL) // No latitude string
return(ok);
if (Substring[2] == NULL) // No N/S string
return(ok);
if (Substring[3] == NULL) // No longitude string
return(ok);
if (Substring[4] == NULL) // No E/W string
return(ok);
temp_char = toupper((int)Substring[2][0]);
if (temp_char != 'N' && temp_char != 'S')
return(ok);
xastir_snprintf(lat_s,
sizeof(lat_s),
"%s%c",
Substring[1],
temp_char);
// Need to check lat_s for validity here. Note that some GPS's put out another digit of precision
// (4801.1234). Next character after digits should be a ','
// Count digits after the decimal point for latitude
if (strchr(Substring[1],'.')) {
*num_digits = strlen(Substring[1]) - (int)(strchr(Substring[1],'.') - Substring[1]) - 1;
}
else {
*num_digits = 0;
}
temp_char = toupper((int)Substring[4][0]);
if (temp_char != 'E' && temp_char != 'W')
return(ok);
xastir_snprintf(long_s,
sizeof(long_s),
"%s%c",
Substring[3],
temp_char);
// Need to check long_s for validity here. Should be all digits. Note that some GPS's put out another
// digit of precision. (12201.1234). Next character after digits should be a ','
p_station->coord_lat = convert_lat_s2l(lat_s);
p_station->coord_lon = convert_lon_s2l(long_s);
ok = 1; // We have enough for a position now
xastir_snprintf(p_station->course,
sizeof(p_station->course),
"000.0"); // Fill in with dummy values
p_station->speed[0] = '\0'; // Fill in with dummy values
// A is valid, V is a warning but we can get good data still?
// We don't currently check the data valid flag.
return(ok);
}
//
// Extract data for $GPGGA
//
// GPGGA,UTC-Time,lat,N/S,long,E/W,GPS-Quality,nsat,HDOP,MSL-Meters,M,Geoidal-Meters,M,DGPS-Data-Age(seconds),DGPS-Ref-Station-ID[*CHK]
// GPGGA,hhmmss[.sss],ddmm.mm[mm],{N|S},dddmm.mm[mm],{E|W},{0-8},dd,[d]d.d,[-dddd]d.d,M,[-ddd]d.d,M,[dddd.d],[dddd][*CHK]
//
// GPS-Quality:
// 0: Invalid Fix
// 1: GPS Fix
// 2: DGPS Fix
// 3: PPS Fix
// 4: RTK Fix
// 5: Float RTK Fix
// 6: Estimated (dead-reckoning) Fix
// 7: Manual Input Mode
// 8: Simulation Mode
//
// $GPGGA,170834,4124.8963,N,08151.6838,W,1,05,1.5,280.2,M,-34.0,M,,,*75
// $GPGGA,104438.833,4301.1439,N,08803.0338,W,1,05,1.8,185.8,M,-34.2,M,0.0,0000*40
//
// nsat=Number of Satellites being tracked
//
//
int extract_GGA(AprsDataRow *p_station,char *data,char *call_sign, char *path, int *num_digits) {
char temp_data[40]; // short term string storage, MAX_CALLSIGN, ... ???
char lat_s[20];
char long_s[20];
int ok;
char *Substring[15]; // Pointers to substrings parsed by split_string()
char temp_string[MAX_MESSAGE_LENGTH+1];
char temp_char;
int temp_num;
ok = 0; // Start out as invalid. If we get enough info, we change this to a 1.
if ( (data == NULL) || (strlen(data) < 32) ) // Not enough data to parse position from.
return(ok);
p_station->record_type = NORMAL_GPS_GGA;
// Create a timestamp from the current time
// get_time saves the time in temp_data
xastir_snprintf(p_station->pos_time,
sizeof(p_station->pos_time),
"%s",
get_time(temp_data));
p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
/* check aprs type on call sign */
p_station->aprs_symbol = *id_callsign(call_sign, path);
// Make a copy of the incoming data. The string passed to
// split_string() gets destroyed.
xastir_snprintf(temp_string,
sizeof(temp_string),
"%s",
data);
split_string(temp_string, Substring, 15);
// The Substring[] array contains pointers to each substring in
// the original data string.
// GPGGA,hhmmss[.sss],ddmm.mm[mm],{N|S},dddmm.mm[mm],{E|W},{0-8},dd,[d]d.d,[-dddd]d.d,M,[-ddd]d.d,M,[dddd.d],[dddd][*CHK]
// 0 1 2 3 4 5 6 7 8 9 1 1 1 1 1
// 0 1 2 3 4
if (Substring[0] == NULL) // No GPGGA string
return(ok);
if (Substring[1] == NULL) // No time string
return(ok);
if (Substring[2] == NULL) // No latitude string
return(ok);
if (Substring[3] == NULL) // No latitude N/S
return(ok);
// Need to check lat_s for validity here. Note that some GPS's put out another digit of precision
// (4801.1234). Next character after digits should be a ','
// Count digits after the decimal point for latitude
if (strchr(Substring[2],'.')) {
*num_digits = strlen(Substring[2]) - (int)(strchr(Substring[2],'.') - Substring[2]) - 1;
}
else {
*num_digits = 0;
}
temp_char = toupper((int)Substring[3][0]);
if (temp_char != 'N' && temp_char != 'S') // Bad N/S
return(ok);
xastir_snprintf(lat_s,
sizeof(lat_s),
"%s%c",
Substring[2],
temp_char);
if (Substring[4] == NULL) // No longitude string
return(ok);
if (Substring[5] == NULL) // No longitude E/W
return(ok);
// Need to check long_s for validity here. Should be all digits. Note that some GPS's put out another
// digit of precision. (12201.1234). Next character after digits should be a ','
temp_char = toupper((int)Substring[5][0]);
if (temp_char != 'E' && temp_char != 'W') // Bad E/W
return(ok);
xastir_snprintf(long_s,
sizeof(long_s),
"%s%c",
Substring[4],
temp_char);
p_station->coord_lat = convert_lat_s2l(lat_s);
p_station->coord_lon = convert_lon_s2l(long_s);
// If we've made it this far, We have enough for a position now!
ok = 1;
// Now that we have a basic position, let's see what other data
// can be parsed from the packet. The rest of it can still be
// corrupt, so we're proceeding carefully under yellow alert on
// impulse engines only.
// Check for valid fix {
if (Substring[6] == NULL
|| Substring[6][0] == '0' // Fix quality
|| Substring[7] == NULL // Sat number
|| Substring[8] == NULL // hdop
|| Substring[9] == NULL) { // Altitude in meters
p_station->sats_visible[0] = '\0'; // Store empty sats visible
p_station->altitude[0] = '\0';; // Store empty altitude
return(ok); // A field between fix quality and altitude is missing
}
// Need to check for validity of this number. Should be 0-12? Perhaps a few more with WAAS, GLONASS, etc?
temp_num = atoi(Substring[7]);
if (temp_num < 0 || temp_num > 30) {
return(ok); // Number of satellites not valid
}
else {
// Store
xastir_snprintf(p_station->sats_visible,
sizeof(p_station->sats_visible),
"%d",
temp_num);
}
// Check for valid number for HDOP instead of just throwing it away?
xastir_snprintf(p_station->altitude,
sizeof(p_station->altitude),
"%s",
Substring[9]); // Get altitude
// Need to check for valid altitude before conversion
// unit is in meters, if not adjust value ???
if (Substring[10] == NULL) // No units for altitude
return(ok);
if (Substring[10][0] != 'M') {
//fprintf(stderr,"ERROR: should adjust altitude for meters\n");
//} else { // Altitude units wrong. Assume altitude bad
p_station->altitude[0] = '\0';
}
return(ok);
}
static void extract_multipoints(AprsDataRow *p_station,
char *data,
int type,
int remove_string) {
// If they're in there, the multipoints start with the
// sequence and end with a .
// In addition, there must be no spaces in there, and there
// must be an even number of characters (after the lead-in).
char *p, *p2;
int found = 0;
char *end;
int data_size;
if (data == NULL) {
return;
}
//fprintf(stderr,"Data: %s\t\t", data);
data_size = strlen(data);
end = data + (strlen(data) - 7); // 7 == 3 lead-in chars, plus 2 points
p_station->num_multipoints = 0;
/*
for (p = data; !found && p <= end; ++p) {
if (*p == ' ' && *(p+1) == RBRACE && islower((int)*(p+2)) && isdigit((int)*(p+3)) &&
(p2 = strchr(p+4, LBRACE)) != NULL && ((p2 - p) % 2) == 1) {
found = 1;
}
}
*/
// Start looking at the beginning of the data.
p = data;
// Look for the opening string.
while (!found && p < end && (p = strstr(p, START_STR)) != NULL) {
// The opening string was found. Check the following information.
if (islower((int)*(p+2)) && g_ascii_isdigit(*(p+3)) && (p2 = strchr(p+4, LBRACE)) != NULL && ((p2 - p) % 2) == 1) {
// It all looks good!
found = 1;
}
else {
// The following characters are not right. Advance and
// look again.
++p;
}
}
if (found) {
long multiplier;
double d;
char *m_start = p; // Start of multipoint string
char ok = 1;
// The second character (the lowercase) indicates additional style information,
// such as color, line type, etc.
p_station->style = *(p+2);
// The third character (the digit) indicates the way the points should be
// used. They may be used to draw a closed polygon, a series of line segments,
// etc.
p_station->type = *(p+3);
// The fourth character indicates the scale of the coordinates that
// follow. It may range from '!' to 'z'. The value represents the
// unit of measure (1, 0.1, 0.001, etc., in degrees) used in the offsets.
//
// Use the following formula to convert the char to the value:
// (10 ^ ((c - 33) / 20)) / 10000 degrees
//
// Finally we have to convert to Xastir units. Xastir stores coordinates
// as hudredths of seconds. There are 360,000 of those per degree, so we
// need to multiply by that factor so our numbers will be converted to
// Xastir units.
p = p + 4;
if (*p < '!' || *p > 'z') {
fprintf(stderr,"extract_multipoints: invalid scale character %d\n", *p);
ok = 0; // Failure
}
else {
d = (double)(*p);
d = pow(10.0, ((d - 33) / 20)) / 10000.0 * 360000.0;
multiplier = (long)d;
++p;
// The remaining characters are in pairs. Each pair is the
// offset lat and lon for one of the points. (The offset is
// from the actual location of the object.) Convert each
// character to its numeric value and save it.
while (*p != LBRACE && p_station->num_multipoints < MAX_MULTIPOINTS) {
// The characters are in the range '"' (34 decimal) to 'z' (122). They
// encode values in the range -44 to +44. To convert to the correct
// value 78 is subtracted from the character's value.
int lat_val = *p - 78;
int lon_val = *(p+1) - 78;
// Check for correct values.
if (lon_val < -44 || lon_val > 44 || lat_val < -44 || lat_val > 44) {
char temp[MAX_LINE_SIZE+1];
int i;
// Filter the string so we don't send strange
// chars to the xterm
for (i = 0; i < (int)strlen(data); i++) {
temp[i] = data[i] & 0x7f;
if ( (temp[i] < 0x20) || (temp[i] > 0x7e) )
temp[i] = ' ';
}
temp[strlen(data)] = '\0';
fprintf(stderr,"extract_multipoints: invalid value in (filtered) \"%s\": %d,%d\n",
temp,
lat_val,
lon_val);
p_station->num_multipoints = 0; // forget any points we already set
ok = 0; // Failure to decode
break;
}
// Malloc the storage area for this if we don't have
// it yet.
if (p_station->multipoint_data == NULL) {
p_station->multipoint_data = malloc(sizeof(AprsMultipointRow));
if (p_station->multipoint_data == NULL) {
p_station->num_multipoints = 0;
fprintf(stderr,"Couldn't malloc AprsMultipointRow'\n");
return;
}
}
// Add the offset to the object's position to obtain the position of the point.
// Note that we're working in Xastir coordinates, and in North America they
// are exactly opposite to lat/lon (larger numbers are farther east and south).
// An offset with a positive value means that the point should be north and/or
// west of the object, so we have to *subtract* the offset to get the correct
// placement in Xastir coordinates.
// TODO: Consider what we should do in the other geographic quadrants. Should we
// check here for the correct sign of the offset? Or should the program that
// creates the offsets take that into account?
p_station->multipoint_data->multipoints[p_station->num_multipoints][0]
= p_station->coord_lon - (lon_val * multiplier);
p_station->multipoint_data->multipoints[p_station->num_multipoints][1]
= p_station->coord_lat - (lat_val * multiplier);
p += 2;
++p_station->num_multipoints;
} // End of while loop
}
if (ok && remove_string) {
// We've successfully decoded a multipoint object?
// Remove the multipoint strings (and the sequence
// number at the end if present) from the data string.
// m_start points to the first character (a space). 'p'
// should be pointing at the LBRACE character.
// Make 'p' point to just after the end of the chars
while ( (p < data+strlen(data)) && (*p != ' ') ) {
p++;
}
// The string that 'p' points to now may be empty
// Truncate "data" at the starting brace - 1
*m_start = '\0';
// Now we have two strings inside "data". Copy the 2nd
// string directly onto the end of the first.
strncat(data, p, data_size+1);
// The multipoint string and sequence number should be
// erased now from "data".
//fprintf(stderr,"New Data: %s\n", data);
}
}
}
// Returns time in seconds since the Unix epoch.
//
time_t sec_now(void) {
time_t timenw;
time_t ret;
ret = time(&timenw);
return(ret);
}
int is_my_station(AprsDataRow *p_station) {
// if station is owned by me (including SSID)
return(p_station->flag & ST_MYSTATION);
}
int is_my_object_item(AprsDataRow *p_station) {
// If object/item is owned by me (including SSID)
return(p_station->flag & ST_MYOBJITEM);
}
/*
* Display text in the status line, text is removed after timeout
*/
void statusline(char *status_text,int update) {
// Maybe useful
/*
XmTextFieldSetString (text, status_text);
last_statusline = sec_now(); // Used for auto-ID timeout
*/
}
/*
* Extract text inserted by TNC X-1J4 from start of info line
*/
void extract_TNC_text(char *info) {
int i,j,len;
if (strncasecmp(info,"thenet ",7) == 0) { // 1st match
len = strlen(info)-1;
for (i=7;i7 && info[i] == ')' && info[i+1] == ' ') { // found
i += 2;
for (j=0;i<=len;i++,j++) {
info[j] = info[i];
}
}
}
}
/*
* Check for valid path and change it to TAPR format
* Add missing asterisk for stations that we must have heard via a digi
* Extract port for KAM TNCs
* Handle igate injection ID formats: "callsign-ssid,I" & "callsign-0ssid"
*
* TAPR-2 Format:
* KC2ELS-1*>SX0PWT,RELAY,WIDE:`2`$l##>/>"4)}
*
* AEA Format:
* KC2ELS-1*>RELAY>WIDE>SX0PWT:`2`$l##>/>"4)}
*/
int valid_path(char *path) {
int i,len,hops,j;
int type,ast,allast,ins;
char ch;
len = (int)strlen(path);
type = 0; // 0: unknown, 1: AEA '>', 2: TAPR2 ',', 3: mixed
hops = 1;
ast = 0;
allast = 0;
// There are some multi-port TNCs that deliver the port at the end
// of the path. For now we discard this information. If there is
// multi-port TNC support some day, we should write the port into
// our database.
// KAM: /V /H
// KPC-9612: /0 /1 /2
if (len > 2 && path[len-2] == '/') {
ch = path[len-1];
if (ch == 'V' || ch == 'H' || ch == '0' || ch == '1' || ch == '2') {
path[len-2] = '\0';
len = (int)strlen(path);
}
}
// One way of adding igate injection ID is to add "callsign-ssid,I".
// We need to remove the ",I" portion so it doesn't count as another
// digi here. This should be at the end of the path.
if (len > 2 && path[len-2] == ',' && path[len-1] == 'I') { // Found ",I"
//fprintf(stderr,"%s\n",path);
//fprintf(stderr,"Found ',I'\n");
path[len-2] = '\0';
len = (int)strlen(path);
//fprintf(stderr,"%s\n\n",path);
}
// Now look for the same thing but with a '*' character at the end.
// This should be at the end of the path.
if (len > 3 && path[len-3] == ',' && path[len-2] == 'I' && path[len-1] == '*') { // Found ",I*"
//fprintf(stderr,"%s\n",path);
//fprintf(stderr,"Found ',I*'\n");
path[len-3] = '\0';
len = (int)strlen(path);
//fprintf(stderr,"%s\n\n",path);
}
// Another method of adding igate injection ID is to add a '0' in front of
// the SSID. For WE7U it would change to WE7U-00, for WE7U-15 it would
// change to WE7U-015. Take out this zero so the rest of the decoding will
// work. This should be at the end of the path.
// Also look for the same thing but with a '*' character at the end.
if (len > 6) {
for (i=len-1; i>len-6; i--) {
if (path[i] == '-' && path[i+1] == '0') {
//fprintf(stderr,"%s\n",path);
for (j=i+1; j' || ch == ',') { // found digi call separator
// We're at the start of a callsign entry in the path
if (ast > 1 || (ast == 1 && i-j > 10) || (ast == 0 && (i == j || i-j > 9))) {
return(0); // more than one asterisk in call or wrong call size
}
ast = 0; // reset local asterisk counter
j = i+1; // set to start of next call
if (ch == ',')
type |= 0x02; // set TAPR2 flag
else
type |= 0x01; // set AEA flag (found '>')
hops++; // count hops
}
else { // digi call character or asterisk
// We're in the middle of a callsign entry
if (ch == '*') {
ast++; // count asterisks in call
allast++; // count asterisks in path
}
else if ((ch <'A' || ch > 'Z') // Not A-Z
&& (ch <'a' || ch > 'z') // Not a-z
&& (ch <'0' || ch > '9') // Not 0-9
&& ch != '-') {
// Note that Q-construct and internet callsigns can
// have a-z in them, AX.25 callsigns cannot unless
// they are in a 3rd-party packet.
return(0); // wrong character in path
}
}
}
if (ast > 1 || (ast == 1 && i-j > 10) || (ast == 0 && (i == j || i-j > 9))) {
return(0); // more than one asterisk or wrong call size
}
if (type == 0x03) {
return(0); // wrong format, both '>' and ',' in path
}
if (hops > 9) { // [APRS Reference chapter 3]
return(0); // too much hops, destination + 0-8 digipeater addresses
}
if (type == 0x01) {
int delimiters[20];
int k = 0;
char dest[15];
char rest[100];
for (i=0; i') {
path[i] = ','; // Exchange separator character
delimiters[k++] = i; // Save the delimiter indexes
}
}
// We also need to move the destination callsign to the end.
// AEA has them in a different order than TAPR-2 format.
// We'll move the destination address between delimiters[0]
// and [1] to the end of the string.
//fprintf(stderr,"Orig. Path:%s\n",path);
// Save the destination
xastir_snprintf(dest,sizeof(dest),"%s",&path[delimiters[--k]+1]);
dest[strlen(path) - delimiters[k] - 1] = '\0'; // Terminate it
dest[14] = '\0'; // Just to make sure
path[delimiters[k]] = '\0'; // Delete it from the original path
//fprintf(stderr,"Destination: %s\n",dest);
// TAPR-2 Format:
// KC2ELS-1*>SX0PWT,RELAY,WIDE:`2`$l##>/>"4)}
//
// AEA Format:
// KC2ELS-1*>RELAY>WIDE>SX0PWT:`2`$l##>/>"4)}
// 9 15 20
// We now need to insert the destination into the middle of
// the string. Save part of it in another variable first.
xastir_snprintf(rest,
sizeof(rest),
"%s",
path);
//fprintf(stderr,"Rest:%s\n",rest);
xastir_snprintf(path,len+1,"%s,%s",dest,rest);
//fprintf(stderr,"New Path:%s\n",path);
}
if (allast < 1) { // try to insert a missing asterisk
ins = 0;
hops = 0;
for (i=0; i 0 && (j - i) == 5) { // WIDE3
if ( path[ i ] == 'W' && path[i+1] == 'I' && path[i+2] == 'D'
&& path[i+3] == 'E' && path[i+4] >= '0' && path[i+4] <= '9') {
ins = j;
}
}
/*
Don't do this! It can mess up relay/wide1-1 digipeating by adding
an asterisk later in the path than the first unused digi.
if (hops > 0 && (j - i) == 7) { // WIDE3-2
if ( path[ i ] == 'W' && path[i+1] == 'I' && path[i+2] == 'D'
&& path[i+3] == 'E' && path[i+4] >= '0' && path[i+4] <= '9'
&& path[i+5] == '-' && path[i+6] >= '0' && path[i+6] <= '9'
&& (path[i+4] != path[i+6]) ) {
ins = j;
}
}
*/
if (hops > 0 && (j - i) == 6) { // TRACE3
if ( path[ i ] == 'T' && path[i+1] == 'R' && path[i+2] == 'A'
&& path[i+3] == 'C' && path[i+4] == 'E'
&& path[i+5] >= '0' && path[i+5] <= '9') {
if (hops == 1)
ins = j;
else
ins = i-1;
}
}
/*
Don't do this! It can mess up relay/wide1-1 digipeating by adding
an asterisk later in the path than the first unused digi.
if (hops > 0 && (j - i) == 8) { // TRACE3-2
if ( path[ i ] == 'T' && path[i+1] == 'R' && path[i+2] == 'A'
&& path[i+3] == 'C' && path[i+4] == 'E' && path[i+5] >= '0'
&& path[i+5] <= '9' && path[i+6] == '-' && path[i+7] >= '0'
&& path[i+7] <= '9' && (path[i+5] != path[i+7]) ) {
if (hops == 1)
ins = j;
else
ins = i-1;
}
}
*/
hops++;
i = j; // skip to start of next call
}
if (ins > 0) {
for (i=len;i>=ins;i--) {
path[i+1] = path[i]; // generate space for '*'
// we work on a separate path copy which is long enough to do it
}
path[ins] = '*'; // and insert it
}
}
return(1); // Path is good
}
char *remove_leading_spaces(char *data) {
int i,j;
int count;
if (data == NULL)
return NULL;
if (strlen(data) == 0)
return NULL;
count = 0;
// Count the leading space characters
for (i = 0; i < (int)strlen(data); i++) {
if (data[i] == ' ') {
count++;
}
else { // Found a non-space
break;
}
}
// Check whether entire string was spaces
if (count == (int)strlen(data)) {
// Empty the string
data[0] = '\0';
}
else if (count > 0) { // Found some spaces
i = 0;
for( j = count; j < (int)strlen(data); j++ ) {
data[i++] = data[j]; // Move string left
}
data[i] = '\0'; // Terminate the new string
}
return(data);
}
int is_num_chr(char ch) {
return((int)isdigit(ch));
}
char *remove_trailing_spaces(char *data) {
int i;
if (data == NULL)
return NULL;
if (strlen(data) == 0)
return NULL;
for(i=strlen(data)-1;i>=0;i--)
if(data[i] == ' ')
data[i] = '\0';
else
break;
return(data);
}
char *remove_trailing_asterisk(char *data) {
int i;
if (data == NULL)
return NULL;
if (strlen(data) == 0)
return NULL;
// Should the test here be i>=0 ??
for(i=strlen(data)-1;i>0;i--) {
if(data[i] == '*')
data[i] = '\0';
}
return(data);
}
//--------------------------------------------------------------------
//Removes all control codes ( <0x20 or >0x7e ) from a string, including
// CR's, LF's, tab's, etc.
//
void makePrintable(char *cp) {
int i,j;
int len = (int)strlen(cp);
unsigned char *ucp = (unsigned char *)cp;
for (i=0, j=0; i<=len; i++) {
ucp[i] &= 0x7f; // Clear 8th bit
if ( ((ucp[i] >= (unsigned char)0x20) && (ucp[i] <= (unsigned char)0x7e))
|| ((char)ucp[i] == '\0') ) // Check for printable or terminating 0
ucp[j++] = ucp[i] ; // Copy to (possibly) new location if printable
}
}
/*
* Check for a valid AX.25 call
* Valid calls consist of up to 6 uppercase alphanumeric characters
* plus optional SSID (four-bit integer) [APRS Reference, AX.25 Reference]
*/
int valid_call(char *call) {
int len, ok;
int i, del, has_num, has_chr;
char c;
has_num = 0;
has_chr = 0;
ok = 1;
len = (int)strlen(call);
if (len == 0)
return(0); // wrong size
while (call[0]=='c' && call[1]=='m' && call[2]=='d' && call[3]==':') {
// Erase TNC prompts from beginning of callsign. This may
// not be the right place to do this, but it came in handy
// here, so that's where I put it. -- KB6MER
for(i=0; call[i+4]; i++)
call[i]=call[i+4];
call[i++]=0;
call[i++]=0;
call[i++]=0;
call[i++]=0;
len=strlen(call);
}
if (len > 9)
return(0); // Too long for valid call (6-2 max e.g. KB6MER-12)
del = 0;
for (i=len-2;ok && i>0 && i>=len-3;i--) { // search for optional SSID
if (call[i] =='-')
del = i; // found the delimiter
}
if (del) { // we have a SSID, so check it
if (len-del == 2) { // 2 char SSID
if (call[del+1] < '1' || call[del+1] > '9') // -1 ... -9
del = 0;
}
else { // 3 char SSID
if (call[del+1] != '1' || call[del+2] < '0' || call[del+2] > '5') // -10 ... -15
del = 0;
}
}
if (del)
len = del; // length of base call
for (i=0;ok && i= 'A' && c <= 'Z')
has_chr = 1; // we need at least one char
else if (c >= '0' && c <= '9')
has_num = 1; // we need at least one number
else
ok = 0; // wrong character in call
}
// if (!has_num || !has_chr) // with this we also discard NOCALL etc.
if (!has_chr)
ok = 0;
ok = (ok && strcmp(call,"NOCALL") != 0); // check for errors
ok = (ok && strcmp(call,"ERROR!") != 0);
ok = (ok && strcmp(call,"WIDE") != 0);
ok = (ok && strcmp(call,"RELAY") != 0);
ok = (ok && strcmp(call,"MAIL") != 0);
return(ok);
}
/*
* Check whether callsign is mine. "exact == 1" checks the SSID
* for a match as well. "exact == 0" checks only the base
* callsign.
*/
int is_my_call(char *call, int exact) {
char *p_del;
int ok;
// U.S. special-event callsigns can be as short as three
// characters, any less and we don't have a valid callsign. We
// don't check for that restriction here though.
if (exact) {
// We're looking for an exact match
ok = (int)( !strcmp(call,_aprs_mycall ) );
//fprintf(stderr,"My exact call found: %s\n",call);
}
else {
// We're looking for a similar match. Compare only up to
// the '-' in each (if present).
int len1,len2;
p_del = index(call,'-');
if (p_del == NULL)
len1 = (int)strlen(call);
else
len1 = p_del - call;
p_del = index(_aprs_mycall,'-');
if (p_del == NULL)
len2 = (int)strlen(_aprs_mycall);
else
len2 = p_del - _aprs_mycall;
ok = (int)(len1 == len2 && !strncmp(call,_aprs_mycall,(size_t)len1));
//fprintf(stderr,"My base call found: %s\n",call);
}
return(ok);
}
/*
* Check for a valid internet name.
* Accept darned-near anything here as long as it is the proper
* length and printable.
*/
int valid_inet_name(char *name, char *info, char *origin, int origin_size) {
int len, i, ok;
char *ptr;
len = (int)strlen(name);
if (len > 9 || len == 0) // max 9 printable ASCII characters
return(0); // wrong size
for (i=0;i= 5 && strncmp(name,"aprsd",5) == 0) {
snprintf(origin, origin_size, "INET");
origin[4] = '\0'; // Terminate it
return(1); // aprsdXXXX is ok
}
// Modifies "origin" if a match found
//
if (len == 6) { // check for NWS
ok = 1;
for (i=0;i<6;i++)
if (name[i] <'A' || name[i] > 'Z') // 6 uppercase characters
ok = 0;
ok = ok && (info != NULL); // check if we can test info
if (ok) {
ptr = strstr(info,":NWS-"); // "NWS-" in info field (non-compressed alert)
ok = (ptr != NULL);
if (!ok) {
ptr = strstr(info,":NWS_"); // "NWS_" in info field (compressed alert)
ok = (ptr != NULL);
}
}
if (ok) {
snprintf(origin, origin_size, "INET-NWS");
origin[8] = '\0';
return(1); // weather alerts
}
}
return(1); // Accept anything else if we get to this point in
// the code. After all, the message came from the
// internet, not from RF.
}
/*
* Extract third-party traffic from information field before processing
*/
int extract_third_party(char *call,
char *path,
int path_size,
char **info,
char *origin,
int origin_size) {
int ok;
char *p_call;
char *p_path;
p_call = NULL; // to make the compiler happy...
p_path = NULL; // to make the compiler happy...
ok = 0;
if (!is_my_call(call,1)) { // Check SSID also
// todo: add reporting station call to database ??
// but only if not identical to reported call
(*info) = (*info) +1; // strip '}' character
p_call = strtok((*info),">"); // extract call
if (p_call != NULL) {
p_path = strtok(NULL,":"); // extract path
if (p_path != NULL) {
(*info) = strtok(NULL,""); // rest is information field
if ((*info) != NULL) // the above looks dangerous, but works on same string
if (strlen(p_path) < 100)
ok = 1; // we have found all three components
}
}
}
if (ok) {
snprintf(path,
path_size,
"%s",
p_path);
ok = valid_path(path); // check the path and convert it to TAPR format
// Note that valid_path() also removes igate injection identifiers
}
if (ok) { // check callsign
(void)remove_trailing_asterisk(p_call); // is an asterisk valid here ???
if (valid_inet_name(p_call,(*info),origin,origin_size)) { // accept some of the names used in internet
// Treat it as object with special origin
snprintf(call,
MAX_CALLSIGN+1,
"%s",
p_call);
}
else if (valid_call(p_call)) { // accept real AX.25 calls
snprintf(call,
MAX_CALLSIGN+1,
"%s",
p_call);
}
else {
ok = 0;
}
}
return(ok);
}
// DK7IN 99
/*
* Extract Compressed Position Report Data Formats from begin of line
* [APRS Reference, chapter 9]
*
* If a position is found, it is deleted from the data. If a
* compressed position is found, delete the three csT bytes as well,
* even if all spaces.
* Returns 0 if the packet is NOT a properly compressed position
* packet, returns 1 if ok.
*/
int extract_comp_position(AprsDataRow *p_station, char **info, /*@unused@*/ int type) {
int ok;
int x1, x2, x3, x4, y1, y2, y3, y4;
int c = 0;
int s = 0;
int T = 0;
int len;
char *my_data;
float lon = 0;
float lat = 0;
float range;
int skip = 0;
char L;
my_data = (*info);
// Check leading char. Must be one of these:
// '/'
// '\'
// A-Z
// a-j
//
L = my_data[0];
if ( L == '/'
|| L == '\\'
|| ( L >= 'A' && L <= 'Z' )
|| ( L >= 'a' && L <= 'j' ) ) {
// We're good so far
}
else {
// Note one of the symbol table or overlay characters, so
// there's something funky about this packet. It's not a
// properly formatted compressed position.
return(0);
}
//fprintf(stderr,"my_data: %s\n",my_data);
// If c = space, csT bytes are ignored. Minimum length: 8
// bytes for lat/lon, 2 for symbol, 3 for csT for a total of 13.
len = strlen(my_data);
ok = (int)(len >= 13);
if (ok) {
y1 = (int)my_data[1] - '!';
y2 = (int)my_data[2] - '!';
y3 = (int)my_data[3] - '!';
y4 = (int)my_data[4] - '!';
x1 = (int)my_data[5] - '!';
x2 = (int)my_data[6] - '!';
x3 = (int)my_data[7] - '!';
x4 = (int)my_data[8] - '!';
// csT bytes
if (my_data[10] == ' ') // Space
c = -1; // This causes us to ignore csT
else {
c = (int)my_data[10] - '!';
s = (int)my_data[11] - '!';
T = (int)my_data[12] - '!';
}
skip = 13;
// Convert ' ' to '0'. Not specified in APRS Reference! Do
// we need it?
if (x1 == -1) x1 = '\0';
if (x2 == -1) x2 = '\0';
if (x3 == -1) x3 = '\0';
if (x4 == -1) x4 = '\0';
if (y1 == -1) y1 = '\0';
if (y2 == -1) y2 = '\0';
if (y3 == -1) y3 = '\0';
if (y4 == -1) y4 = '\0';
ok = (int)(ok && (x1 >= '\0' && x1 < 91)); // /YYYYXXXX$csT
ok = (int)(ok && (x2 >= '\0' && x2 < 91)); // 0123456789012
ok = (int)(ok && (x3 >= '\0' && x3 < 91));
ok = (int)(ok && (x4 >= '\0' && x4 < 91));
ok = (int)(ok && (y1 >= '\0' && y1 < 91));
ok = (int)(ok && (y2 >= '\0' && y2 < 91));
ok = (int)(ok && (y3 >= '\0' && y3 < 91));
ok = (int)(ok && (y4 >= '\0' && y4 < 91));
T &= 0x3F; // DK7IN: force Compression Byte to valid format
// mask off upper two unused bits, they should be zero!?
ok = (int)(ok && (c == -1 || ((c >=0 && c < 91) && (s >= 0 && s < 91) && (T >= 0 && T < 64))));
if (ok) {
lat = (((y1 * 91 + y2) * 91 + y3) * 91 + y4 ) / 380926.0; // in deg, 0: 90°N
lon = (((x1 * 91 + x2) * 91 + x3) * 91 + x4 ) / 190463.0; // in deg, 0: 180°W
lat *= 60 * 60 * 100; // in 1/100 sec
lon *= 60 * 60 * 100; // in 1/100 sec
// The below check should _not_ be done. Compressed
// format can resolve down to about 1 foot worldwide
// (0.3 meters).
//if ((((long)(lat+4) % 60) > 8) || (((long)(lon+4) % 60) > 8))
// ok = 0; // check max resolution 0.01 min to
// catch even more errors
}
}
if (ok) {
overlay_symbol(my_data[9], my_data[0], p_station); // Symbol / Table
// Callsign check here includes checking SSID for an exact
// match
// if (!is_my_call(p_station->call_sign,1)) { // don't change my position, I know it better...
if ( !(is_my_station(p_station)) ) { // don't change my position, I know it better...
// Record the uncompressed lat/long that we just
// computed.
p_station->coord_lat = (long)((lat)); // in 1/100 sec
p_station->coord_lon = (long)((lon)); // in 1/100 sec
}
if (c >= 0) { // ignore csT if c = ' '
if (c < 90) { // Found course/speed or altitude bytes
if ((T & 0x18) == 0x10) { // check for GGA (with altitude)
xastir_snprintf(p_station->altitude, sizeof(p_station->altitude), "%06.0f",pow(1.002,(double)(c*91+s))*0.3048);
}
else { // Found compressed course/speed bytes
// Convert 0 degrees to 360 degrees so that
// Xastir will see it as a valid course and do
// dead-reckoning properly on this station
if (c == 0) {
c = 90;
}
// Compute course in degrees
xastir_snprintf(p_station->course,
sizeof(p_station->course),
"%03d",
c*4);
// Compute speed in knots
xastir_snprintf(p_station->speed,
sizeof(p_station->speed),
"%03.0f",
pow( 1.08,(double)s ) - 1.0);
//fprintf(stderr,"Decoded speed:%s, course:%s\n",p_station->speed,p_station->course);
}
}
else { // Found pre-calculated radio range bytes
if (c == 90) {
// pre-calculated radio range
range = 2 * pow(1.08,(double)s); // miles
// DK7IN: dirty hack... but better than nothing
if (s <= 5) // 2.9387 mi
xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "000");
else if (s <= 17) // 7.40 mi
xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "111");
else if (s <= 36) // 31.936 mi
xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "222");
else if (s <= 75) // 642.41 mi
xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "333");
else // max 90: 2037.8 mi
xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "444");
}
}
}
(*info) += skip; // delete position from comment
}
//fprintf(stderr," extract_comp_position end: %s\n",*info);
return(ok);
}
/*
* Extract bearing and number/range/quality from beginning of info field
*/
int extract_bearing_NRQ(char *info, char *bearing, char *nrq) {
int i,found,len;
len = (int)strlen(info);
found = 0;
if (len >= 8) {
found = 1;
for(i=1; found && i<8; i++) // check data format
if(!(isdigit((int)info[i]) || (i==4 && info[i]=='/')))
found=0;
}
if (found) {
substr(bearing,info+1,3);
substr(nrq,info+5,3);
for (i=0;i<=len-8;i++) // delete bearing/nrq from info field
info[i] = info[i+8];
}
if (!found) {
bearing[0] ='\0';
nrq[0]='\0';
}
return(found);
}
// APRS Data Extensions [APRS Reference p.27]
// .../... Course & Speed, may be followed by others (see p.27)
// .../... Wind Dir and Speed
// PHG.... Station Power and Effective Antenna Height/Gain
// RNG.... Pre-Calculated Radio Range
// DFS.... DF Signal Strength and Effective Antenna Height/Gain
// T../C.. Area Object Descriptor
/* Extract one of several possible APRS Data Extensions */
void process_data_extension(AprsDataRow *p_station, char *data, /*@unused@*/ int type) {
char temp1[7+1];
char temp2[3+1];
char temp3[10+1];
char bearing[3+1];
char nrq[3+1];
if (p_station->aprs_symbol.aprs_type == '\\' && p_station->aprs_symbol.aprs_symbol == 'l') {
/* This check needs to come first because the area object extension can look
exactly like what extract_speed_course will attempt to decode. */
extract_area(p_station, data);
}
else {
clear_area(p_station); // we got a packet with a non area symbol, so clear the data
if (extract_speed_course(data,temp1,temp2)) { // ... from Mic-E, etc.
//fprintf(stderr,"extracted speed/course\n");
if (atof(temp2) > 0) {
//fprintf(stderr,"course is non-zero\n");
xastir_snprintf(p_station->speed,
sizeof(p_station->speed),
"%06.2f",
atof(temp1));
xastir_snprintf(p_station->course, // in degrees
sizeof(p_station->course),
"%s",
temp2);
}
if (extract_bearing_NRQ(data, bearing, nrq)) { // Beam headings from DF'ing
//fprintf(stderr,"extracted bearing and NRQ\n");
xastir_snprintf(p_station->bearing,
sizeof(p_station->bearing),
"%s",
bearing);
xastir_snprintf(p_station->NRQ,
sizeof(p_station->NRQ),
"%s",
nrq);
p_station->signal_gain[0] = '\0'; // And blank out the shgd values
}
}
// Don't try to extract speed & course if a compressed
// object. Test for beam headings for compressed packets
// here
else if (extract_bearing_NRQ(data, bearing, nrq)) { // Beam headings from DF'ing
//fprintf(stderr,"extracted bearing and NRQ\n");
xastir_snprintf(p_station->bearing,
sizeof(p_station->bearing),
"%s",
bearing);
xastir_snprintf(p_station->NRQ,
sizeof(p_station->NRQ),
"%s",
nrq);
p_station->signal_gain[0] = '\0'; // And blank out the shgd values
}
else {
if (extract_powergain_range(data,temp1)) {
//fprintf(stderr,"Found power_gain: %s\n", temp1);
xastir_snprintf(p_station->power_gain,
sizeof(p_station->power_gain),
"%s",
temp1);
if (extract_bearing_NRQ(data, bearing, nrq)) { // Beam headings from DF'ing
//fprintf(stderr,"extracted bearing and NRQ\n");
xastir_snprintf(p_station->bearing,
sizeof(p_station->bearing),
"%s",
bearing);
xastir_snprintf(p_station->NRQ,
sizeof(p_station->NRQ),
"%s",
nrq);
p_station->signal_gain[0] = '\0'; // And blank out the shgd values
}
}
else {
if (extract_omnidf(data,temp1)) {
xastir_snprintf(p_station->signal_gain,
sizeof(p_station->signal_gain),
"%s",
temp1); // Grab the SHGD values
p_station->bearing[0] = '\0'; // And blank out the bearing/NRQ values
p_station->NRQ[0] = '\0';
// The spec shows speed/course before DFS, but example packets that
// come with DOSaprs show DFSxxxx/speed/course. We'll take care of
// that possibility by trying to decode speed/course again.
if (extract_speed_course(data,temp1,temp2)) { // ... from Mic-E, etc.
//fprintf(stderr,"extracted speed/course\n");
if (atof(temp2) > 0) {
//fprintf(stderr,"course is non-zero\n");
xastir_snprintf(p_station->speed,
sizeof(p_station->speed),
"%06.2f",
atof(temp1));
xastir_snprintf(p_station->course,
sizeof(p_station->course),
"%s",
temp2); // in degrees
}
}
// The spec shows that omnidf and bearing/NRQ can be in the same
// packet, which makes no sense, but we'll try to decode it that
// way anyway.
if (extract_bearing_NRQ(data, bearing, nrq)) { // Beam headings from DF'ing
//fprintf(stderr,"extracted bearing and NRQ\n");
xastir_snprintf(p_station->bearing,
sizeof(p_station->bearing),
"%s",
bearing);
xastir_snprintf(p_station->NRQ,
sizeof(p_station->NRQ),
"%s",
nrq);
//p_station->signal_gain[0] = '\0'; // And blank out the shgd values
}
}
}
}
if (extract_signpost(data, temp2)) {
//fprintf(stderr,"extracted signpost data\n");
xastir_snprintf(p_station->signpost,
sizeof(p_station->signpost),
"%s",
temp2);
}
if (extract_probability_min(data, temp3, sizeof(temp3))) {
//fprintf(stderr,"extracted probability_min data: %s\n",temp3);
xastir_snprintf(p_station->probability_min,
sizeof(p_station->probability_min),
"%s",
temp3);
}
if (extract_probability_max(data, temp3, sizeof(temp3))) {
//fprintf(stderr,"extracted probability_max data: %s\n",temp3);
xastir_snprintf(p_station->probability_max,
sizeof(p_station->probability_max),
"%s",
temp3);
}
}
}
/*
* Extract altitude from APRS info field "/A=012345" in feet
*/
int extract_altitude(char *info, char *altitude) {
int i,ofs,found,len;
found=0;
len = (int)strlen(info);
for(ofs=0; !found && ofsaltitude, sizeof(p_station->altitude), "%.2f",atof(temp_data)*0.3048);
//fprintf(stderr,"%.2f\n",atof(temp_data)*0.3048);
}
// do other things...
}
/*
* Extract Uncompressed Position Report from begin of line
*
* If a position is found, it is deleted from the data.
*/
int extract_position(AprsDataRow *p_station, char **info, int type) {
int ok;
char temp_lat[8+1];
char temp_lon[9+1];
char temp_grid[8+1];
char *my_data;
float gridlat;
float gridlon;
my_data = (*info);
if (type != APRS_GRID){ // Not a grid
ok = (int)(strlen(my_data) >= 19);
ok = (int)(ok && my_data[4]=='.' && my_data[14]=='.'
&& (toupper(my_data[7]) =='N' || toupper(my_data[7]) =='S')
&& (toupper(my_data[17])=='E' || toupper(my_data[17])=='W'));
// errors found: [4]: X [7]: n s [17]: w e
if (ok) {
ok = is_num_chr(my_data[0]); // 5230.31N/01316.88E>
ok = (int)(ok && is_num_chr(my_data[1])); // 0123456789012345678
ok = (int)(ok && is_num_or_sp(my_data[2]));
ok = (int)(ok && is_num_or_sp(my_data[3]));
ok = (int)(ok && is_num_or_sp(my_data[5]));
ok = (int)(ok && is_num_or_sp(my_data[6]));
ok = (int)(ok && is_num_chr(my_data[9]));
ok = (int)(ok && is_num_chr(my_data[10]));
ok = (int)(ok && is_num_chr(my_data[11]));
ok = (int)(ok && is_num_or_sp(my_data[12]));
ok = (int)(ok && is_num_or_sp(my_data[13]));
ok = (int)(ok && is_num_or_sp(my_data[15]));
ok = (int)(ok && is_num_or_sp(my_data[16]));
}
if (ok) {
overlay_symbol(my_data[18], my_data[8], p_station);
p_station->pos_amb = 0;
// spaces in latitude set position ambiguity, spaces in longitude do not matter
// we will adjust the lat/long to the center of the rectangle of ambiguity
if (my_data[2] == ' ') { // nearest degree
p_station->pos_amb = 4;
my_data[2] = my_data[12] = '3';
my_data[3] = my_data[5] = my_data[6] = '0';
my_data[13] = my_data[15] = my_data[16] = '0';
}
else if (my_data[3] == ' ') { // nearest 10 minutes
p_station->pos_amb = 3;
my_data[3] = my_data[13] = '5';
my_data[5] = my_data[6] = '0';
my_data[15] = my_data[16] = '0';
}
else if (my_data[5] == ' ') { // nearest minute
p_station->pos_amb = 2;
my_data[5] = my_data[15] = '5';
my_data[6] = '0';
my_data[16] = '0';
}
else if (my_data[6] == ' ') { // nearest 1/10th minute
p_station->pos_amb = 1;
my_data[6] = my_data[16] = '5';
}
xastir_snprintf(temp_lat,
sizeof(temp_lat),
"%s",
my_data);
temp_lat[7] = toupper(my_data[7]);
temp_lat[8] = '\0';
xastir_snprintf(temp_lon,
sizeof(temp_lon),
"%s",
my_data+9);
temp_lon[8] = toupper(my_data[17]);
temp_lon[9] = '\0';
// Callsign check here also checks SSID for an exact
// match
// if (!is_my_call(p_station->call_sign,1)) { // don't change my position, I know it better...
if ( !(is_my_station(p_station)) ) { // don't change my position, I know it better...
p_station->coord_lat = convert_lat_s2l(temp_lat); // ...in case of position ambiguity
p_station->coord_lon = convert_lon_s2l(temp_lon);
}
(*info) += 19; // delete position from comment
}
}
else { // It is a grid
// first sanity checks, need more
ok = (int)(is_num_chr(my_data[2]));
ok = (int)(ok && is_num_chr(my_data[3]));
ok = (int)(ok && ((my_data[0]>='A')&&(my_data[0]<='R')));
ok = (int)(ok && ((my_data[1]>='A')&&(my_data[1]<='R')));
if (ok) {
xastir_snprintf(temp_grid,
sizeof(temp_grid),
"%s",
my_data);
// this test treats >6 digit grids as 4 digit grids; >6 are uncommon.
// the spec mentioned 4 or 6, I'm not sure >6 is even allowed.
if ( (temp_grid[6] != ']') || (temp_grid[4] == 0) || (temp_grid[5] == 0)){
p_station->pos_amb = 6; // 1deg lat x 2deg lon
temp_grid[4] = 'L';
temp_grid[5] = 'L';
}
else {
p_station->pos_amb = 5; // 2.5min lat x 5min lon
temp_grid[4] = toupper(temp_grid[4]);
temp_grid[5] = toupper(temp_grid[5]);
}
// These equations came from what I read in the qgrid source code and
// various mailing list archives.
gridlon= (20.*((float)temp_grid[0]-65.) + 2.*((float)temp_grid[2]-48.) + 5.*((float)temp_grid[4]-65.)/60.) - 180.;
gridlat= (10.*((float)temp_grid[1]-65.) + ((float)temp_grid[3]-48.) + 5.*(temp_grid[5]-65.)/120.) - 90.;
// could check for my callsign here, and avoid changing it...
p_station->coord_lat = (unsigned long)(32400000l + (360000.0 * (-gridlat)));
p_station->coord_lon = (unsigned long)(64800000l + (360000.0 * gridlon));
p_station->aprs_symbol.aprs_type = '/';
p_station->aprs_symbol.aprs_symbol = 'G';
} // is it valid grid or not - "ok"
// could cut off the grid square from the comment here, but why bother?
} // is it grid or not
return(ok);
}
// Add a status line to the linked-list of status records
// associated with a station. Note that a blank status line is
// allowed, but we don't store that unless we have seen a non-blank
// status line previously.
//
void add_status(AprsDataRow *p_station, char *status_string) {
AprsCommentRow *ptr;
int add_it = 0;
int len;
len = strlen(status_string);
// Eliminate line-end chars
if (len > 1) {
if ( (status_string[len-1] == '\n')
|| (status_string[len-1] == '\r') ) {
status_string[len-1] = '\0';
}
}
// Shorten it
(void)remove_trailing_spaces(status_string);
(void)remove_leading_spaces(status_string);
len = strlen(status_string);
// Check for valid pointer
if (p_station != NULL) {
// We should probably create a new station record for this station
// if there isn't one. This allows us to collect as much info about
// a station as we can until a posit comes in for it. Right now we
// don't do this. If we decide to do this in the future, we also
// need a method to find out the info about that station without
// having to click on an icon, 'cuz the symbol won't be on our map
// until we have a posit.
//fprintf(stderr,"Station:%s\tStatus:%s\n",p_station->call_sign,status_string);
// Check whether we have any data stored for this station
if (p_station->status_data == NULL) {
if (len > 0) {
// No status stored yet and new status is non-NULL,
// so add it to the list.
add_it++;
}
}
else { // We have status data stored already
// Check for an identical string
AprsCommentRow *ptr2;
int ii = 0;
ptr = p_station->status_data;
ptr2 = ptr;
while (ptr != NULL) {
// Note that both text_ptr and comment_string can be
// empty strings.
if (strcasecmp(ptr->text_ptr, status_string) == 0) {
// Found a matching string
//fprintf(stderr,"Found match:
//%s:%s\n",p_station->call_sign,status_string);
// Instead of updating the timestamp, we'll delete the record from
// the list and add it to the top in the code below. Make sure to
// tweak the "ii" pointer so that we don't end up shortening the
// list unnecessarily.
if (ptr == p_station->status_data) {
// Only update the timestamp: We're at the
// beginning of the list already.
ptr->sec_heard = sec_now();
return; // No need to add a new record
}
else { // Delete the record
AprsCommentRow *ptr3;
// Keep a pointer to the record
ptr3 = ptr;
// Close the chain, skipping this record
ptr2->next = ptr3->next;
// Skip "ptr" over the record we wish to
// delete
ptr = ptr3->next;
// Free the record
free(ptr3->text_ptr);
free(ptr3);
// Muck with the counter 'cuz we just
// deleted one record
ii--;
}
}
ptr2 = ptr; // Back one record
if (ptr != NULL) {
ptr = ptr->next;
}
ii++;
}
// No matching string found, or new timestamp found for
// old record. Add it to the top of the list.
add_it++;
//fprintf(stderr,"No match:
//%s:%s\n",p_station->call_sign,status_string);
// We counted the records. If we have more than
// MAX_STATUS_LINES records we'll delete/free the last
// one to make room for the next. This keeps us from
// storing unique status records ad infinitum for active
// stations, limiting the total space used.
//
if (ii >= MAX_STATUS_LINES) {
// We know we didn't get a match, and that our list
// is full (as full as we want it to be). Traverse
// the list again, looking for ptr2->next->next ==
// NULL. If found, free last record and set the
// ptr2->next pointer to NULL.
ptr2 = p_station->status_data;
while (ptr2->next->next != NULL) {
ptr2 = ptr2->next;
}
// At this point, we have a pointer to the last
// record in ptr2->next. Free it and the text
// string in it.
free(ptr2->next->text_ptr);
free(ptr2->next);
ptr2->next = NULL;
}
}
if (add_it) { // We add to the beginning so we don't have
// to traverse the linked list. This also
// puts new records at the beginning of the
// list to keep them in sorted order.
ptr = p_station->status_data; // Save old pointer to records
p_station->status_data = (AprsCommentRow *)malloc(sizeof(AprsCommentRow));
CHECKMALLOC(p_station->status_data);
p_station->status_data->next = ptr; // Link in old records or NULL
// Malloc the string space we'll need, attach it to our
// new record
p_station->status_data->text_ptr = (char *)malloc(sizeof(char) * (len+1));
CHECKMALLOC(p_station->status_data->text_ptr);
// Fill in the string
xastir_snprintf(p_station->status_data->text_ptr,
len+1,
"%s",
status_string);
// Fill in the timestamp
p_station->status_data->sec_heard = sec_now();
//fprintf(stderr,"Station:%s\tStatus:%s\n\n",p_station->call_sign,p_station->status_data->text_ptr);
}
}
}
// Add a comment line to the linked-list of comment records
// associated with a station. Note that a blank comment is allowed
// and necessary for the times when we wish to blank out the comment
// on an object/item, but we don't store that unless we have seen a
// non-blank comment line previously.
//
void add_comment(AprsDataRow *p_station, char *comment_string) {
AprsCommentRow *ptr;
int add_it = 0;
int len;
len = strlen(comment_string);
// Eliminate line-end chars
if (len > 1) {
if ( (comment_string[len-1] == '\n')
|| (comment_string[len-1] == '\r') ) {
comment_string[len-1] = '\0';
}
}
// Shorten it
(void)remove_trailing_spaces(comment_string);
(void)remove_leading_spaces(comment_string);
len = strlen(comment_string);
// Check for valid pointer
if (p_station != NULL) {
// Check whether we have any data stored for this station
if (p_station->comment_data == NULL) {
if (len > 0) {
// No comments stored yet and new comment is
// non-NULL, so add it to the list.
add_it++;
}
}
else { // We have comment data stored already
// Check for an identical string
AprsCommentRow *ptr2;
int ii = 0;
ptr = p_station->comment_data;
ptr2 = ptr;
while (ptr != NULL) {
// Note that both text_ptr and comment_string can be
// empty strings.
if (strcasecmp(ptr->text_ptr, comment_string) == 0) {
// Found a matching string
//fprintf(stderr,"Found match: %s:%s\n",p_station->call_sign,comment_string);
// Instead of updating the timestamp, we'll delete the record from
// the list and add it to the top in the code below. Make sure to
// tweak the "ii" pointer so that we don't end up shortening the
// list unnecessarily.
if (ptr == p_station->comment_data) {
// Only update the timestamp: We're at the
// beginning of the list already.
ptr->sec_heard = sec_now();
return; // No need to add a new record
}
else { // Delete the record
AprsCommentRow *ptr3;
// Keep a pointer to the record
ptr3 = ptr;
// Close the chain, skipping this record
ptr2->next = ptr3->next;
// Skip "ptr" over the record we with to
// delete
ptr = ptr3->next;
// Free the record
free(ptr3->text_ptr);
free(ptr3);
// Muck with the counter 'cuz we just
// deleted one record
ii--;
}
}
ptr2 = ptr; // Keep this pointer one record back as
// we progress.
if (ptr != NULL) {
ptr = ptr->next;
}
ii++;
}
// No matching string found, or new timestamp found for
// old record. Add it to the top of the list.
add_it++;
//fprintf(stderr,"No match: %s:%s\n",p_station->call_sign,comment_string);
// We counted the records. If we have more than
// MAX_COMMENT_LINES records we'll delete/free the last
// one to make room for the next. This keeps us from
// storing unique comment records ad infinitum for
// active stations, limiting the total space used.
//
if (ii >= MAX_COMMENT_LINES) {
// We know we didn't get a match, and that our list
// is full (as we want it to be). Traverse the list
// again, looking for ptr2->next->next == NULL. If
// found, free that last record and set the
// ptr2->next pointer to NULL.
ptr2 = p_station->comment_data;
while (ptr2->next->next != NULL) {
ptr2 = ptr2->next;
}
// At this point, we have a pointer to the last
// record in ptr2->next. Free it and the text
// string in it.
free(ptr2->next->text_ptr);
free(ptr2->next);
ptr2->next = NULL;
}
}
if (add_it) { // We add to the beginning so we don't have
// to traverse the linked list. This also
// puts new records at the beginning of the
// list to keep them in sorted order.
ptr = p_station->comment_data; // Save old pointer to records
p_station->comment_data = (AprsCommentRow *)malloc(sizeof(AprsCommentRow));
CHECKMALLOC(p_station->comment_data);
p_station->comment_data->next = ptr; // Link in old records or NULL
// Malloc the string space we'll need, attach it to our
// new record
p_station->comment_data->text_ptr = (char *)malloc(sizeof(char) * (len+1));
CHECKMALLOC(p_station->comment_data->text_ptr);
// Fill in the string
xastir_snprintf(p_station->comment_data->text_ptr,
len+1,
"%s",
comment_string);
// Fill in the timestamp
p_station->comment_data->sec_heard = sec_now();
}
}
}
// Extract single weather data item from "data". Returns it in
// "temp". Modifies "data" to remove the found data from the
// string. Returns a 1 if found, 0 if not found.
//
// PE1DNN
// If the item is contained in the string but does not contain a
// value then regard the item as "not found" in the weather string.
//
int extract_weather_item(char *data, char type, int datalen, char *temp) {
int i,ofs,found,len;
//fprintf(stderr,"%s\n",data);
found=0;
len = (int)strlen(data);
for(ofs=0; !found && ofsweather_data == NULL) { // new weather data, allocate storage and init
fill->weather_data = malloc(sizeof(AprsWeatherRow));
if (fill->weather_data == NULL) {
fprintf(stderr,"Couldn't allocate memory in get_weather_record()\n");
ok = 0;
}
else {
init_weather(fill->weather_data);
}
}
return(ok);
}
// DK7IN 77
// raw weather report in information field
// positionless weather report in information field
// complete weather report with lat/lon
// see APRS Reference page 62ff
//
// Added 'F' for Fuel Temp and 'f' for Fuel Moisture in order to
// decode these two new parameters used for RAWS weather station
// objects.
//
// By the time we call this function we've already extracted any
// time/position info at the beginning of the string.
//
int extract_weather(AprsDataRow *p_station, char *data, int compr) {
char time_data[MAX_TIME];
char temp[5];
int ok = 1;
AprsWeatherRow *weather;
char course[4];
char speed[4];
int in_knots = 0;
//WE7U
// Try copying the string to a temporary string, then do some
// extractions to see if a few weather items are present? This
// would allow us to have the weather items in any order, and if
// enough of them were present, we consider it to be a weather
// packet? We'd need to qualify all of the data to make sure we had
// the proper number of digits for each. The trick is to make sure
// we don't decide it's a weather packet if it's not. We don't know
// what people might send in packets in the future.
if (compr) { // compressed position report
// Look for weather data in fixed locations first
if (strlen(data) >= 8
&& data[0] =='g' && is_weather_data(&data[1],3)
&& data[4] =='t' && is_weather_data(&data[5],3)) {
// Snag WX course/speed from compressed position data.
// This speed is in knots. This assumes that we've
// already extracted speed/course from the compressed
// packet. extract_comp_position() extracts
// course/speed as well.
xastir_snprintf(speed,
sizeof(speed),
"%s",
p_station->speed);
xastir_snprintf(course,
sizeof(course),
"%s",
p_station->course);
in_knots = 1;
//fprintf(stderr,"Found compressed wx\n");
}
// Look for weather data in non-fixed locations (RAWS WX
// Stations?)
else if ( strlen(data) >= 8
&& test_extract_weather_item(data,'g',3)
&& test_extract_weather_item(data,'t',3) ) {
// Snag WX course/speed from compressed position data.
// This speed is in knots. This assumes that we've
// already extracted speed/course from the compressed
// packet. extract_comp_position() extracts
// course/speed as well.
xastir_snprintf(speed,
sizeof(speed),
"%s",
p_station->speed);
xastir_snprintf(course,
sizeof(course),
"%s",
p_station->course);
in_knots = 1;
//fprintf(stderr,"Found compressed WX in non-fixed locations! %s:%s\n",
// p_station->call_sign,data);
}
else { // No weather data found
ok = 0;
//fprintf(stderr,"No compressed wx\n");
}
}
else { // Look for non-compressed weather data
// Look for weather data in defined locations first
if (strlen(data)>=15 && data[3]=='/'
&& is_weather_data(data,3) && is_weather_data(&data[4],3)
&& data[7] =='g' && is_weather_data(&data[8], 3)
&& data[11]=='t' && is_weather_data(&data[12],3)) { // Complete Weather Report
// Get speed/course. Speed is in knots.
(void)extract_speed_course(data,speed,course);
in_knots = 1;
// Either one not found? Try again.
if ( (speed[0] == '\0') || (course[0] == '\0') ) {
// Try to get speed/course from 's' and 'c' fields
// (another wx format). Speed is in mph.
(void)extract_weather_item(data,'c',3,course); // wind direction (in degrees)
(void)extract_weather_item(data,'s',3,speed); // sustained one-minute wind speed (in mph)
in_knots = 0;
}
//fprintf(stderr,"Found Complete Weather Report\n");
}
// Look for date/time and weather in fixed locations first
else if (strlen(data)>=16
&& data[0] =='c' && is_weather_data(&data[1], 3)
&& data[4] =='s' && is_weather_data(&data[5], 3)
&& data[8] =='g' && is_weather_data(&data[9], 3)
&& data[12]=='t' && is_weather_data(&data[13],3)) { // Positionless Weather Data
//fprintf(stderr,"Found positionless wx data\n");
// Try to snag speed/course out of first 7 bytes. Speed
// is in knots.
(void)extract_speed_course(data,speed,course);
in_knots = 1;
// Either one not found? Try again.
if ( (speed[0] == '\0') || (course[0] == '\0') ) {
//fprintf(stderr,"Trying again for course/speed\n");
// Also try to get speed/course from 's' and 'c' fields
// (another wx format)
(void)extract_weather_item(data,'c',3,course); // wind direction (in degrees)
(void)extract_weather_item(data,'s',3,speed); // sustained one-minute wind speed (in mph)
in_knots = 0;
}
//fprintf(stderr,"Found weather\n");
}
// Look for weather data in non-fixed locations (RAWS WX
// Stations?)
else if (strlen (data) >= 16
&& test_extract_weather_item(data,'h',2)
&& test_extract_weather_item(data,'g',3)
&& test_extract_weather_item(data,'t',3) ) {
// Try to snag speed/course out of first 7 bytes. Speed
// is in knots.
(void)extract_speed_course(data,speed,course);
in_knots = 1;
// Either one not found? Try again.
if ( (speed[0] == '\0') || (course[0] == '\0') ) {
// Also try to get speed/course from 's' and 'c' fields
// (another wx format)
(void)extract_weather_item(data,'c',3,course); // wind direction (in degrees)
(void)extract_weather_item(data,'s',3,speed); // sustained one-minute wind speed (in mph)
in_knots = 0;
}
//fprintf(stderr,"Found WX in non-fixed locations! %s:%s\n",
// p_station->call_sign,data);
}
else { // No weather data found
ok = 0;
//fprintf(stderr,"No wx found\n");
}
}
if (ok) {
ok = get_weather_record(p_station); // get existing or create new weather record
}
if (ok) {
weather = p_station->weather_data;
// Copy into weather speed variable. Convert knots to mph
// if necessary.
if (in_knots) {
xastir_snprintf(weather->wx_speed,
sizeof(weather->wx_speed),
"%03.0f",
atoi(speed) * 1.1508); // Convert knots to mph
}
else {
// Already in mph. Copy w/no conversion.
xastir_snprintf(weather->wx_speed,
sizeof(weather->wx_speed),
"%s",
speed);
}
xastir_snprintf(weather->wx_course,
sizeof(weather->wx_course),
"%s",
course);
if (compr) { // course/speed was taken from normal data, delete that
// fix me: we delete a potential real speed/course now
// we should differentiate between normal and weather data in compressed position decoding...
// p_station->speed_time[0] = '\0';
p_station->speed[0] = '\0';
p_station->course[0] = '\0';
}
(void)extract_weather_item(data,'g',3,weather->wx_gust); // gust (peak wind speed in mph in the last 5 minutes)
(void)extract_weather_item(data,'t',3,weather->wx_temp); // temperature (in deg Fahrenheit), could be negative
(void)extract_weather_item(data,'r',3,weather->wx_rain); // rainfall (1/100 inch) in the last hour
(void)extract_weather_item(data,'p',3,weather->wx_prec_24); // rainfall (1/100 inch) in the last 24 hours
(void)extract_weather_item(data,'P',3,weather->wx_prec_00); // rainfall (1/100 inch) since midnight
if (extract_weather_item(data,'h',2,weather->wx_hum)) // humidity (in %, 00 = 100%)
xastir_snprintf(weather->wx_hum, sizeof(weather->wx_hum), "%03d",(atoi(weather->wx_hum)+99)%100+1);
if (extract_weather_item(data,'b',5,weather->wx_baro)) // barometric pressure (1/10 mbar / 1/10 hPascal)
xastir_snprintf(weather->wx_baro,
sizeof(weather->wx_baro),
"%0.1f",
(float)(atoi(weather->wx_baro)/10.0));
// If we parsed a speed/course, a second 's' parameter means
// snowfall. Try to parse it, but only in the case where
// we've parsed speed out of this packet already.
if ( (speed[0] != '\0') && (course[0] != '\0') ) {
(void)extract_weather_item(data,'s',3,weather->wx_snow); // snowfall, inches in the last 24 hours
}
(void)extract_weather_item(data,'L',3,temp); // luminosity (in watts per square meter) 999 and below
(void)extract_weather_item(data,'l',3,temp); // luminosity (in watts per square meter) 1000 and above
(void)extract_weather_item(data,'#',3,temp); // raw rain counter
(void)extract_weather_item(data,'F',3,weather->wx_fuel_temp); // Fuel Temperature in °F (RAWS)
if (extract_weather_item(data,'f',2,weather->wx_fuel_moisture))// Fuel Moisture (RAWS) (in %, 00 = 100%)
xastir_snprintf(weather->wx_fuel_moisture,
sizeof(weather->wx_fuel_moisture),
"%03d",
(atoi(weather->wx_fuel_moisture)+99)%100+1);
// extract_weather_item(data,'w',3,temp); // ?? text wUII
// now there should be the name of the weather station...
// Create a timestamp from the current time
xastir_snprintf(weather->wx_time,
sizeof(weather->wx_time),
"%s",
get_time(time_data));
// Set the timestamp in the weather record so that we can
// decide whether or not to "ghost" the weather data later.
weather->wx_sec_time=sec_now();
// weather->wx_data=1; // we don't need this
// case ('.'):/* skip */
// wx_strpos+=4;
// break;
// default:
// wx_done=1;
// weather->wx_type=data[wx_strpos];
// if(strlen(data)>wx_strpos+1)
// xastir_snprintf(weather->wx_station,
// sizeof(weather->wx_station),
// "%s",
// data+wx_strpos+1);
// break;
}
return(ok);
}
// Initial attempt at decoding tropical storm, tropical depression,
// and hurricane data.
//
// This data can be in an Object report, but can also be in an Item
// or position report.
// "/TS" = Tropical Storm
// "/HC" = Hurricane
// "/TD" = Tropical Depression
// "/TY" = Typhoon
// "/ST" = Super Typhoon
// "/SC" = Severe Cyclone
// The symbol will be either "\@" for current position, or "/@" for
// predicted position.
//
int extract_storm(AprsDataRow *p_station, char *data, int compr) {
char time_data[MAX_TIME];
int ok = 1;
AprsWeatherRow *weather;
char course[4];
char speed[4]; // Speed in knots
char *p, *p2;
// Should probably encode the storm type in the weather object and
// print it out in plain text in the Station Info dialog.
if ((p = strstr(data, "/TS")) != NULL) {
// We have a Tropical Storm
//fprintf(stderr,"Tropical Storm! %s\n",data);
}
else if ((p = strstr(data, "/TD")) != NULL) {
// We have a Tropical Depression
//fprintf(stderr,"Tropical Depression! %s\n",data);
}
else if ((p = strstr(data, "/HC")) != NULL) {
// We have a Hurricane
//fprintf(stderr,"Hurricane! %s\n",data);
}
else if ((p = strstr(data, "/TY")) != NULL) {
// We have a Typhoon
//fprintf(stderr,"Hurricane! %s\n",data);
}
else if ((p = strstr(data, "/ST")) != NULL) {
// We have a Super Typhoon
//fprintf(stderr,"Hurricane! %s\n",data);
}
else if ((p = strstr(data, "/SC")) != NULL) {
// We have a Severe Cyclone
//fprintf(stderr,"Hurricane! %s\n",data);
}
else { // Not one of the three we're trying to decode
ok = 0;
return(ok);
}
//fprintf(stderr,"\n%s\n",data);
// Back up 7 spots to try to extract the next items
p2 = p - 7;
if (p2 >= data) {
// Attempt to extract course/speed. Speed in knots.
if (!extract_speed_course(p2,speed,course)) {
// No speed/course to extract
//fprintf(stderr,"No speed/course found\n");
ok = 0;
return(ok);
}
}
else { // Not enough characters for speed/course. Must have
// guessed wrong on what type of data it is.
//fprintf(stderr,"No speed/course found 2\n");
ok = 0;
return(ok);
}
//fprintf(stderr,"%s\n",data);
if (ok) {
// If we got this far, we have speed/course and know what type
// of storm it is.
//fprintf(stderr,"Speed: %s, Course: %s\n",speed,course);
ok = get_weather_record(p_station); // get existing or create new weather record
}
if (ok) {
// p_station->speed_time[0] = '\0';
p_station->weather_data->wx_storm = 1; // We found a storm
// Note that speed is in knots. If we were stuffing it into
// "wx_speed" we'd have to convert it to MPH.
if (strcmp(speed," ") != 0 && strcmp(speed,"...") != 0) {
xastir_snprintf(p_station->speed,
sizeof(p_station->speed),
"%s",
speed);
}
else {
p_station->speed[0] = '\0';
}
if (strcmp(course," ") != 0 && strcmp(course,"...") != 0)
xastir_snprintf(p_station->course,
sizeof(p_station->course),
"%s",
course);
else
p_station->course[0] = '\0';
weather = p_station->weather_data;
p2++; // Skip the description text, "/TS", "/HC", "/TD", "/TY", "/ST", or "/SC"
// Extract the sustained wind speed in knots
if(extract_weather_item(p2,'/',3,weather->wx_speed))
// Convert from knots to MPH
xastir_snprintf(weather->wx_speed,
sizeof(weather->wx_speed),
"%0.1f",
(float)(atoi(weather->wx_speed)) * 1.1508);
//fprintf(stderr,"%s\n",data);
// Extract gust speed in knots
if (extract_weather_item(p2,'^',3,weather->wx_gust)) // gust (peak wind speed in knots)
// Convert from knots to MPH
xastir_snprintf(weather->wx_gust,
sizeof(weather->wx_gust),
"%0.1f",
(float)(atoi(weather->wx_gust)) * 1.1508);
//fprintf(stderr,"%s\n",data);
// Pressure is already in millibars/hPa. No conversion
// needed.
if (extract_weather_item(p2,'/',4,weather->wx_baro)) // barometric pressure (1/10 mbar / 1/10 hPascal)
xastir_snprintf(weather->wx_baro,
sizeof(weather->wx_baro),
"%0.1f",
(float)(atoi(weather->wx_baro)));
//fprintf(stderr,"%s\n",data);
(void)extract_weather_item(p2,'>',3,weather->wx_hurricane_radius); // Nautical miles
//fprintf(stderr,"%s\n",data);
(void)extract_weather_item(p2,'&',3,weather->wx_trop_storm_radius); // Nautical miles
//fprintf(stderr,"%s\n",data);
(void)extract_weather_item(p2,'%',3,weather->wx_whole_gale_radius); // Nautical miles
//fprintf(stderr,"%s\n",data);
// Create a timestamp from the current time
xastir_snprintf(weather->wx_time,
sizeof(weather->wx_time),
"%s",
get_time(time_data));
// Set the timestamp in the weather record so that we can
// decide whether or not to "ghost" the weather data later.
weather->wx_sec_time=sec_now();
}
return(ok);
}
/*
* Free station memory for one entry
*/
void delete_station_memory(AprsDataRow *p_del) {
if (p_del == NULL)
return;
remove_name(p_del);
remove_time(p_del);
free(p_del);
station_count--;
}
/*
* Create new uninitialized element in station list
* and insert it before p_name after p_time entries.
*
* Returns NULL if malloc error.
*/
/*@null@*/ AprsDataRow *insert_new_station(AprsDataRow *p_name, AprsDataRow *p_time) {
AprsDataRow *p_new;
p_new = (AprsDataRow *)malloc(sizeof(AprsDataRow));
if (p_new != NULL) { // we really got the memory
p_new->call_sign[0] = '\0'; // just to be sure
p_new->n_next = NULL;
p_new->n_prev = NULL;
p_new->t_newer = NULL;
p_new->t_older = NULL;
insert_name(p_new,p_name); // insert element into name ordered list
insert_time(p_new,p_time); // insert element into time ordered list
}
else { // p_new == NULL
fprintf(stderr,"ERROR: we got no memory for station storage\n");
}
return(p_new); // return pointer to new element
}
// Station storage is done in a double-linked list. In fact there are two such
// pointer structures, one for sorting by name and one for sorting by time.
// We store both the pointers to the next and to the previous elements. DK7IN
/*
* Setup station storage structure
*/
void init_station_data(void) {
station_count = 0; // empty station list
n_first = NULL; // pointer to next element in name sorted list
n_last = NULL; // pointer to previous element in name sorted list
t_oldest = NULL; // pointer to oldest element in time sorted list
t_newest = NULL; // pointer to newest element in time sorted list
last_sec = sec_now(); // check value for detecting changed seconds in time
next_time_sn = 0; // serial number for unique time index
// current_trail_color = 0x00; // first trail color used will be 0x01
last_station_remove = sec_now(); // last time we checked for stations to remove
}
/*
* Initialize station data
*/
void init_station(AprsDataRow *p_station) {
// the list pointers should already be set
p_station->oldest_trackpoint = NULL; // no trail
p_station->newest_trackpoint = NULL; // no trail
p_station->trail_color = 0;
p_station->weather_data = NULL; // no weather
p_station->coord_lat = 0l; // 90°N \ undefined
p_station->coord_lon = 0l; // 180°W / position
p_station->pos_amb = 0; // No ambiguity
p_station->error_ellipse_radius = 600; // In cm, default 6 meters
p_station->lat_precision = 60; // In 100ths of seconds latitude (60 = 0.01 minutes)
p_station->lon_precision = 60; // In 100ths of seconds longitude (60 = 0.01 minutes)
p_station->call_sign[0] = '\0'; // ?????
p_station->tactical_call_sign = NULL;
p_station->sec_heard = 0;
p_station->time_sn = 0;
p_station->flag = 0; // set all flags to inactive
p_station->object_retransmit = -1; // transmit forever
p_station->last_transmit_time = sec_now(); // Used for object/item decaying algorithm
p_station->transmit_time_increment = 0; // Used in data_add()
// p_station->last_modified_time = 0; // Used for object/item dead-reckoning
p_station->record_type = '\0';
p_station->heard_via_tnc_port = 0;
p_station->heard_via_tnc_last_time = 0;
p_station->last_port_heard = 0;
p_station->num_packets = 0;
p_station->aprs_symbol.aprs_type = '\0';
p_station->aprs_symbol.aprs_symbol = '\0';
p_station->aprs_symbol.special_overlay = '\0';
p_station->aprs_symbol.area_object.type = AREA_NONE;
p_station->aprs_symbol.area_object.color = AREA_GRAY_LO;
p_station->aprs_symbol.area_object.sqrt_lat_off = 0;
p_station->aprs_symbol.area_object.sqrt_lon_off = 0;
p_station->aprs_symbol.area_object.corridor_width = 0;
// p_station->station_time_type = '\0';
p_station->origin[0] = '\0'; // no object
p_station->packet_time[0] = '\0';
p_station->node_path_ptr = NULL;
p_station->pos_time[0] = '\0';
// p_station->altitude_time[0] = '\0';
p_station->altitude[0] = '\0';
// p_station->speed_time[0] = '\0';
p_station->speed[0] = '\0';
p_station->course[0] = '\0';
p_station->bearing[0] = '\0';
p_station->NRQ[0] = '\0';
p_station->power_gain[0] = '\0';
p_station->signal_gain[0] = '\0';
p_station->signpost[0] = '\0';
p_station->probability_min[0] = '\0';
p_station->probability_max[0] = '\0';
// p_station->station_time[0] = '\0';
p_station->sats_visible[0] = '\0';
p_station->status_data = NULL;
p_station->comment_data = NULL;
p_station->df_color = -1;
// Show that there are no other points associated with this
// station. We could also zero all the entries of the
// multipoints[][] array, but nobody should be looking there
// unless this is non-zero.
// KG4NBB
p_station->num_multipoints = 0;
p_station->multipoint_data = NULL;
}
void init_tactical_hash(int clobber) {
// make sure we don't leak
if (tactical_hash) {
if (clobber) {
hashtable_destroy(tactical_hash, 1);
tactical_hash=create_hashtable(TACTICAL_HASH_SIZE,
tactical_hash_from_key,
tactical_keys_equal);
}
}
else {
tactical_hash=create_hashtable(TACTICAL_HASH_SIZE,
tactical_hash_from_key,
tactical_keys_equal);
}
}
char *get_tactical_from_hash(char *callsign) {
char *result;
if (callsign == NULL || *callsign == '\0') {
fprintf(stderr,"Empty callsign passed to get_tactical_from_hash()\n");
return(NULL);
}
if (!tactical_hash) { // no table to search
//fprintf(stderr,"Creating hash table\n");
init_tactical_hash(1); // so create one
return NULL;
}
// fprintf(stderr," searching for %s...",callsign);
result=hashtable_search(tactical_hash,callsign);
if (result) {
// fprintf(stderr,"\t\tFound it, %s, len=%d, %s\n",
// callsign,
// strlen(callsign),
// result);
} else {
// fprintf(stderr,"\t\tNot found, %s, len=%d\n",
// callsign,
// strlen(callsign));
}
return (result);
}
// Distance calculation (Great Circle) using the Haversine formula
// (2-parameter arctan version), which gives better accuracy than
// the "Law of Cosines" for short distances. It should be
// equivalent to the "Law of Cosines for Spherical Trigonometry" for
// longer distances. Haversine is a great-circle calculation.
//
//
// Inputs: lat1/long1/lat2/long2 in radians (double)
//
// Outputs: Distance in meters between them (double)
//
double calc_distance_haversine_radian(double lat1, double lon1, double lat2, double lon2) {
double dlon, dlat;
double a, c, d;
double R = EARTH_RADIUS_METERS;
#define square(x) (x)*(x)
dlon = lon2 - lon1;
dlat = lat2 - lat1;
a = square((sin(dlat/2.0))) + cos(lat1) * cos(lat2) * square((sin(dlon/2.0)));
c = 2.0 * atan2(sqrt(a), sqrt(1.0-a));
d = R * c;
return(d);
}
/*
* Insert existing element into name ordered list before p_name.
* If p_name is NULL then we add it to the end instead.
*/
void insert_name(AprsDataRow *p_new, AprsDataRow *p_name) {
// Set up pointer to next record (or NULL), sorted by name
p_new->n_next = p_name;
if (p_name == NULL) { // Add to end of list
p_new->n_prev = n_last;
if (n_last == NULL) // If we have an empty list
n_first = p_new; // Add it to the head of the list
else // List wasn't empty, add to the end of the list.
n_last->n_next = p_new;
n_last = p_new;
}
else { // Insert new record ahead of p_name record
p_new->n_prev = p_name->n_prev;
if (p_name->n_prev == NULL) // add to begin of list
n_first = p_new;
else
p_name->n_prev->n_next = p_new;
p_name->n_prev = p_new;
}
}
// Update all of the pointers so that they accurately reflect the
// current state of the station database.
//
// NOTE: This part of the code could be made smarter so that the
// pointers are updated whenever they are found to be out of whack,
// instead of zeroing all of them and starting from scratch each
// time. Alternate: Follow the current pointer if non-NULL then go
// up/down the list to find the current switchover point between
// letters.
//
// Better: Tie into the station insert function. If a new letter
// is inserted, or a new station at the beginning of a letter group,
// run this function to keep things up to date. That way we won't
// have to traverse in both directions to find a callsign in the
// search_station_name() function.
//
// If hash_key_in is -1, we need to redo all of the hash keys. If
// it is between 0 and 16383, then we need to redo just that one
// hash key. The 2nd parameter is either NULL for a removed record,
// or a pointer to a new station record in the case of an addition.
//
void station_shortcuts_update_function(int hash_key_in, AprsDataRow *p_rem) {
int ii;
AprsDataRow *ptr;
int prev_hash_key = 0x0000;
int hash_key;
// I just changed the function so that we can pass in the hash_key
// that we wish to update: We should be able to speed things up by
// updating one hash key instead of all 16384 pointers.
if ( (hash_key_in != -1)
&& (hash_key_in >= 0)
&& (hash_key_in < 16384) ) {
// We're adding/changing a hash key entry
station_shortcuts[hash_key_in] = p_rem;
//fprintf(stderr,"%i ",hash_key_in);
}
else { // We're removing a hash key entry.
// Clear and rebuild the entire hash table.
//??????????????????????????????????????????????????
// Clear all of the pointers before we begin????
//??????????????????????????????????????????????????
for (ii = 0; ii < 16384; ii++) {
station_shortcuts[ii] = NULL;
}
ptr = n_first; // Start of list
// Loop through entire list, writing the pointer into the
// station_shortcuts array whenever a new character is
// encountered. Do this until the end of the array or the end
// of the list.
//
while ( (ptr != NULL) && (prev_hash_key < 16384) ) {
// We create the hash key out of the lower 7 bits of the
// first two characters, creating a 14-bit key (1 of 16384)
//
hash_key = (int)((ptr->call_sign[0] & 0x7f) << 7);
hash_key = hash_key | (int)(ptr->call_sign[1] & 0x7f);
if (hash_key > prev_hash_key) {
// We found the next hash_key. Store the pointer at the
// correct location.
if (hash_key < 16384) {
station_shortcuts[hash_key] = ptr;
}
prev_hash_key = hash_key;
}
ptr = ptr->n_next;
}
}
}
/*
* Create new initialized element for call in station list
* and insert it before p_name after p_time entries.
*
* Returns NULL if mallc error.
*/
/*@null@*/ AprsDataRow *add_new_station(AprsDataRow *p_name, AprsDataRow *p_time, char *call) {
AprsDataRow *p_new;
int hash_key; // We use a 14-bit hash key
char *tactical_call;
if (call[0] == '\0') {
// Do nothing. No update needed. Callsign is empty.
return(NULL);
}
if(_aprs_show_new_station_alert)
{
const gchar *msg = g_strdup_printf("New station: %s", call);
hildon_banner_show_information(_window, NULL, msg);
}
p_new = insert_new_station(p_name,p_time); // allocate memory
if (p_new == NULL) {
// Couldn't allocate space for the station
return(NULL);
}
init_station(p_new); // initialize new station record
xastir_snprintf(p_new->call_sign,
sizeof(p_new->call_sign),
"%s",
call);
station_count++;
// Do some quick checks to see if we just inserted a new hash
// key or inserted at the beginning of a hash key (making the
// old pointer incorrect). If so, update our pointers to match.
// We create the hash key out of the lower 7 bits of the first
// two characters, creating a 14-bit key (1 of 16384)
//
hash_key = (int)((call[0] & 0x7f) << 7);
hash_key = hash_key | (int)(call[1] & 0x7f);
if (station_shortcuts[hash_key] == NULL) {
// New hash key entry point found. Fill in the pointer.
station_shortcuts_update_function(hash_key, p_new);
}
else if (p_new->n_prev == NULL) {
// We just inserted at the beginning of the list. Assume
// that we inserted at the beginning of our hash_key
// segment.
station_shortcuts_update_function(hash_key, p_new);
}
else {
// Check whether either of the first two chars of the new
// callsign and the previous callsign are different. If so,
// we need to update the hash table entry for our new record
// 'cuz we're at the start of a new hash table entry.
if (p_new->n_prev->call_sign[0] != call[0]
|| p_new->n_prev->call_sign[1] != call[1]) {
station_shortcuts_update_function(hash_key, p_new);
}
}
// Check whether we have a tactical call to assign to this
// station in our tactical hash table.
tactical_call = get_tactical_from_hash(call);
// If tactical call found and not blank
if (tactical_call && tactical_call[0] != '\0') {
// Malloc some memory to hold it in the station record.
p_new->tactical_call_sign = (char *)malloc(MAX_TACTICAL_CALL+1);
CHECKMALLOC(p_new->tactical_call_sign);
snprintf(p_new->tactical_call_sign,
MAX_TACTICAL_CALL+1,
"%s",
tactical_call);
//if (tactical_call[0] == '\0')
// fprintf(stderr,"Blank tactical call\n");
}
return(p_new); // return pointer to new element
}
/*
* Add data from APRS information field to station database
* Returns a 1 if successful
*/
int data_add(gint type,
gchar *call_sign,
gchar *path,
gchar *data,
TAprsPort port,
gchar *origin,
gint third_party,
gint station_is_mine,
gint object_is_mine) {
AprsDataRow *p_station;
AprsDataRow *p_time;
char call[MAX_CALLSIGN+1];
char new_station;
long last_lat, last_lon;
char last_alt[MAX_ALTITUDE];
char last_speed[MAX_SPEED+1];
char last_course[MAX_COURSE+1];
time_t last_stn_sec;
short last_flag;
char temp_data[40]; // short term string storage, MAX_CALLSIGN, ...
// long l_lat, l_lon;
double distance;
// char station_id[600];
int found_pos;
float value;
AprsWeatherRow *weather;
int moving;
int changed_pos;
int screen_update;
int ok, store;
int ok_to_display;
int compr_pos;
char *p = NULL; // KC2ELS - used for WIDEn-N
int direct = 0;
int object_is_mine_previous = 0;
int new_origin_is_mine = 0;
int num_digits = 0; // Number of digits after decimal point in NMEA string
// TODO update based on time
// static time_t lastScreenUpdate;
// call and path had been validated before
// Check "data" against the max APRS length, and dump the packet if too long.
if ( (data != NULL) && (strlen(data) > MAX_INFO_FIELD_SIZE) ) {
// Overly long packet. Throw it away.
return(0); // Not an ok packet
}
// Check for some reasonable string in call_sign parameter
if (call_sign == NULL || strlen(call_sign) == 0) {
return(0);
}
if (origin && is_my_call(origin, 1)) {
new_origin_is_mine++; // The new object/item is owned by me
}
weather = NULL; // only to make the compiler happy...
found_pos = 1;
snprintf(call,
sizeof(call),
"%s",
call_sign);
p_station = NULL;
new_station = (char)FALSE; // to make the compiler happy...
last_lat = 0L;
last_lon = 0L;
last_stn_sec = sec_now();
last_alt[0] = '\0';
last_speed[0] = '\0';
last_course[0] = '\0';
last_flag = 0;
ok = 0;
store = 0;
p_time = NULL; // add to end of time sorted list (newest)
compr_pos = 0;
if (search_station_name(&p_station,call,1))
{ // If we found the station in our list
// fprintf(stderr, "DEBUG: Station:,Found,:%s:\n", call);
// Check whether it's already a locally-owned object/item
if (is_my_object_item(p_station)) {
// We don't want to re-order it in the time-ordered list
// so that it'll expire from the queue normally. Don't
// call "move_station_time()" here.
// We need an exception later in this function for the
// case where we've moved an object/item (by how much?).
// We need to update the time in this case so that it'll
// expire later (in fact it could already be expired
// when we move it). We should be able to move expired
// objects/items to make them active again. Perhaps
// some other method as well?
new_station = (char)FALSE;
object_is_mine_previous++;
}
else {
move_station_time(p_station,p_time); // update time, change position in time sorted list
new_station = (char)FALSE; // we have seen this one before
}
if (is_my_station(p_station)) {
station_is_mine++; // Station/object/item is owned/controlled by me
}
}
else
{
// fprintf(stderr, "DEBUG: Station:,New,:%s:\n", call);
p_station = add_new_station(p_station,p_time,call); // create storage
new_station = (char)TRUE; // for new station
}
if (p_station != NULL)
{
last_lat = p_station->coord_lat; // remember last position
last_lon = p_station->coord_lon;
last_stn_sec = p_station->sec_heard;
snprintf(last_alt,
sizeof(last_alt),
"%s",
p_station->altitude);
snprintf(last_speed,
sizeof(last_speed),
"%s",
p_station->speed);
snprintf(last_course,
sizeof(last_course),
"%s",
p_station->course);
last_flag = p_station->flag;
// Wipe out old data so that it doesn't hang around forever
p_station->altitude[0] = '\0';
p_station->speed[0] = '\0';
p_station->course[0] = '\0';
ok = 1; // succeed as default
switch (type) {
case (APRS_MICE): // Mic-E format
case (APRS_FIXED): // '!'
case (APRS_MSGCAP): // '='
if (!extract_position(p_station,&data,type)) { // uncompressed lat/lon
compr_pos = 1;
if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
ok = 0;
else
p_station->pos_amb = 0; // No ambiguity in compressed posits
}
if (ok) {
// Create a timestamp from the current time
snprintf(p_station->pos_time,
sizeof(p_station->pos_time),
"%s", get_time(temp_data));
(void)extract_storm(p_station,data,compr_pos);
(void)extract_weather(p_station,data,compr_pos); // look for weather data first
process_data_extension(p_station,data,type); // PHG, speed, etc.
process_info_field(p_station,data,type); // altitude
if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
extract_multipoints(p_station, data, type, 1);
add_comment(p_station,data);
p_station->record_type = NORMAL_APRS;
if (type == APRS_MSGCAP)
p_station->flag |= ST_MSGCAP; // set "message capable" flag
else
p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
// Assign a non-default value for the error
// ellipse?
if (type == APRS_MICE || !compr_pos) {
p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
p_station->lat_precision = 60;
p_station->lon_precision = 60;
}
else {
p_station->error_ellipse_radius = 600; // Default of 6m
p_station->lat_precision = 6;
p_station->lon_precision = 6;
}
}
break;
/*
case (APRS_DOWN): // '/'
ok = extract_time(p_station, data, type); // we need a time
if (ok) {
if (!extract_position(p_station,&data,type)) { // uncompressed lat/lon
compr_pos = 1;
if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
ok = 0;
else
p_station->pos_amb = 0; // No ambiguity in compressed posits
}
}
if (ok) {
// Create a timestamp from the current time
xastir_snprintf(p_station->pos_time,
sizeof(p_station->pos_time),
"%s",
get_time(temp_data));
process_data_extension(p_station,data,type); // PHG, speed, etc.
process_info_field(p_station,data,type); // altitude
if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
extract_multipoints(p_station, data, type, 1);
add_comment(p_station,data);
p_station->record_type = DOWN_APRS;
p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
// Assign a non-default value for the error
// ellipse?
if (!compr_pos) {
p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
p_station->lat_precision = 60;
p_station->lon_precision = 60;
}
else {
p_station->error_ellipse_radius = 600; // Default of 6m
p_station->lat_precision = 6;
p_station->lon_precision = 6;
}
}
break;
*/
case (APRS_DF): // '@'
case (APRS_MOBILE): // '@'
ok = extract_time(p_station, data, type); // we need a time
if (ok) {
if (!extract_position(p_station,&data,type)) { // uncompressed lat/lon
compr_pos = 1;
if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
ok = 0;
else
p_station->pos_amb = 0; // No ambiguity in compressed posits
}
}
if (ok) {
process_data_extension(p_station,data,type); // PHG, speed, etc.
process_info_field(p_station,data,type); // altitude
if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
extract_multipoints(p_station, data, type, 1);
add_comment(p_station,data);
if(type == APRS_MOBILE)
p_station->record_type = MOBILE_APRS;
else
p_station->record_type = DF_APRS;
//@ stations have messaging per spec
p_station->flag |= (ST_MSGCAP); // set "message capable" flag
// Assign a non-default value for the error
// ellipse?
if (!compr_pos) {
p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
p_station->lat_precision = 60;
p_station->lon_precision = 60;
}
else {
p_station->error_ellipse_radius = 600; // Default of 6m
p_station->lat_precision = 6;
p_station->lon_precision = 6;
}
}
break;
case (APRS_GRID):
ok = extract_position(p_station, &data, type);
if (ok) {
process_info_field(p_station,data,type); // altitude
if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
extract_multipoints(p_station, data, type, 1);
add_comment(p_station,data);
// Assign a non-default value for the error
// ellipse?
// p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
// WE7U
// This needs to change based on the number of grid letters/digits specified
// p_station->lat_precision = 60;
// p_station->lon_precision = 60;
}
else {
}
break;
case (STATION_CALL_DATA):
p_station->record_type = NORMAL_APRS;
found_pos = 0;
break;
case (APRS_STATUS): // '>' Status Reports [APRS Reference, chapter 16]
(void)extract_time(p_station, data, type); // we need a time
// todo: could contain Maidenhead or beam heading+power
if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
extract_multipoints(p_station, data, type, 1);
add_status(p_station,data);
p_station->flag |= (ST_STATUS); // set "Status" flag
p_station->record_type = NORMAL_APRS; // ???
found_pos = 0;
break;
case (OTHER_DATA): // Other Packets [APRS Reference, chapter 19]
// non-APRS beacons, treated as status reports until we get a real one
if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
extract_multipoints(p_station, data, type, 1);
if ((p_station->flag & (~ST_STATUS)) == 0) { // only store if no status yet
add_status(p_station,data);
p_station->record_type = NORMAL_APRS; // ???
}
found_pos = 0;
break;
case (APRS_OBJECT):
// If old match is a killed Object (owner doesn't
// matter), new one is an active Object and owned by
// us, remove the old record and create a new one
// for storing this Object. Do the same for Items
// in the next section below.
//
// The easiest implementation might be to remove the
// old record and then call this routine again with
// the same parameters, which will cause a brand-new
// record to be created.
//
// The new record we're processing is an active
// object, as data_add() won't be called on a killed
// object.
//
// if ( is_my_call(origin,1) // If new Object is owned by me (including SSID)
if (new_origin_is_mine
&& !(p_station->flag & ST_ACTIVE)
&& (p_station->flag & ST_OBJECT) ) { // Old record was a killed Object
remove_name(p_station); // Remove old killed Object
// redo_list = (int)TRUE;
return( data_add(type, call_sign, path, data, port, origin, third_party, 1, 1) );
}
ok = extract_time(p_station, data, type); // we need a time
if (ok) {
if (!extract_position(p_station,&data,type)) { // uncompressed lat/lon
compr_pos = 1;
if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
ok = 0;
else
p_station->pos_amb = 0; // No ambiguity in compressed posits
}
}
p_station->flag |= ST_OBJECT; // Set "Object" flag
if (ok) {
// If object was owned by me but another station
// is transmitting it now, write entries into
// the object.log file showing that we don't own
// this object anymore.
// if ( (is_my_call(p_station->origin,1)) // If station was owned by me (including SSID)
// && (!is_my_call(origin,1)) ) { // But isn't now
// TODO
/*
if (is_my_object_item(p_station) // If station was owned by me (include SSID)
&& !new_origin_is_mine) { // But isn't now
disown_object_item(call_sign, origin);
}
*/
// If station is owned by me (including SSID)
// but it's a new object/item
// if ( (is_my_call(p_station->origin,1))
if (new_origin_is_mine
&& (p_station->transmit_time_increment == 0) ) {
// This will get us transmitting this object
// on the decaying algorithm schedule.
// We've transmitted it once if we've just
// gotten to this code.
p_station->transmit_time_increment = OBJECT_CHECK_RATE;
//fprintf(stderr,"data_add(): Setting transmit_time_increment to %d\n", OBJECT_CHECK_RATE);
}
// Create a timestamp from the current time
xastir_snprintf(p_station->pos_time,
sizeof(p_station->pos_time),
"%s",
get_time(temp_data));
xastir_snprintf(p_station->origin,
sizeof(p_station->origin),
"%s",
origin); // define it as object
(void)extract_storm(p_station,data,compr_pos);
(void)extract_weather(p_station,data,compr_pos); // look for wx info
process_data_extension(p_station,data,type); // PHG, speed, etc.
process_info_field(p_station,data,type); // altitude
if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
extract_multipoints(p_station, data, type, 0);
add_comment(p_station,data);
// the last char always was missing...
//p_station->comments[ strlen(p_station->comments) - 1 ] = '\0'; // Wipe out '\n'
// moved that to decode_ax25_line
// and don't added a '\n' in interface.c
p_station->record_type = NORMAL_APRS;
p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
// Assign a non-default value for the error
// ellipse?
if (!compr_pos) {
p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
p_station->lat_precision = 60;
p_station->lon_precision = 60;
}
else {
p_station->error_ellipse_radius = 600; // Default of 6m
p_station->lat_precision = 6;
p_station->lon_precision = 6;
}
}
break;
case (APRS_ITEM):
// If old match is a killed Item (owner doesn't
// matter), new one is an active Item and owned by
// us, remove the old record and create a new one
// for storing this Item. Do the same for Objects
// in the previous section above.
//
// The easiest implementation might be to remove the
// old record and then call this routine again with
// the same parameters, which will cause a brand-new
// record to be created.
//
// The new record we're processing is an active
// Item, as data_add() won't be called on a killed
// Item.
//
// if ( is_my_call(origin,1) // If new Item is owned by me (including SSID)
if (new_origin_is_mine
&& !(p_station->flag & ST_ACTIVE)
&& (p_station->flag & ST_ITEM) ) { // Old record was a killed Item
remove_name(p_station); // Remove old killed Item
// redo_list = (int)TRUE;
return( data_add(type, call_sign, path, data, port, origin, third_party, 1, 1) );
}
if (!extract_position(p_station,&data,type)) { // uncompressed lat/lon
compr_pos = 1;
if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
ok = 0;
else
p_station->pos_amb = 0; // No ambiguity in compressed posits
}
p_station->flag |= ST_ITEM; // Set "Item" flag
if (ok) {
// If item was owned by me but another station
// is transmitting it now, write entries into
// the object.log file showing that we don't own
// this item anymore.
// TODO
/*
// if ( (is_my_call(p_station->origin,1)) // If station was owned by me (including SSID)
// && (!is_my_call(origin,1)) ) { // But isn't now
if (is_my_object_item(p_station)
&& !new_origin_is_mine) { // But isn't now
disown_object_item(call_sign,origin);
}
*/
// If station is owned by me (including SSID)
// but it's a new object/item
// if ( (is_my_call(p_station->origin,1))
if (is_my_object_item(p_station)
&& (p_station->transmit_time_increment == 0) ) {
// This will get us transmitting this object
// on the decaying algorithm schedule.
// We've transmitted it once if we've just
// gotten to this code.
p_station->transmit_time_increment = OBJECT_CHECK_RATE;
//fprintf(stderr,"data_add(): Setting transmit_time_increment to %d\n", OBJECT_CHECK_RATE);
}
// Create a timestamp from the current time
xastir_snprintf(p_station->pos_time,
sizeof(p_station->pos_time),
"%s",
get_time(temp_data));
xastir_snprintf(p_station->origin,
sizeof(p_station->origin),
"%s",
origin); // define it as item
(void)extract_storm(p_station,data,compr_pos);
(void)extract_weather(p_station,data,compr_pos); // look for wx info
process_data_extension(p_station,data,type); // PHG, speed, etc.
process_info_field(p_station,data,type); // altitude
if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
extract_multipoints(p_station, data, type, 0);
add_comment(p_station,data);
// the last char always was missing...
//p_station->comments[ strlen(p_station->comments) - 1 ] = '\0'; // Wipe out '\n'
// moved that to decode_ax25_line
// and don't added a '\n' in interface.c
p_station->record_type = NORMAL_APRS;
p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
// Assign a non-default value for the error
// ellipse?
if (!compr_pos) {
p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
p_station->lat_precision = 60;
p_station->lon_precision = 60;
}
else {
p_station->error_ellipse_radius = 600; // Default of 6m
p_station->lat_precision = 6;
p_station->lon_precision = 6;
}
}
break;
case (APRS_WX1): // weather in '@' or '/' packet
ok = extract_time(p_station, data, type); // we need a time
if (ok) {
if (!extract_position(p_station,&data,type)) { // uncompressed lat/lon
compr_pos = 1;
if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
ok = 0;
else
p_station->pos_amb = 0; // No ambiguity in compressed posits
}
}
if (ok) {
(void)extract_storm(p_station,data,compr_pos);
(void)extract_weather(p_station,data,compr_pos);
p_station->record_type = (char)APRS_WX1;
process_info_field(p_station,data,type); // altitude
if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
extract_multipoints(p_station, data, type, 1);
add_comment(p_station,data);
// Assign a non-default value for the error
// ellipse?
if (!compr_pos) {
p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
p_station->lat_precision = 60;
p_station->lon_precision = 60;
}
else {
p_station->error_ellipse_radius = 600; // Default of 6m
p_station->lat_precision = 6;
p_station->lon_precision = 6;
}
}
break;
case (APRS_WX2): // '_'
ok = extract_time(p_station, data, type); // we need a time
if (ok) {
(void)extract_storm(p_station,data,compr_pos);
(void)extract_weather(p_station,data,0); // look for weather data first
p_station->record_type = (char)APRS_WX2;
found_pos = 0;
process_info_field(p_station,data,type); // altitude
if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
extract_multipoints(p_station, data, type, 1);
}
break;
case (APRS_WX4): // '#' Peet Bros U-II (km/h)
case (APRS_WX6): // '*' Peet Bros U-II (mph)
case (APRS_WX3): // '!' Peet Bros Ultimeter 2000 (data logging mode)
case (APRS_WX5): // '$ULTW' Peet Bros Ultimeter 2000 (packet mode)
if (get_weather_record(p_station)) { // get existing or create new weather record
weather = p_station->weather_data;
if (type == APRS_WX3) // Peet Bros Ultimeter 2000 data logging mode
{
decode_U2000_L (1,(unsigned char *)data,weather);
}
else if (type == APRS_WX5) // Peet Bros Ultimeter 2000 packet mode
decode_U2000_P(1,(unsigned char *)data,weather);
else // Peet Bros Ultimeter-II
decode_Peet_Bros(1,(unsigned char *)data,weather,type);
p_station->record_type = (char)type;
// Create a timestamp from the current time
xastir_snprintf(weather->wx_time,
sizeof(weather->wx_time),
"%s",
get_time(temp_data));
weather->wx_sec_time = sec_now();
found_pos = 0;
}
break;
// GPRMC, digits after decimal point
// ---------------------------------
// 2 = 25.5 meter error ellipse
// 3 = 6.0 meter error ellipse
// 4+ = 6.0 meter error ellipse
case (GPS_RMC): // $GPRMC
// WE7U
// Change this function to return HDOP and the number of characters
// after the decimal point.
ok = extract_RMC(p_station,data,call_sign,path,&num_digits);
if (ok) {
// Assign a non-default value for the error
// ellipse?
//
// WE7U
// Degrade based on the precision provided in the sentence. If only
// 2 digits after decimal point, give it 2550 as a radius (25.5m).
// Best (smallest) circle should be 600 as we have no augmentation
// flag to check here for anything better.
//
switch (num_digits) {
case 0:
p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
p_station->lat_precision = 6000;
p_station->lon_precision = 6000;
break;
case 1:
p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
p_station->lat_precision = 600;
p_station->lon_precision = 600;
break;
case 2:
p_station->error_ellipse_radius = 600; // Default of 6m
p_station->lat_precision = 60;
p_station->lon_precision = 60;
break;
case 3:
p_station->error_ellipse_radius = 600; // Default of 6m
p_station->lat_precision = 6;
p_station->lon_precision = 6;
break;
case 4:
case 5:
case 6:
case 7:
p_station->error_ellipse_radius = 600; // Default of 6m
p_station->lat_precision = 0;
p_station->lon_precision = 0;
break;
default:
p_station->error_ellipse_radius = 600; // Default of 6m
p_station->lat_precision = 60;
p_station->lon_precision = 60;
break;
}
}
break;
// GPGGA, digits after decimal point, w/o augmentation
// ---------------------------------------------------
// 2 = 25.5 meter error ellipse
// 3 = 6.0 meter error ellipse unless HDOP>4, then 10.0 meters
// 4+ = 6.0 meter error ellipse unless HDOP>4, then 10.0 meters
//
//
// GPGGA, digits after decimal point, w/augmentation
// --------------------------------------------------
// 2 = 25.5 meter error ellipse
// 3 = 2.5 meter error ellipse unless HDOP>4, then 10.0 meters
// 4+ = 0.6 meter error ellipse unless HDOP>4, then 10.0 meters
case (GPS_GGA): // $GPGGA
// WE7U
// Change this function to return HDOP and the number of characters
// after the decimal point.
ok = extract_GGA(p_station,data,call_sign,path,&num_digits);
if (ok) {
// Assign a non-default value for the error
// ellipse?
//
// WE7U
// Degrade based on the precision provided in the sentence. If only
// 2 digits after decimal point, give it 2550 as a radius (25.5m).
// 3 digits: 6m w/o augmentation unless HDOP >4 = 10m, 2.5m w/augmentation.
// 4+ digits: 6m w/o augmentation unless HDOP >4 = 10m, 0.6m w/augmentation.
//
switch (num_digits) {
case 0:
p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
p_station->lat_precision = 6000;
p_station->lon_precision = 6000;
break;
case 1:
p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
p_station->lat_precision = 600;
p_station->lon_precision = 600;
break;
case 2:
p_station->error_ellipse_radius = 600; // Default of 6m
p_station->lat_precision = 60;
p_station->lon_precision = 60;
break;
case 3:
p_station->error_ellipse_radius = 600; // Default of 6m
p_station->lat_precision = 6;
p_station->lon_precision = 6;
break;
case 4:
case 5:
case 6:
case 7:
p_station->error_ellipse_radius = 600; // Default of 6m
p_station->lat_precision = 0;
p_station->lon_precision = 0;
break;
default:
p_station->error_ellipse_radius = 600; // Default of 6m
p_station->lat_precision = 60;
p_station->lon_precision = 60;
break;
}
}
break;
// GPGLL, digits after decimal point
// ---------------------------------
// 2 = 25.5 meter error ellipse
// 3 = 6.0 meter error ellipse
// 4+ = 6.0 meter error ellipse
case (GPS_GLL): // $GPGLL
ok = extract_GLL(p_station,data,call_sign,path,&num_digits);
if (ok) {
// Assign a non-default value for the error
// ellipse?
//
// WE7U
// Degrade based on the precision provided in the sentence. If only
// 2 digits after decimal point, give it 2550 as a radius, otherwise
// give it 600.
//
switch (num_digits) {
case 0:
p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
p_station->lat_precision = 6000;
p_station->lon_precision = 6000;
break;
case 1:
p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
p_station->lat_precision = 600;
p_station->lon_precision = 600;
break;
case 2:
p_station->error_ellipse_radius = 600; // Default of 6m
p_station->lat_precision = 60;
p_station->lon_precision = 60;
break;
case 3:
p_station->error_ellipse_radius = 600; // Default of 6m
p_station->lat_precision = 6;
p_station->lon_precision = 6;
break;
case 4:
case 5:
case 6:
case 7:
p_station->error_ellipse_radius = 600; // Default of 6m
p_station->lat_precision = 0;
p_station->lon_precision = 0;
break;
default:
p_station->error_ellipse_radius = 600; // Default of 6m
p_station->lat_precision = 60;
p_station->lon_precision = 60;
break;
}
}
break;
default:
fprintf(stderr,"ERROR: UNKNOWN TYPE in data_add\n");
ok = 0;
break;
}
// Left this one in, just in case. Perhaps somebody might
// attach a multipoint string onto the end of a packet we
// might not expect. For this case we need to check whether
// we have multipoints first, as we don't want to erase the
// work we might have done with a previous call to
// extract_multipoints().
if (ok && (p_station->coord_lat > 0)
&& (p_station->coord_lon > 0)
&& (p_station->num_multipoints == 0) ) { // No multipoints found yet
extract_multipoints(p_station, data, type, 0);
}
}
if (!ok) { // non-APRS beacon, treat it as Other Packet [APRS Reference, chapter 19]
// GPRMC etc. without a position is here too, but it should not be stored as status!
// store it as status report until we get a real one
if ((p_station->flag & (~ST_STATUS)) == 0) { // only store it if no status yet
add_status(p_station,data-1);
p_station->record_type = NORMAL_APRS; // ???
}
ok = 1;
found_pos = 0;
}
curr_sec = sec_now();
if (ok) {
// data packet is valid
// announce own echo, we soon discard that packet...
// if (!new_station && is_my_call(p_station->call_sign,1) // Check SSID as well
if (!new_station
&& is_my_station(p_station) // Check SSID as well
&& strchr(path,'*') != NULL) {
upd_echo(path); // store digi that echoes my signal...
statusline("Echo from digipeater",0); // Echo from digipeater
}
// check if data is just a secondary echo from another digi
if ((last_flag & ST_VIATNC) == 0
|| (curr_sec - last_stn_sec) > 15
|| p_station->coord_lon != last_lon
|| p_station->coord_lat != last_lat)
store = 1; // don't store secondary echos
}
if (!ok && new_station)
delete_station_memory(p_station); // remove unused record
if (store) {
// we now have valid data to store into database
// make time index unique by adding a serial number
if (station_is_mine) {
// This station/object/item is owned by me. Set the
// flag which shows that we own/control this
// station/object/item. We use this flag later in lieu
// of the is_my_call() function in order to speed things
// up.
//
p_station->flag |= ST_MYSTATION;
}
// Check whether it's a locally-owned object/item
if ( object_is_mine
|| ( new_origin_is_mine
&& (p_station->flag & ST_ACTIVE)
&& (p_station->flag & ST_OBJECT) ) ) {
p_station->flag |= ST_MYOBJITEM;
// Do nothing else. We don't want to update the
// last-heard time so that it'll expire from the queue
// normally, unless it is a new object/item.
//
if (new_station) {
p_station->sec_heard = curr_sec;
}
// We need an exception later in this function for the
// case where we've moved an object/item (by how much?).
// We need to update the time in this case so that it'll
// expire later (in fact it could already be expired
// when we move it). We should be able to move expired
// objects/items to make them active again. Perhaps
// some other method as well?.
}
else {
// Reset the "my object" flag
p_station->flag &= ~ST_MYOBJITEM;
p_station->sec_heard = curr_sec; // Give it a new timestamp
}
if (curr_sec != last_sec) { // todo: if old time calculate time_sn from database
last_sec = curr_sec;
next_time_sn = 0; // restart time serial number
}
p_station->time_sn = next_time_sn++; // todo: warning if serial number too high
if (port == APRS_PORT_TTY) { // heard via TNC
if (!third_party) { // Not a third-party packet
p_station->flag |= ST_VIATNC; // set "via TNC" flag
p_station->heard_via_tnc_last_time = curr_sec;
p_station->heard_via_tnc_port = port;
}
else { // Third-party packet
// Leave the previous setting of "flag" alone.
// Specifically do NOT set the ST_VIATNC flag if it
// was a third-party packet.
}
}
else { // heard other than TNC
if (new_station) { // new station
p_station->flag &= (~ST_VIATNC); // clear "via TNC" flag
//fprintf(stderr,"New_station: Cleared ST_VIATNC flag: %s\n", p_station->call_sign);
p_station->heard_via_tnc_last_time = 0l;
}
}
p_station->last_port_heard = port;
// Create a timestamp from the current time
snprintf(p_station->packet_time, sizeof(p_station->packet_time),
"%s", get_time(temp_data)); // get_time returns value in temp_data
p_station->flag |= ST_ACTIVE;
if (third_party)
p_station->flag |= ST_3RD_PT; // set "third party" flag
else
p_station->flag &= (~ST_3RD_PT); // clear "third party" flag
if (origin != NULL && strcmp(origin,"INET") == 0) // special treatment for inet names
snprintf(p_station->origin,
sizeof(p_station->origin),
"%s",
origin); // to keep them separated from calls
if (origin != NULL && strcmp(origin,"INET-NWS") == 0) // special treatment for NWS
snprintf(p_station->origin,
sizeof(p_station->origin),
"%s",
origin); // to keep them separated from calls
if (origin == NULL || origin[0] == '\0') // normal call
p_station->origin[0] = '\0'; // undefine possible former object with same name
//--------------------------------------------------------------------
// KC2ELS
// Okay, here are the standards for ST_DIRECT:
// 1. The packet must have been received via TNC.
// 2. The packet must not have any * flags.
// 3. If present, the first WIDEn-N (or TRACEn-N) must have n=N.
// A station retains the ST_DIRECT setting. If
// "st_direct_timeout" seconds have passed since we set
// that bit then APRSD queries and displays based on the
// ST_DIRECT bit will skip that station.
// In order to make this scheme work for stations that straddle both
// RF and INET, we need to make sure that node_path_ptr doesn't get
// overwritten with an INET path if there's an RF path already in
// there and it has been less than st_direct_timeout seconds since
// the station was last heard on RF.
if ((port == APRS_PORT_TTY) // Heard via TNC
&& !third_party // Not a 3RD-Party packet
&& path != NULL // Path is not NULL
&& strchr(path,'*') == NULL) { // No asterisk found
// Look for WIDE or TRACE
if ((((p = strstr(path,"WIDE")) != NULL)
&& (p+=4)) ||
(((p = strstr(path,"TRACE")) != NULL)
&& (p+=5))) {
// Look for n=N on WIDEn-N/TRACEn-N digi field
if ((*p != '\0') && isdigit((int)*p)) {
if ((*(p+1) != '\0') && (*(p+1) == '-')) {
if ((*(p+2) != '\0') && g_ascii_isdigit(*(p+2))) {
if (*(p) == *(p+2)) {
direct = 1;
}
else {
direct = 0;
}
}
else {
direct = 0;
}
}
else {
direct = 0;
}
}
else {
direct = 1;
}
}
else {
direct = 1;
}
}
else {
direct = 0;
}
if (direct == 1) {
// This packet was heard direct. Set the ST_DIRECT bit
// and save the timestamp away.
p_station->direct_heard = curr_sec;
p_station->flag |= (ST_DIRECT);
}
else {
// This packet was NOT heard direct. Check whether we
// need to expire the ST_DIRECT bit. A lot of fixed
// stations transmit every 30 minutes. One hour gives
// us time to receive a direct packet from them among
// all the digipeated packets.
if ((p_station->flag & ST_DIRECT) != 0 &&
curr_sec > (p_station->direct_heard + st_direct_timeout)) {
p_station->flag &= (~ST_DIRECT);
}
}
// If heard on TNC, overwrite node_path_ptr if any of these
// conditions are met:
// *) direct == 1 (packet was heard direct)
// *) ST_DIRECT flag == 0 (packet hasn't been heard
// direct recently)
// *) ST_DIRECT is set, st_direct_timeout has expired
// (packet hasn't been heard direct recently)
//
// These rules will allow us to keep directly heard paths
// saved for at least an hour (st_direct_timeout), and not
// get overwritten with digipeated paths during that time.
//
if ((port == APRS_PORT_TTY) // Heard via TNC
&& !third_party // Not a 3RD-Party packet
&& path != NULL) { // Path is not NULL
// Heard on TNC interface and not third party. Check
// the other conditions listed in the comments above to
// decide whether we should overwrite the node_path_ptr
// variable.
//
if ( direct // This packet was heard direct
|| (p_station->flag & ST_DIRECT) == 0 // Not heard direct lately
|| ( (p_station->flag & ST_DIRECT) != 0 // Not heard direct lately
&& (curr_sec > (p_station->direct_heard+st_direct_timeout) ) ) ) {
// Free any old path we might have
if (p_station->node_path_ptr != NULL)
free(p_station->node_path_ptr);
// Malloc and store the new path
p_station->node_path_ptr = (char *)malloc(strlen(path) + 1);
CHECKMALLOC(p_station->node_path_ptr);
substr(p_station->node_path_ptr,path,strlen(path));
}
}
// If a 3rd-party packet heard on TNC, overwrite
// node_path_ptr only if heard_via_tnc_last_time is older
// than one hour (zero counts as well!), plus clear the
// ST_DIRECT and ST_VIATNC bits in this case. This makes us
// keep the RF path around for at least one hour after the
// station is heard.
//
else if ((port == APRS_PORT_TTY) // Heard via TNC
&& third_party // It's a 3RD-Party packet
&& path != NULL) { // Path is not NULL
// 3rd-party packet heard on TNC interface. Check if
// heard_via_tnc_last_time is older than an hour. If
// so, overwrite the path and clear a few bits to show
// that it has timed out on RF and we're now receiving
// that station from an igate.
//
if (curr_sec > (p_station->heard_via_tnc_last_time + 60*60)) {
// Yep, more than one hour old or is a zero,
// overwrite the node_path_ptr variable with the new
// one. We're only hearing this station on INET
// now.
// Free any old path we might have
if (p_station->node_path_ptr != NULL)
free(p_station->node_path_ptr);
// Malloc and store the new path
p_station->node_path_ptr = (char *)malloc(strlen(path) + 1);
CHECKMALLOC(p_station->node_path_ptr);
substr(p_station->node_path_ptr,path,strlen(path));
// Clear the ST_VIATNC bit
p_station->flag &= ~ST_VIATNC;
}
// If direct_heard is over an hour old, clear the
// ST_DIRECT flag. We're only hearing this station on
// INET now.
//
if (curr_sec > (p_station->direct_heard + st_direct_timeout)) {
// Yep, more than one hour old or is a zero, clear
// the ST_DIRECT flag.
p_station->flag &= ~ST_DIRECT;
}
}
// If heard on INET then overwrite node_path_ptr only if
// heard_via_tnc_last_time is older than one hour (zero
// counts as well!), plus clear the ST_DIRECT and ST_VIATNC
// bits in this case. This makes us keep the RF path around
// for at least one hour after the station is heard.
//
else if (port != APRS_PORT_TTY // From an INET interface
&& !third_party // Not a 3RD-Party packet
&& path != NULL) { // Path is not NULL
// Heard on INET interface. Check if
// heard_via_tnc_last_time is older than an hour. If
// so, overwrite the path and clear a few bits to show
// that it has timed out on RF and we're now receiving
// that station from the INET feeds.
//
if (curr_sec > (p_station->heard_via_tnc_last_time + 60*60)) {
// Yep, more than one hour old or is a zero,
// overwrite the node_path_ptr variable with the new
// one. We're only hearing this station on INET
// now.
// Free any old path we might have
if (p_station->node_path_ptr != NULL)
free(p_station->node_path_ptr);
// Malloc and store the new path
p_station->node_path_ptr = (char *)malloc(strlen(path) + 1);
CHECKMALLOC(p_station->node_path_ptr);
substr(p_station->node_path_ptr,path,strlen(path));
// Clear the ST_VIATNC bit
p_station->flag &= ~ST_VIATNC;
}
// If direct_heard is over an hour old, clear the
// ST_DIRECT flag. We're only hearing this station on
// INET now.
//
if (curr_sec > (p_station->direct_heard + st_direct_timeout)) {
// Yep, more than one hour old or is a zero, clear
// the ST_DIRECT flag.
p_station->flag &= ~ST_DIRECT;
}
}
//---------------------------------------------------------------------
p_station->num_packets += 1;
screen_update = 0;
if (new_station) {
if (strlen(p_station->speed) > 0 && atof(p_station->speed) > 0) {
p_station->flag |= (ST_MOVING); // it has a speed, so it's moving
moving = 1;
}
if (p_station->coord_lat != 0 && p_station->coord_lon != 0) { // discard undef positions from screen
if (!altnet || is_altnet(p_station) ) {
screen_update = 1;
// TODO - check needed
}
}
}
else { // we had seen this station before...
if (found_pos && position_defined(p_station->coord_lat,p_station->coord_lon,1)) { // ignore undefined and 0N/0E
if (p_station->newest_trackpoint != NULL) {
moving = 1; // it's moving if it has a trail
}
else {
if (strlen(p_station->speed) > 0 && atof(p_station->speed) > 0) {
moving = 1; // declare it moving, if it has a speed
}
else {
// Here's where we detect movement
if (position_defined(last_lat,last_lon,1)
&& (p_station->coord_lat != last_lat || p_station->coord_lon != last_lon)) {
moving = 1; // it's moving if it has changed the position
}
else {
moving = 0;
}
}
}
changed_pos = 0;
if (moving == 1) {
p_station->flag |= (ST_MOVING);
// we have a moving station, process trails
if (atoi(p_station->speed) < TRAIL_MAX_SPEED) { // reject high speed data (undef gives 0)
// we now may already have the 2nd position, so store the old one first
if (p_station->newest_trackpoint == NULL) {
if (position_defined(last_lat,last_lon,1)) { // ignore undefined and 0N/0E
(void)store_trail_point(p_station,
last_lon,
last_lat,
last_stn_sec,
last_alt,
last_speed,
last_course,
last_flag);
}
}
//if ( p_station->coord_lon != last_lon
// || p_station->coord_lat != last_lat ) {
// we don't store redundant points (may change this
// later ?)
//
// There are often echoes delayed 15 minutes
// or so it looks ugly on the trail, so I
// want to discard them This also discards
// immediate echoes. Duplicates back in time
// up to TRAIL_ECHO_TIME minutes are
// discarded.
//
if (!is_trailpoint_echo(p_station)) {
(void)store_trail_point(p_station,
p_station->coord_lon,
p_station->coord_lat,
p_station->sec_heard,
p_station->altitude,
p_station->speed,
p_station->course,
p_station->flag);
changed_pos = 1;
// Check whether it's a locally-owned object/item
if (object_is_mine) {
// Update time, change position in
// time-sorted list to change
// expiration time.
move_station_time(p_station,p_time);
// Give it a new timestamp
p_station->sec_heard = curr_sec;
//fprintf(stderr,"Updating last heard time\n");
}
}
}
// if (track_station_on == 1) // maybe we are tracking a station
// track_station(da,tracking_station_call,p_station);
} // moving...
// now do the drawing to the screen
ok_to_display = !altnet || is_altnet(p_station); // Optimization step, needed twice below.
// TODO
// screen_update = 0;
// if (changed_pos == 1 && Display_.trail && ((p_station->flag & ST_INVIEW) != 0)) {
// if (ok_to_display) {
// draw_trail(da,p_station,1); // update trail
// screen_update = 1;
// }
}
if (changed_pos == 1 || !position_defined(last_lat,last_lon,0)) {
if (ok_to_display) {
screen_update = 1; // TODO -check needed
}
}
} // defined position
}
if (screen_update) {
if (p_station->last_port_heard == APRS_PORT_TTY) { // Data from local TNC
redraw_on_new_data = 2; // Update all symbols NOW!
}
else {
redraw_on_new_data = 0; // Update each 60 secs
}
}
if(ok)
{
plot_aprs_station( p_station, TRUE );
}
return(ok);
} // End of data_add() function
int is_xnum_or_dash(char *data, int max){
int i;
int ok;
ok=1;
for(i=0; i= '0' && info[7] <= '9') // Overlay char
|| (info[7] >= 'A' && info[7] <= 'Z') ) { // Overlay char
// We're good, keep going
}
else { // Symbol table or overlay char incorrect
if (info[6] == '/' || info[6] == '\\') { // Found it back one char in string
// Don't print out the full info string here because it
// can contain unprintable characters. In fact, we
// should check the chars we do print out to make sure
// they're printable, else print a space char.
}
return(1); // No good, not MIC-E format or corrupted packet. Return 1
// so that it won't get added to the database at all.
}
// Check for valid symbol. Should be between '!' and '~' only.
if (info[6] < '!' || info[6] > '~') {
return(1); // No good, not MIC-E format or corrupted packet. Return 1
// so that it won't get added to the database at all.
}
// Check for minimum MIC-E size.
if (strlen(info) < 8) {
return(1); // No good, not MIC-E format or corrupted packet. Return 1
// so that it won't get added to the database at all.
}
// Check for 8-bit characters in the first eight slots. Not
// allowed per Mic-E chapter of the spec.
for (ii = 0; ii < 8; ii++) {
if ((unsigned char)info[ii] > 0x7f) {
// 8-bit data was found in the lat/long/course/speed
// portion. Bad packet. Drop it.
//fprintf(stderr, "%s: 8-bits found in Mic-E packet initial portion. Dropping it.\n", call_sign);
return(1);
}
}
// Check whether we have more data. If flag character is 0x1d
// (8-bit telemetry flag) then don't do the 8-bit check below.
if (strlen(info) > 8) {
// Check for the 8-bit telemetry flag
if ((unsigned char)info[8] == 0x1d) {
// 8-bit telemetry found, skip the check loop below
}
else { // 8-bit telemetry flag was not found. Check that
// we only have 7-bit characters through the rest of
// the packet.
for (ii = 8; ii < (int)strlen(info); ii++) {
if ((unsigned char)info[ii] > 0x7f) {
// 8-bit data was found. Bad packet. Drop it.
//fprintf(stderr, "%s: 8-bits found in Mic-E packet final portion (not 8-bit telemetry). Dropping it.\n", call_sign);
return(1);
}
}
}
}
//fprintf(stderr,"Path1:%s\n",path);
msg1 = (int)( ((unsigned char)path[0] & 0x40) >>4 );
msg2 = (int)( ((unsigned char)path[1] & 0x40) >>5 );
msg3 = (int)( ((unsigned char)path[2] & 0x40) >>6 );
msg = msg1 | msg2 | msg3; // We now have the complemented message number in one variable
msg = msg ^ 0x07; // And this is now the normal message number
msgtyp = 0; // DK7IN: Std message, I have to add custom msg decoding
//fprintf(stderr,"Msg: %d\n",msg);
/* Snag the latitude from the destination field, Assume TAPR-2 */
/* DK7IN: latitude now works with custom message */
s_b1 = (unsigned char)( (path[0] & 0x0f) + (char)0x2f );
//fprintf(stderr,"path0:%c\ts_b1:%c\n",path[0],s_b1);
if (path[0] & 0x10) // A-J
s_b1 += (unsigned char)1;
if (s_b1 > (unsigned char)0x39) // K,L,Z
s_b1 = (unsigned char)0x20;
//fprintf(stderr,"s_b1:%c\n",s_b1);
s_b2 = (unsigned char)( (path[1] & 0x0f) + (char)0x2f );
//fprintf(stderr,"path1:%c\ts_b2:%c\n",path[1],s_b2);
if (path[1] & 0x10) // A-J
s_b2 += (unsigned char)1;
if (s_b2 > (unsigned char)0x39) // K,L,Z
s_b2 = (unsigned char)0x20;
//fprintf(stderr,"s_b2:%c\n",s_b2);
s_b3 = (unsigned char)( (path[2] & (char)0x0f) + (char)0x2f );
if (path[2] & 0x10) // A-J
s_b3 += (unsigned char)1;
if (s_b3 > (unsigned char)0x39) // K,L,Z
s_b3 = (unsigned char)0x20;
s_b4 = (unsigned char)( (path[3] & 0x0f) + (char)0x30 );
if (s_b4 > (unsigned char)0x39) // L,Z
s_b4 = (unsigned char)0x20;
s_b5 = (unsigned char)( (path[4] & 0x0f) + (char)0x30 );
if (s_b5 > (unsigned char)0x39) // L,Z
s_b5 = (unsigned char)0x20;
s_b6 = (unsigned char)( (path[5] & 0x0f) + (char)0x30 );
//fprintf(stderr,"path5:%c\ts_b6:%c\n",path[5],s_b6);
if (s_b6 > (unsigned char)0x39) // L,Z
s_b6 = (unsigned char)0x20;
//fprintf(stderr,"s_b6:%c\n",s_b6);
s_b7 = (unsigned char)path[6]; // SSID, not used here
//fprintf(stderr,"path6:%c\ts_b7:%c\n",path[6],s_b7);
//fprintf(stderr,"\n");
// Special tests for 'L' due to position ambiguity deviances in
// the APRS spec table. 'L' has the 0x40 bit set, but they
// chose in the spec to have that represent position ambiguity
// _without_ the North/West/Long Offset bit being set. Yuk!
// Please also note that the tapr.org Mic-E document (not the
// APRS spec) has the state of the bit wrong in columns 2 and 3
// of their table. Reverse them.
if (path[3] == 'L')
north = 0;
else
north = (int)((path[3] & 0x40) == (char)0x40); // N/S Lat Indicator
if (path[4] == 'L')
long_offset = 0;
else
long_offset = (int)((path[4] & 0x40) == (char)0x40); // Longitude Offset
if (path[5] == 'L')
west = 0;
else
west = (int)((path[5] & 0x40) == (char)0x40); // W/E Long Indicator
//fprintf(stderr,"north:%c->%d\tlat:%c->%d\twest:%c->%d\n",path[3],north,path[4],long_offset,path[5],west);
/* Put the latitude string into the temp variable */
snprintf(temp, sizeof(temp), "%c%c%c%c.%c%c%c%c",s_b1,s_b2,s_b3,s_b4,s_b5,s_b6,
(north ? 'N': 'S'), info[7]); // info[7] = symbol table
/* Compute degrees longitude */
snprintf(new_info, sizeof(new_info), "%s", temp);
d = (int) info[0]-28;
if (long_offset)
d += 100;
if ((180<=d)&&(d<=189)) // ??
d -= 80;
if ((190<=d)&&(d<=199)) // ??
d -= 190;
/* Compute minutes longitude */
m = (int) info[1]-28;
if (m>=60)
m -= 60;
/* Compute hundredths of minutes longitude */
h = (int) info[2]-28;
/* Add the longitude string into the temp variable */
xastir_snprintf(temp, sizeof(temp), "%03d%02d.%02d%c%c",d,m,h,(west ? 'W': 'E'), info[6]);
strncat(new_info,
temp,
sizeof(new_info) - strlen(new_info));
/* Compute speed in knots */
speed = (int)( ( info[3] - (char)28 ) * (char)10 );
speed += ( (int)( (info[4] - (char)28) / (char)10) );
if (speed >= 800)
speed -= 800; // in knots
/* Compute course */
course = (int)( ( ( (info[4] - (char)28) % 10) * (char)100) + (info[5] - (char)28) );
if (course >= 400)
course -= 400;
/* ???
fprintf(stderr,"info[4]-28 mod 10 - 4 = %d\n",( ( (int)info[4]) - 28) % 10 - 4);
fprintf(stderr,"info[5]-28 = %d\n", ( (int)info[5]) - 28 );
*/
xastir_snprintf(temp, sizeof(temp), "%03d/%03d",course,speed);
strncat(new_info,
temp,
sizeof(new_info) - strlen(new_info));
offset = 8; // start of rest of info
/* search for rig type in Mic-E data */
rig_type[0] = '\0';
if (info[offset] != '\0' && (info[offset] == '>' || info[offset] == ']')) {
/* detected type code: > TH-D7 ] TM-D700 */
if (info[offset] == '>')
xastir_snprintf(rig_type,
sizeof(rig_type),
" TH-D7");
else
xastir_snprintf(rig_type,
sizeof(rig_type),
" TM-D700");
offset++;
}
info_size = (int)strlen(info);
/* search for compressed altitude in Mic-E data */ // {
if (info_size >= offset+4 && info[offset+3] == '}') { // {
/* detected altitude ___} */
alt = ((((long)info[offset] - (long)33) * (long)91 +(long)info[offset+1] - (long)33) * (long)91
+ (long)info[offset+2] - (long)33) - 10000; // altitude in meters
alt /= 0.3048; // altitude in feet, as in normal APRS
//32808 is -10000 meters, or 10 km (deepest ocean), which is as low as a MIC-E
//packet may go. Upper limit is mostly a guess.
if ( (alt > 500000) || (alt < -32809) ) { // Altitude is whacko. Skip it.
offset += 4;
}
else { // Altitude is ok
xastir_snprintf(temp, sizeof(temp), " /A=%06ld",alt);
offset += 4;
strncat(new_info,
temp,
sizeof(new_info) - strlen(new_info));
}
}
/* start of comment */
if (strlen(rig_type) > 0) {
xastir_snprintf(temp, sizeof(temp), "%s",rig_type);
strncat(new_info,
temp,
sizeof(new_info) - strlen(new_info));
}
strncat(new_info,
" Mic-E ",
sizeof(new_info) - strlen(new_info));
if (msgtyp == 0) {
switch (msg) {
case 1:
strncat(new_info,
"Enroute",
sizeof(new_info) - strlen(new_info));
break;
case 2:
strncat(new_info,
"In Service",
sizeof(new_info) - strlen(new_info));
break;
case 3:
strncat(new_info,
"Returning",
sizeof(new_info) - strlen(new_info));
break;
case 4:
strncat(new_info,
"Committed",
sizeof(new_info) - strlen(new_info));
break;
case 5:
strncat(new_info,
"Special",
sizeof(new_info) - strlen(new_info));
break;
case 6:
strncat(new_info,
"Priority",
sizeof(new_info) - strlen(new_info));
break;
case 7:
strncat(new_info,
"Emergency",
sizeof(new_info) - strlen(new_info));
// Functionality removed
break;
default:
strncat(new_info,
"Off Duty",
sizeof(new_info) - strlen(new_info));
}
}
else {
xastir_snprintf(temp, sizeof(temp), "Custom%d",msg);
strncat(new_info,
temp,
sizeof(new_info) - strlen(new_info));
}
if (info[offset] != '\0') {
/* Append the rest of the message to the expanded MIC-E message */
for (ii=offset; iiflag &= (~ST_ACTIVE); // clear flag
p_station->flag &= (~ST_INVIEW); // clear "In View" flag
if (position_on_screen(p_station->coord_lat,p_station->coord_lon))
redraw_on_new_data = 2; // redraw now
// there is some problem... it is not redrawn immediately! ????
// but deleted from list immediatetly
redo_list = (int)TRUE; // and update lists
}
*/
}
/*
* Decode APRS Information Field and dispatch it depending on the Data Type ID
*
* call = Callsign or object/item name string
* path = Path string
* message = Info field (corrupted already if object/item packet)
* origin = Originating callsign if object/item, otherwise NULL
* from = DATA_VIA_LOCAL/DATA_VIA_TNC/DATA_VIA_NET/DATA_VIA_FILE
* port = Port number
* third_party = Set to one if third-party packet
* orig_message = Unmodified info field
*
*/
void decode_info_field(gchar *call,
gchar *path,
gchar *message,
gchar *origin,
TAprsPort port,
gint third_party,
gchar *orig_message)
{
// char line[MAX_LINE_SIZE+1];
int ok_igate_net;
int ok_igate_rf;
int done, ignore;
char data_id;
int station_is_mine = 0;
int object_is_mine = 0;
/* remember fixed format starts with ! and can be up to 24 chars in the message */ // ???
done = 0; // if 1, packet was decoded
ignore = 0; // if 1, don't treat undecoded packets as status text
ok_igate_net = 0; // if 1, send packet to internet
ok_igate_rf = 0; // if 1, igate packet to RF if "from" is in nws-stations.txt
if ( is_my_call(call, 1) ) {
station_is_mine++; // Station is controlled by me
}
if ( (message != NULL) && (strlen(message) > MAX_LINE_SIZE) ) {
// Overly long message, throw it away.
done = 1;
}
else if (message == NULL || strlen(message) == 0) {
// we could have an empty message
(void)data_add(STATION_CALL_DATA,call,path,NULL,port,origin,third_party, station_is_mine, 0);
done = 1;
// don't report it to internet
}
// special treatment for objects/items.
if (!done && origin[0] != '\0') {
// If station/object/item is owned by me (including SSID)
if ( is_my_call(origin, 1) ) {
object_is_mine++;
}
if (message[0] == '*') { // set object
(void)data_add(APRS_OBJECT,call,path,message+1,port,origin,third_party, station_is_mine, object_is_mine);
if (strlen(origin) > 0 && strncmp(origin,"INET",4)!=0) {
ok_igate_net = 1; // report it to internet
}
ok_igate_rf = 1;
done = 1;
}
else if (message[0] == '!') {
// set item
(void)data_add(APRS_ITEM,call,path,message+1,port,origin,third_party, station_is_mine, object_is_mine);
if (strlen(origin) > 0 && strncmp(origin,"INET",4)!=0) {
ok_igate_net = 1; // report it to internet
}
ok_igate_rf = 1;
done = 1;
}
else if (message[0] == '_') { // delete object/item
// TODO
/*
AprsDataRow *p_station;
delete_object(call); // ?? does not vanish from map immediately !!???
// If object was owned by me but another station is
// transmitting it now, write entries into the
// object.log file showing that we don't own this object
// anymore.
p_station = NULL;
if (search_station_name(&p_station,call,1)) {
if (is_my_object_item(p_station) // If station was owned by me (including SSID)
&& (!object_is_mine) ) { // But isn't now
disown_object_item(call,origin);
}
}
if (strlen(origin) > 0 && strncmp(origin,"INET",4)!=0) {
ok_igate_net = 1; // report it to internet
}
ok_igate_rf = 1;
*/
done = 1;
}
}
if (!done) {
int rdf_type;
data_id = message[0]; // look at the APRS Data Type ID (first char in information field)
message += 1; // extract data ID from information field
ok_igate_net = 1; // as default report packet to internet
switch (data_id) {
case '=': // Position without timestamp (with APRS messaging)
//WE7U
// Need to check for weather info in this packet type as well?
done = data_add(APRS_MSGCAP,call,path,message,port,origin,third_party, station_is_mine, 0);
ok_igate_rf = done;
break;
case '!': // Position without timestamp (no APRS messaging) or Ultimeter 2000 WX
if (message[0] == '!' && is_xnum_or_dash(message+1,40)) // Ultimeter 2000 WX
done = data_add(APRS_WX3,call,path,message+1,port,origin,third_party, station_is_mine, 0);
else
done = data_add(APRS_FIXED,call,path,message,port,origin,third_party, station_is_mine, 0);
ok_igate_rf = done;
break;
case '/': // Position with timestamp (no APRS messaging)
//WE7U
// Need weather decode in this section similar to the '@' section
// below.
if ((toupper(message[14]) == 'N' || toupper(message[14]) == 'S') &&
(toupper(message[24]) == 'W' || toupper(message[24]) == 'E')) { // uncompressed format
if (message[29] == '/') {
if (message[33] == 'g' && message[37] == 't')
done = data_add(APRS_WX1,call,path,message,port,origin,third_party, station_is_mine, 0);
else
done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
}
else
done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
}
else { // compressed format
if (message[16] >= '!' && message[16] <= 'z') { // csT is speed/course
if (message[20] == 'g' && message[24] == 't') // Wx data
done = data_add(APRS_WX1,call,path,message,port,origin,third_party, station_is_mine, 0);
else
done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
}
else
done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
}
// done = data_add(APRS_DOWN,call,path,message,from,port,origin,third_party, station_is_mine, 0);
ok_igate_rf = done;
break;
case '@': // Position with timestamp (with APRS messaging)
// DK7IN: could we need to test the message length first?
if ((toupper(message[14]) == 'N' || toupper(message[14]) == 'S') &&
(toupper(message[24]) == 'W' || toupper(message[24]) == 'E')) { // uncompressed format
if (message[29] == '/') {
if (message[33] == 'g' && message[37] == 't')
done = data_add(APRS_WX1,call,path,message,port,origin,third_party, station_is_mine, 0);
else
done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
}
else
done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
}
else { // compressed format
if (message[16] >= '!' && message[16] <= 'z') { // csT is speed/course
if (message[20] == 'g' && message[24] == 't') // Wx data
done = data_add(APRS_WX1,call,path,message,port,origin,third_party, station_is_mine, 0);
else
done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
}
else
done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
}
ok_igate_rf = done;
break;
case '[': // Maidenhead grid locator beacon (obsolete- but used for meteor scatter)
done = data_add(APRS_GRID,call,path,message,port,origin,third_party, station_is_mine, 0);
ok_igate_rf = done;
break;
case 0x27: // Mic-E Old GPS data (or current GPS data in Kenwood TM-D700)
case 0x60: // Mic-E Current GPS data (but not used in Kennwood TM-D700)
//case 0x1c:// Mic-E Current GPS data (Rev. 0 beta units only)
//case 0x1d:// Mic-E Old GPS data (Rev. 0 beta units only)
done = decode_Mic_E(call,path,message,port,third_party);
ok_igate_rf = done;
break;
case '_': // Positionless weather data [APRS Reference, chapter 12]
done = data_add(APRS_WX2,call,path,message,port,origin,third_party, station_is_mine, 0);
ok_igate_rf = done;
break;
case '#': // Peet Bros U-II Weather Station (km/h) [APRS Reference, chapter 12]
if (is_xnum_or_dash(message,13))
done = data_add(APRS_WX4,call,path,message,port,origin,third_party, station_is_mine, 0);
ok_igate_rf = done;
break;
case '*': // Peet Bros U-II Weather Station (mph)
if (is_xnum_or_dash(message,13))
done = data_add(APRS_WX6,call,path,message,port,origin,third_party, station_is_mine, 0);
ok_igate_rf = done;
break;
case '$': // Raw GPS data or Ultimeter 2000
if (strncmp("ULTW",message,4) == 0 && is_xnum_or_dash(message+4,44))
done = data_add(APRS_WX5,call,path,message+4,port,origin,third_party, station_is_mine, 0);
else if (strncmp("GPGGA",message,5) == 0)
done = data_add(GPS_GGA,call,path,message,port,origin,third_party, station_is_mine, 0);
else if (strncmp("GPRMC",message,5) == 0)
done = data_add(GPS_RMC,call,path,message,port,origin,third_party, station_is_mine, 0);
else if (strncmp("GPGLL",message,5) == 0)
done = data_add(GPS_GLL,call,path,message,port,origin,third_party, station_is_mine, 0);
else {
// handle VTG and WPT too (APRS Ref p.25)
}
ok_igate_rf = done;
break;
case ':': // Message
// Do message logging if that feature is enabled.
done = decode_message(call,path,message,port,third_party);
// there could be messages I should not retransmit to internet... ??? Queries to me...
break;
case '>': // Status [APRS Reference, chapter 16]
done = data_add(APRS_STATUS,call,path,message,port,origin,third_party, station_is_mine, 0);
ok_igate_rf = done;
break;
case '?': // Query
done = process_query(call,path,message,port,third_party);
ignore = 1; // don't treat undecoded packets as status text
break;
case 'T': // Telemetry data [APRS Reference, chapter 13]
// We treat these as status packets currently.
ok_igate_rf = 1;
done = data_add(APRS_STATUS,call,path,message,port,origin,third_party, station_is_mine, 0);
break;
case '{': // User-defined APRS packet format //}
// We treat these as status packets currently.
ok_igate_rf = 1;
break;
case '<': // Station capabilities [APRS Reference, chapter 15]
//
// We could tweak the Incoming Data dialog to add
// filter togglebuttons. One such toggle could be
// "Station Capabilities". We'd then have a usable
// dialog for displaying things like ?IGATE?
// responses. In this case we wouldn't have to do
// anything special with the packet for decoding,
// just let it hit the default block below for
// putting them into the status field of the record.
// One downside is that we'd only be able to catch
// new station capability records in that dialog.
// The only way to look at past capability records
// would be the Station Info dialog for each
// station.
//
//fprintf(stderr,"%10s: %s\n", call, message);
// Don't set "done" as we want these to appear in
// the status text for the record.
break;
case '%': // Agrelo DFJr / MicroFinder Radio Direction Finding
// Here is where we'd add a call to an RDF decode
// function so that we could display vectors on the
// map for each RDF position.
//
// Agrelo format: "%XXX/Q"
//
// "XXX" is relative bearing to the signal (000-359). Careful here:
// At least one unit reports in magnetic instead of relative
// degrees. "000" means no direction info available, 360 means true
// north.
//
// "Q" is bearing quality (0-9). 0 = unsuitable. 9 = manually
// entered. 1-8 = varying quality with 8 being the best.
//
// I've also seen these formats, which may not be Agrelo compatible:
//
// "%136.0/9"
// "%136.0/8/158.0" (That last number is magnetic bearing)
//
// These sentences may be sent MULTIPLE times per second, like 20 or
// more! If we decide to average readings, we'll need to dump our
// averages and start over if our course changes.
//
// Check for Agrelo format:
if ( strlen(message) >= 5
&& is_num_chr(message[0]) // "%136/9"
&& is_num_chr(message[1])
&& is_num_chr(message[2])
&& message[3] == '/'
&& is_num_chr(message[4]) ) {
rdf_type = 1;
fprintf(stderr,
"Type 1 RDF packet from call: %s\tBearing: %c%c%c\tQuality: %c\n",
call,
message[0],
message[1],
message[2],
message[4]);
}
// Check for extended formats (not
// Agrelo-compatible):
else if (strlen(message) >= 13
&& is_num_chr(message[0]) // "%136.0/8/158.0"
&& is_num_chr(message[1])
&& is_num_chr(message[2])
&& message[3] == '.'
&& is_num_chr(message[4])
&& message[5] == '/'
&& is_num_chr(message[6])
&& message[7] == '/'
&& is_num_chr(message[8])
&& is_num_chr(message[9])
&& is_num_chr(message[10])
&& message[11] == '.'
&& is_num_chr(message[12]) ) {
rdf_type = 3;
fprintf(stderr,
"Type 3 RDF packet from call: %s\tBearing: %c%c%c%c%c\tQuality: %c\tMag Bearing: %c%c%c%c%c\n",
call,
message[0],
message[1],
message[2],
message[3],
message[4],
message[6],
message[8],
message[9],
message[10],
message[11],
message[12]);
}
// Check for extended formats (not
// Agrelo-compatible):
else if (strlen(message) >= 7
&& is_num_chr(message[0]) // "%136.0/9"
&& is_num_chr(message[1])
&& is_num_chr(message[2])
&& message[3] == '.'
&& is_num_chr(message[4])
&& message[5] == '/'
&& is_num_chr(message[6]) ) {
rdf_type = 2;
fprintf(stderr,
"Type 2 RDF packet from call: %s\tBearing: %c%c%c%c%c\tQuality: %c\n",
call,
message[0],
message[1],
message[2],
message[3],
message[4],
message[6]);
}
// Don't set "done" as we want these to appear in
// the status text for the record until we get the
// full decoding for this type of packet coded up.
break;
case '~': // UI-format messages, not relevant for APRS ("Do not use" in Reference)
case ',': // Invalid data or test packets [APRS Reference, chapter 19]
case '&': // Reserved -- Map Feature
ignore = 1; // Don't treat undecoded packets as status text
break;
}
// Add most remaining data to the station record as status
// info
//
if (!done && !ignore) { // Other Packets [APRS Reference, chapter 19]
done = data_add(OTHER_DATA,call,path,message-1,port,origin,third_party, station_is_mine, 0);
ok_igate_net = 0; // don't put data on internet ????
}
if (!done) { // data that we do ignore...
//fprintf(stderr,"decode_info_field: not decoding info: Call:%s ID:%c Msg:|%s|\n",call,data_id,message);
ok_igate_net = 0; // don't put data on internet
}
}
if (third_party)
ok_igate_net = 0; // don't put third party traffic on internet
if (station_is_mine)
ok_igate_net = 0; // don't put my data on internet ???
// TODO - Add TX to IGate support
/*
if (ok_igate_net) {
if ( (from == DATA_VIA_TNC) // Came in via a TNC
&& (strlen(orig_message) > 0) ) { // Not empty
// Here's where we inject our own callsign like this:
// "WE7U-15,I" in order to provide injection ID for our
// igate.
snprintf(line,
sizeof(line),
"%s>%s,%s,I:%s",
(strlen(origin)) ? origin : call,
path,
_aprs_mycall,
orig_message);
output_igate_net(line, port, third_party);
}
}
*/
}
/*
* Check for a valid object name
*/
int valid_object(char *name) {
int len, i;
// max 9 printable ASCII characters, case sensitive [APRS
// Reference]
len = (int)strlen(name);
if (len > 9 || len == 0)
return(0); // wrong size
for (i=0;i 1+9) {
substr(call,(*info)+1,9); // extract object name
(*info) = (*info) + 10;
// Remove leading spaces ? They look bad, but are allowed by the APRS Reference ???
(void)remove_trailing_spaces(call);
if (valid_object(call)) {
// info length is at least 1
ok = 1;
}
}
}
else if ((*info)[0] == ')') { // item
// 3 - 9 character item name with any printable ASCII character
if (strlen((*info)) > 1+3) {
for (i = 1; i <= 9; i++) {
if ((*info)[i] == '!' || (*info)[i] == '_') {
call[i-1] = '\0';
break;
}
call[i-1] = (*info)[i];
}
call[9] = '\0'; // In case we never saw '!' || '_'
(*info) = &(*info)[i];
// Remove leading spaces ? They look bad, but are allowed by the APRS Reference ???
//(void)remove_trailing_spaces(call); // This statement messed up our searching!!! Don't use it!
if (valid_object(call)) {
// info length is at least 1
ok = 1;
}
}
}
else
{
fprintf(stderr,"Not an object, nor an item!!! call=%s, info=%s, origin=%s.\n",
call, *info, origin);
}
return(ok);
}
/*
* Decode AX.25 line
* \r and \n should already be stripped from end of line
* line should not be NULL
*
* If dbadd is set, add to database. Otherwise, just return true/false
* to indicate whether input is valid AX25 line.
*/
//
// Note that the length of "line" can be up to MAX_DEVICE_BUFFER,
// which is currently set to 4096.
//
gint decode_ax25_line(gchar *line, TAprsPort port) {
gchar *call_sign;
gchar *path0;
gchar path[100+1]; // new one, we may add an '*'
gchar *info;
gchar info_copy[MAX_LINE_SIZE+1];
gchar call[MAX_CALLSIGN+1];
gchar origin[MAX_CALLSIGN+1];
gint ok;
gint third_party;
// gchar backup[MAX_LINE_SIZE+1];
// gchar tmp_line[MAX_LINE_SIZE+1];
// gchar tmp_path[100+1];
// gchar *ViaCalls[10];
if (line == NULL)
{
return(FALSE);
}
if ( strlen(line) > MAX_LINE_SIZE )
{
// Overly long message, throw it away. We're done.
return(FALSE);
}
if (line[strlen(line)-1] == '\n') // better: look at other places,
// so that we don't get it here...
line[strlen(line)-1] = '\0'; // Wipe out '\n', to be sure
if (line[strlen(line)-1] == '\r')
line[strlen(line)-1] = '\0'; // Wipe out '\r'
call_sign = NULL;
path0 = NULL;
info = NULL;
origin[0] = '\0';
call[0] = '\0';
path[0] = '\0';
third_party = 0;
// CALL>PATH:APRS-INFO-FIELD // split line into components
// ^ ^
ok = 0;
call_sign = (gchar*)strtok(line,">"); // extract call from AX.25 line
if (call_sign != NULL) {
path0 = (gchar*)strtok(NULL,":"); // extract path from AX.25 line
if (path0 != NULL) {
info = (gchar*)strtok(NULL,""); // rest is info_field
if (info != NULL) {
if ((info - path0) < 100) { // check if path could be copied
ok = 1; // we have found all three components
}
}
}
}
if (ok)
{
snprintf(path, sizeof(path), "%s", path0);
snprintf(info_copy, sizeof(info_copy), "%s", info);
ok = valid_path(path); // check the path and convert it to TAPR format
// Note that valid_path() also removes igate injection identifiers
}
if (ok)
{
extract_TNC_text(info); // extract leading text from TNC X-1J4
if (strlen(info) > 256) // first check if information field conforms to APRS specs
ok = 0; // drop packets too long
}
if (ok)
{ // check callsign
(void)remove_trailing_asterisk(call_sign); // is an asterisk valid here ???
if (valid_inet_name(call_sign,info,origin,sizeof(origin)))
{ // accept some of the names used in internet
snprintf(call, sizeof(call), "%s", call_sign);
}
else if (valid_call(call_sign))
{ // accept real AX.25 calls
snprintf(call, sizeof(call), "%s", call_sign);
}
else {
ok = 0;
}
}
if (ok && info[0] == '}')
{
// look for third-party traffic
ok = extract_third_party(call,path,sizeof(path),&info,origin,sizeof(origin)); // extract third-party data
third_party = 1;
}
if (ok && (info[0] == ';' || info[0] == ')'))
{
// look for objects or items
snprintf(origin, sizeof(origin), "%s", call);
ok = extract_object(call,&info,origin); // extract object data
}
if (ok)
{
// decode APRS information field, always called with valid call and path
// info is a string with 0 - 256 bytes
// fprintf(stderr,"dec: %s (%s) %s\n",call,origin,info);
decode_info_field(call,
path,
info,
origin,
port,
third_party,
info_copy);
}
return(ok);
}
///////////////////
double convert_lon_l2d(long lon)
{
double dResult = 0.00;
int ewpn;
float deg, min; //, sec;
int ideg; //, imin;
long temp;
deg = (float)(lon - 64800000l) / 360000.0;
// Switch to integer arithmetic to avoid floating-point rounding
// errors.
temp = (long)(deg * 100000);
ewpn = 1;
if (temp <= 0)
{
ewpn = -1;
temp = labs(temp);
}
ideg = (int)temp / 100000;
min = (temp % 100000) * 60.0 / 100000.0;
dResult = ewpn*(ideg+min/60.0);
return dResult;
}
// convert latitude from long to double
// Input is in Xastir coordinate system
double convert_lat_l2d(long lat)
{
double dResult = 0.00;
int nspn;
float deg, min;
int ideg;
long temp;
deg = (float)(lat - 32400000l) / 360000.0;
// Switch to integer arithmetic to avoid floating-point
// rounding errors.
temp = (long)(deg * 100000);
nspn = -1;
if (temp <= 0) {
nspn = 1;
temp = labs(temp);
}
ideg = (int)temp / 100000;
min = (temp % 100000) * 60.0 / 100000.0;
dResult = nspn*(ideg+min/60.0);
return dResult;
}
/***********************************************************/
/* returns the hour (00..23), localtime */
/***********************************************************/
int get_hours(void) {
struct tm *time_now;
time_t secs_now;
char shour[5];
secs_now=sec_now();
time_now = localtime(&secs_now);
(void)strftime(shour,4,"%H",time_now);
return(atoi(shour));
}
/***********************************************************/
/* returns the minute (00..59), localtime */
/***********************************************************/
int get_minutes(void) {
struct tm *time_now;
time_t secs_now;
char sminute[5];
secs_now=sec_now();
time_now = localtime(&secs_now);
(void)strftime(sminute,4,"%M",time_now);
return(atoi(sminute));
}
/**************************************************************/
/* compute_rain_hour - rolling average for the last 59.x */
/* minutes of rain. I/O numbers are in hundredths of an inch.*/
/* Output is in "rain_minute_total", a global variable. */
/**************************************************************/
void compute_rain_hour(float rain_total) {
int current, j;
float lowest;
// Deposit the _starting_ rain_total for each minute into a separate bucket.
// Subtract lowest rain_total in queue from current rain_total to get total
// for the last hour.
current = get_minutes(); // Fetch the current minute value. Use this as an index
// into our minute "buckets" where we store the data.
rain_minute[ (current + 1) % 60 ] = 0.0; // Zero out the next bucket (probably have data in
// there from the previous hour).
if (rain_minute[current] == 0.0) // If no rain_total stored yet in this minute's bucket
rain_minute[current] = rain_total; // Write into current bucket
// Find the lowest non-zero value for rain_total. The max value is "rain_total".
lowest = rain_total; // Set to maximum to get things going
for (j = 0; j < 60; j++) {
if ( (rain_minute[j] > 0.0) && (rain_minute[j] < lowest) ) { // Found a lower non-zero value?
lowest = rain_minute[j]; // Snag it
}
}
// Found it, subtract the two to get total for the last hour
rain_minute_total = rain_total - lowest;
}
/***********************************************************/
/* compute_rain - compute rain totals from the total rain */
/* so far. rain_total (in hundredths of an inch) keeps on */
/* incrementing. */
/***********************************************************/
void compute_rain(float rain_total) {
int current, i;
float lower;
// Skip the routine if input is outlandish (Negative value, zero, or 512 inches!).
// We seem to get occasional 0.0 packets from wx200d. This makes them go away.
if ( (rain_total <= 0.0) || (rain_total > 51200.0) )
return;
compute_rain_hour(rain_total);
current = get_hours();
// Set rain_base: The first rain_total for each hour.
if (rain_base[current] == 0.0) { // If we don't have a start value yet for this hour,
rain_base[current] = rain_total; // save it away.
rain_check = 0; // Set up rain_check so we'll do the following
// "else" clause a few times at start of each hour.
}
else { // rain_base has been set, is it wrong? We recheck three times at start of hour.
if (rain_check < 3) {
rain_check++;
// Is rain less than base? It shouldn't be.
if (rain_total < rain_base[current])
rain_base[current] = rain_total;
// Difference greater than 10 inches since last reading? It shouldn't be.
if (fabs(rain_total - rain_base[current]) > 1000.0) // Remember: Hundredths of an inch
rain_base[current] = rain_total;
}
}
rain_base[ (current + 1) % 24 ] = 0.0; // Clear next hour's index.
// Compute total rain in last 24 hours: Really we'll compute the total rain
// in the last 23 hours plus the portion of an hour we've completed (Sum up
// all 24 of the hour totals). This isn't the perfect way to do it, but to
// really do it right we'd need finer increments of time (to get closer to
// the goal of 24 hours of rain).
lower = rain_total;
for ( i = 0; i < 24; i++ ) { // Find the lowest non-zero rain_base value in last 24 hours
if ( (rain_base[i] > 0.0) && (rain_base[i] < lower) ) {
lower = rain_base[i];
}
}
rain_24 = rain_total - lower; // Hundredths of an inch
// Compute rain since midnight. Note that this uses whatever local time was set
// on the machine. It might not be local midnight if your box is set to GMT.
lower = rain_total;
for ( i = 0; i <= current; i++ ) { // Find the lowest non-zero rain_base value since midnight
if ( (rain_base[i] > 0.0) && (rain_base[i] < lower) ) {
lower = rain_base[i];
}
}
rain_00 = rain_total - lower; // Hundredths of an inch
// It is the responsibility of the calling program to save
// the new totals in the data structure for our station.
// We don't return anything except in global variables.
}
/**************************************************************/
/* compute_gust - compute max wind gust during last 5 minutes */
/* */
/**************************************************************/
float compute_gust(float wx_speed, float last_speed, time_t *last_speed_time) {
float computed_gust;
int current, j;
// Deposit max gust for each minute into a different bucket.
// Check all buckets for max gust within the last five minutes
// (Really 4 minutes plus whatever portion of a minute we've completed).
current = get_minutes(); // Fetch the current minute value. We use this as an index
// into our minute "buckets" where we store the data.
// If we haven't started collecting yet, set up to do so
if (gust_read_ptr == gust_write_ptr) { // We haven't started yet
gust_write_ptr = current; // Set to write into current bucket
gust_last_write = current;
gust_read_ptr = current - 1; // Set read pointer back one, modulus 60
if (gust_read_ptr < 0)
gust_read_ptr = 59;
gust[gust_write_ptr] = 0.0; // Zero the max gust
gust[gust_read_ptr] = 0.0; // for both buckets.
//WE7U: Debug
//gust[gust_write_ptr] = 45.9;
}
// Check whether we've advanced at least one minute yet
if (current != gust_write_ptr) { // We've advanced to a different minute
gust_write_ptr = current; // Start writing into a new bucket.
gust[gust_write_ptr] = 0.0; // Zero the new bucket
// Check how many bins of real data we have currently. Note that this '5' is
// correct, as we just advanced "current" to the next minute. We're just pulling
// along the read_ptr behind us if we have 5 bins worth of data by now.
if ( ((gust_read_ptr + 5) % 60) == gust_write_ptr) // We have 5 bins of real data
gust_read_ptr = (gust_read_ptr + 1) % 60; // So advance the read pointer,
// Check for really bad pointers, perhaps the weather station got
// unplugged for a while or it's just REALLY slow at sending us data?
// We're checking to see if gust_last_write happened in the previous
// minute. If not, we skipped a minute or more somewhere.
if ( ((gust_last_write + 1) % 60) != current ) {
// We lost some time somewhere: Reset the pointers, older gust data is
// lost. Start over collecting new gust data.
gust_read_ptr = current - 1; // Set read pointer back one, modulus 60
if (gust_read_ptr < 0)
gust_read_ptr = 59;
gust[gust_read_ptr] = 0.0;
}
gust_last_write = current;
}
// Is current wind speed higher than the current minute bucket?
if (wx_speed > gust[gust_write_ptr])
gust[gust_write_ptr] = wx_speed; // Save it in the bucket
// Read the last (up to) five buckets and find the max gust
computed_gust=gust[gust_write_ptr];
j = gust_read_ptr;
while (j != ((gust_write_ptr + 1) % 60) ) {
if ( computed_gust < gust[j] )
computed_gust = gust[j];
j = (j + 1) % 60;
}
*last_speed_time = sec_now();
return(computed_gust);
}
//*********************************************************************
// Decode Peet Brothers Ultimeter 2000 weather data (Data logging mode)
//
// This function is called from db.c:data_add() only. Used for
// decoding incoming packets, not for our own weather station data.
//
// The Ultimeter 2000 can be in any of three modes, Data Logging Mode,
// Packet Mode, or Complete Record Mode. This routine handles only
// the Data Logging Mode.
//*********************************************************************
void decode_U2000_L (int from, unsigned char *data, AprsWeatherRow *weather) {
time_t last_speed_time;
float last_speed;
float computed_gust;
char temp_data1[10];
char *temp_conv;
char format;
last_speed = 0.0;
last_speed_time = 0;
computed_gust = 0.0;
format = 0;
weather->wx_type = WX_TYPE;
xastir_snprintf(weather->wx_station,
sizeof(weather->wx_station),
"U2k");
/* get last gust speed */
if (strlen(weather->wx_gust) > 0 && !from) {
/* get last speed */
last_speed = (float)atof(weather->wx_gust);
last_speed_time = weather->wx_speed_sec_time;
}
// 006B 00 58
// 00A4 00 46 01FF 380E 2755 02C1 03E8 ---- 0052 04D7 0001 007BM
// ^ ^ ^ ^ ^ ^ ^
// 0 6 8 12 16 24 40
/* wind speed */
if (data[0] != '-') { // '-' signifies invalid data
substr(temp_data1,(char *)data,4);
xastir_snprintf(weather->wx_speed,
sizeof(weather->wx_speed),
"%03d",
(int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
if (from) {
weather->wx_speed_sec_time = sec_now();
} else {
/* local station */
computed_gust = compute_gust((float)atof(weather->wx_speed),
last_speed,
&last_speed_time);
weather->wx_speed_sec_time = sec_now();
xastir_snprintf(weather->wx_gust,
sizeof(weather->wx_gust),
"%03d",
(int)(0.5 + computed_gust)); // Cheater's way of rounding
}
} else {
if (!from)
weather->wx_speed[0] = 0;
}
/* wind direction */
//
// Note that the first two digits here may be 00, or may be FF
// if a direction calibration has been entered. We should zero
// them.
//
if (data[6] != '-') { // '-' signifies invalid data
substr(temp_data1,(char *)(data+6),2);
temp_data1[0] = '0';
temp_data1[1] = '0';
xastir_snprintf(weather->wx_course,
sizeof(weather->wx_course),
"%03d",
(int)(((float)strtol(temp_data1,&temp_conv,16)/256.0)*360.0));
} else {
xastir_snprintf(weather->wx_course,
sizeof(weather->wx_course),
"000");
if (!from)
weather->wx_course[0]=0;
}
/* outdoor temp */
if (data[8] != '-') { // '-' signifies invalid data
int temp4;
substr(temp_data1,(char *)(data+8),4);
temp4 = (int)strtol(temp_data1,&temp_conv,16);
if (temp_data1[0] > '7') { // Negative value, convert
temp4 = (temp4 & (temp4-0x7FFF)) - 0x8000;
}
xastir_snprintf(weather->wx_temp,
sizeof(weather->wx_temp),
"%03d",
(int)((float)((temp4<<16)/65536)/10.0));
}
else {
if (!from)
weather->wx_temp[0]=0;
}
/* rain total long term */
if (data[12] != '-') { // '-' signifies invalid data
substr(temp_data1,(char *)(data+12),4);
xastir_snprintf(weather->wx_rain_total,
sizeof(weather->wx_rain_total),
"%0.2f",
(float)strtol(temp_data1,&temp_conv,16)/100.0);
if (!from) {
/* local station */
compute_rain((float)atof(weather->wx_rain_total));
/*last hour rain */
xastir_snprintf(weather->wx_rain,
sizeof(weather->wx_rain),
"%0.2f",
rain_minute_total);
/*last 24 hour rain */
xastir_snprintf(weather->wx_prec_24,
sizeof(weather->wx_prec_24),
"%0.2f",
rain_24);
/* rain since midnight */
xastir_snprintf(weather->wx_prec_00,
sizeof(weather->wx_prec_00),
"%0.2f",
rain_00);
}
} else {
if (!from)
weather->wx_rain_total[0]=0;
}
/* baro */
if (data[16] != '-') { // '-' signifies invalid data
substr(temp_data1,(char *)(data+16),4);
xastir_snprintf(weather->wx_baro,
sizeof(weather->wx_baro),
"%0.1f",
(float)strtol(temp_data1,&temp_conv,16)/10.0);
} else {
if (!from)
weather->wx_baro[0]=0;
}
/* outdoor humidity */
if (data[24] != '-') { // '-' signifies invalid data
substr(temp_data1,(char *)(data+24),4);
xastir_snprintf(weather->wx_hum,
sizeof(weather->wx_hum),
"%03d",
(int)((float)strtol(temp_data1,&temp_conv,16)/10.0));
} else {
if (!from)
weather->wx_hum[0]=0;
}
/* todays rain total */
if (data[40] != '-') { // '-' signifies invalid data
if (from) {
substr(temp_data1,(char *)(data+40),4);
xastir_snprintf(weather->wx_prec_00,
sizeof(weather->wx_prec_00),
"%0.2f",
(float)strtol(temp_data1,&temp_conv,16)/100.0);
}
} else {
if (!from)
weather->wx_prec_00[0] = 0;
}
}
//********************************************************************
// Decode Peet Brothers Ultimeter 2000 weather data (Packet mode)
//
// This function is called from db.c:data_add() only. Used for
// decoding incoming packets, not for our own weather station data.
//
// The Ultimeter 2000 can be in any of three modes, Data Logging Mode,
// Packet Mode, or Complete Record Mode. This routine handles only
// the Packet Mode.
//********************************************************************
void decode_U2000_P(int from, unsigned char *data, AprsWeatherRow *weather) {
time_t last_speed_time;
float last_speed;
float computed_gust;
char temp_data1[10];
char *temp_conv;
int len;
last_speed = 0.0;
last_speed_time = 0;
computed_gust = 0.0;
len = (int)strlen((char *)data);
weather->wx_type = WX_TYPE;
xastir_snprintf(weather->wx_station,
sizeof(weather->wx_station),
"U2k");
/* get last gust speed */
if (strlen(weather->wx_gust) > 0 && !from) {
/* get last speed */
last_speed = (float)atof(weather->wx_gust);
last_speed_time = weather->wx_speed_sec_time;
}
// $ULTW 0031 00 37 02CE 0069 ---- 0000 86A0 0001 ---- 011901CC 0000 0005
// ^ ^ ^ ^ ^ ^ ^ ^
// 0 6 8 12 16 32 44 48
/* wind speed peak over last 5 min */
if (data[0] != '-') { // '-' signifies invalid data
substr(temp_data1,(char *)data,4);
if (from) {
xastir_snprintf(weather->wx_gust,
sizeof(weather->wx_gust),
"%03d",
(int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
/* this may be the only wind data */
xastir_snprintf(weather->wx_speed,
sizeof(weather->wx_speed),
"%03d",
(int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
} else {
/* local station and may be the only wind data */
if (len < 51) {
xastir_snprintf(weather->wx_speed,
sizeof(weather->wx_speed),
"%03d",
(int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
computed_gust = compute_gust((float)atof(weather->wx_speed),
last_speed,
&last_speed_time);
weather->wx_speed_sec_time = sec_now();
xastir_snprintf(weather->wx_gust,
sizeof(weather->wx_gust),
"%03d",
(int)(0.5 + computed_gust));
}
}
} else {
if (!from)
weather->wx_gust[0] = 0;
}
/* wind direction */
//
// Note that the first two digits here may be 00, or may be FF
// if a direction calibration has been entered. We should zero
// them.
//
if (data[6] != '-') { // '-' signifies invalid data
substr(temp_data1,(char *)(data+6),2);
temp_data1[0] = '0';
temp_data1[1] = '0';
xastir_snprintf(weather->wx_course,
sizeof(weather->wx_course),
"%03d",
(int)(((float)strtol(temp_data1,&temp_conv,16)/256.0)*360.0));
} else {
xastir_snprintf(weather->wx_course,
sizeof(weather->wx_course),
"000");
if (!from)
weather->wx_course[0] = 0;
}
/* outdoor temp */
if (data[8] != '-') { // '-' signifies invalid data
int temp4;
substr(temp_data1,(char *)(data+8),4);
temp4 = (int)strtol(temp_data1,&temp_conv,16);
if (temp_data1[0] > '7') { // Negative value, convert
temp4 = (temp4 & (temp4-0x7FFF)) - 0x8000;
}
xastir_snprintf(weather->wx_temp,
sizeof(weather->wx_temp),
"%03d",
(int)((float)((temp4<<16)/65536)/10.0));
}
else {
if (!from)
weather->wx_temp[0] = 0;
}
/* todays rain total (on some units) */
if ((data[44]) != '-') { // '-' signifies invalid data
if (from) {
substr(temp_data1,(char *)(data+44),4);
xastir_snprintf(weather->wx_prec_00,
sizeof(weather->wx_prec_00),
"%0.2f",
(float)strtol(temp_data1,&temp_conv,16)/100.0);
}
} else {
if (!from)
weather->wx_prec_00[0] = 0;
}
/* rain total long term */
if (data[12] != '-') { // '-' signifies invalid data
substr(temp_data1,(char *)(data+12),4);
xastir_snprintf(weather->wx_rain_total,
sizeof(weather->wx_rain_total),
"%0.2f",
(float)strtol(temp_data1,&temp_conv,16)/100.0);
if (!from) {
/* local station */
compute_rain((float)atof(weather->wx_rain_total));
/*last hour rain */
snprintf(weather->wx_rain,
sizeof(weather->wx_rain),
"%0.2f",
rain_minute_total);
/*last 24 hour rain */
snprintf(weather->wx_prec_24,
sizeof(weather->wx_prec_24),
"%0.2f",
rain_24);
/* rain since midnight */
snprintf(weather->wx_prec_00,
sizeof(weather->wx_prec_00),
"%0.2f",
rain_00);
}
} else {
if (!from)
weather->wx_rain_total[0] = 0;
}
/* baro */
if (data[16] != '-') { // '-' signifies invalid data
substr(temp_data1,(char *)(data+16),4);
xastir_snprintf(weather->wx_baro,
sizeof(weather->wx_baro),
"%0.1f",
(float)strtol(temp_data1,&temp_conv,16)/10.0);
} else {
if (!from)
weather->wx_baro[0] = 0;
}
/* outdoor humidity */
if (data[32] != '-') { // '-' signifies invalid data
substr(temp_data1,(char *)(data+32),4);
xastir_snprintf(weather->wx_hum,
sizeof(weather->wx_hum),
"%03d",
(int)((float)strtol(temp_data1,&temp_conv,16)/10.0));
} else {
if (!from)
weather->wx_hum[0] = 0;
}
/* 1 min wind speed avg */
if (len > 48 && (data[48]) != '-') { // '-' signifies invalid data
substr(temp_data1,(char *)(data+48),4);
xastir_snprintf(weather->wx_speed,
sizeof(weather->wx_speed),
"%03d",
(int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
if (from) {
weather->wx_speed_sec_time = sec_now();
} else {
/* local station */
computed_gust = compute_gust((float)atof(weather->wx_speed),
last_speed,
&last_speed_time);
weather->wx_speed_sec_time = sec_now();
xastir_snprintf(weather->wx_gust,
sizeof(weather->wx_gust),
"%03d",
(int)(0.5 + computed_gust));
}
} else {
if (!from) {
if (len > 48)
weather->wx_speed[0] = 0;
}
}
}
//*****************************************************************
// Decode Peet Brothers Ultimeter-II weather data
//
// This function is called from db.c:data_add() only. Used for
// decoding incoming packets, not for our own weather station data.
//*****************************************************************
void decode_Peet_Bros(int from, unsigned char *data, AprsWeatherRow *weather, int type) {
time_t last_speed_time;
float last_speed;
float computed_gust;
char temp_data1[10];
char *temp_conv;
last_speed = 0.0;
computed_gust = 0.0;
last_speed_time = 0;
weather->wx_type = WX_TYPE;
xastir_snprintf(weather->wx_station,
sizeof(weather->wx_station),
"UII");
// '*' = MPH
// '#' = km/h
//
// # 5 0B 75 0082 0082
// * 7 00 76 0000 0000
// ^ ^ ^ ^
// rain [1/100 inch ?]
// outdoor temp
// wind speed [mph / km/h]
// wind dir
/* wind direction */
//
// 0x00 is N
// 0x04 is E
// 0x08 is S
// 0x0C is W
//
substr(temp_data1,(char *)data,1);
snprintf(weather->wx_course,
sizeof(weather->wx_course),
"%03d",
(int)(((float)strtol(temp_data1,&temp_conv,16)/16.0)*360.0));
/* get last gust speed */
if (strlen(weather->wx_gust) > 0 && !from) {
/* get last speed */
last_speed = (float)atof(weather->wx_gust);
last_speed_time = weather->wx_speed_sec_time;
}
/* wind speed */
substr(temp_data1,(char *)(data+1),2);
if (type == APRS_WX4) { // '#' speed in km/h, convert to mph
snprintf(weather->wx_speed,
sizeof(weather->wx_speed),
"%03d",
(int)(0.5 + (float)(strtol(temp_data1,&temp_conv,16)*0.62137)));
} else { // type == APRS_WX6, '*' speed in mph
xastir_snprintf(weather->wx_speed,
sizeof(weather->wx_speed),
"%03d",
(int)(0.5 + (float)strtol(temp_data1,&temp_conv,16)));
}
if (from) {
weather->wx_speed_sec_time = sec_now();
} else {
/* local station */
computed_gust = compute_gust((float)atof(weather->wx_speed),
last_speed,
&last_speed_time);
weather->wx_speed_sec_time = sec_now();
xastir_snprintf(weather->wx_gust,
sizeof(weather->wx_gust),
"%03d",
(int)(0.5 + computed_gust));
}
/* outdoor temp */
if (data[3] != '-') { // '-' signifies invalid data
int temp4;
substr(temp_data1,(char *)(data+3),2);
temp4 = (int)strtol(temp_data1,&temp_conv,16);
if (temp_data1[0] > '7') { // Negative value, convert
temp4 = (temp4 & (temp4-0x7FFF)) - 0x8000;
}
xastir_snprintf(weather->wx_temp,
sizeof(weather->wx_temp),
"%03d",
temp4-56);
} else {
if (!from)
weather->wx_temp[0] = 0;
}
// Rain divided by 100 for readings in hundredth of an inch
if (data[5] != '-') { // '-' signifies invalid data
substr(temp_data1,(char *)(data+5),4);
xastir_snprintf(weather->wx_rain_total,
sizeof(weather->wx_rain_total),
"%0.2f",
(float)strtol(temp_data1,&temp_conv,16)/100.0);
if (!from) {
/* local station */
compute_rain((float)atof(weather->wx_rain_total));
/*last hour rain */
xastir_snprintf(weather->wx_rain,
sizeof(weather->wx_rain),
"%0.2f",
rain_minute_total);
/*last 24 hour rain */
xastir_snprintf(weather->wx_prec_24,
sizeof(weather->wx_prec_24),
"%0.2f",
rain_24);
/* rain since midnight */
xastir_snprintf(weather->wx_prec_00,
sizeof(weather->wx_prec_00),
"%0.2f",
rain_00);
}
} else {
if (!from)
weather->wx_rain_total[0] = 0;
}
}
#endif //INCLUDE_APRS