/* * * 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 && ofs= 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