3 * This file is part of Maemo Mapper.
5 * Maemo Mapper is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * Maemo Mapper is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with Maemo Mapper. If not, see <http://www.gnu.org/licenses/>.
19 * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
21 * * XASTIR, Amateur Station Tracking and Information Reporting
22 * Copyright (C) 1999,2000 Frank Giannandrea
23 * Copyright (C) 2000-2007 The Xastir Group
34 #include "aprs_decode.h"
42 #include "hashtable.h"
45 #include "aprs_message.h"
46 //#include "aprs_weather.h"
50 float gust[60]; // High wind gust for each min. of last hour
51 int gust_write_ptr = 0;
52 int gust_read_ptr = 0;
53 int gust_last_write = 0;
55 float rain_base[24]; // hundredths of an inch
56 float rain_minute[60]; // Total rain for each min. of last hour, hundredths of an inch
57 int rain_check = 0; // Flag for re-checking rain_total each hour
59 int redraw_on_new_data; // Station redraw request
60 int wait_to_redraw; /* wait to redraw until system is up */
61 time_t posit_next_time;
64 float rain_minute_total = 0.0; // Total for last hour, hundredths of an inch
65 int rain_minute_last_write = -1; // Write pointer for rain_minute[] array, set to an invalid number
66 float rain_00 = 0.0; // hundredths of an inch
67 float rain_24 = 0.0; // hundredths of an inch
70 int station_count; // number of stored stations
71 int station_count_save = 0; // old copy of above
72 AprsDataRow *n_first; // pointer to first element in name sorted station list
73 AprsDataRow *n_last; // pointer to last element in name sorted station list
74 AprsDataRow *t_oldest; // pointer to first element in time sorted station list (oldest)
75 AprsDataRow *t_newest; // pointer to last element in time sorted station list (newest)
76 time_t last_station_remove; // last time we did a check for station removing
77 time_t last_sec,curr_sec; // for comparing if seconds in time have changed
78 int next_time_sn; // time serial number for unique time index
80 int stations_heard = 0;
84 double cvt_m2len = 3.28084; // m to ft // from meter
85 double cvt_kn2len = 1.0; // knots to knots; // from knots
86 double cvt_mi2len = 0.8689607; // mph to knots / mi to nm; // from miles
87 double cvt_dm2len = 0.328084; // dm to ft // from decimeter
88 double cvt_hm2len = 0.0539957; // hm to nm; // from hectometer
91 int emergency_distance_check = 1;
92 float emergency_range = 280.0; // Default is 4hrs @ 70mph distance
95 int decoration_offset_x = 0;
96 int decoration_offset_y = 0;
97 int last_station_info_x = 0;
98 int last_station_info_y = 0;
99 int fcc_lookup_pushed = 0;
100 int rac_lookup_pushed = 0;
102 time_t last_object_check = 0; // Used to determine when to re-transmit objects/items
104 time_t last_emergency_time = 0;
105 char last_emergency_callsign[MAX_CALLSIGN+1];
106 int st_direct_timeout = 60 * 60; // 60 minutes.
108 // Used in search_station_name() function. Shortcuts into the
109 // station list based on the least-significant 7 bits of the first
110 // two letters of the callsign/object name.
111 AprsDataRow *station_shortcuts[16384];
113 // calculate every half hour, display in status line every 5 minutes
114 #define ALOHA_CALC_INTERVAL 1800
115 #define ALOHA_STATUS_INTERVAL 300
117 int process_emergency_packet_again = 0;
123 char altnet_call[MAX_CALLSIGN+1];
124 static struct hashtable *tactical_hash = NULL;
127 char echo_digis[6][MAX_CALLSIGN+1];
128 int tracked_stations = 0; // A count variable used in debug code only
130 int trail_segment_distance; // Segment missing if greater distance
131 int trail_segment_time; // Segment missing if above this time (mins)
132 int skip_dupe_checking;
135 void station_shortcuts_update_function(int hash_key_in, AprsDataRow *p_rem);
136 void delete_station_memory(AprsDataRow *p_del);
137 void decode_Peet_Bros(int from, unsigned char *data, AprsWeatherRow *weather, int type);
138 void decode_U2000_P(int from, unsigned char *data, AprsWeatherRow *weather);
139 void decode_U2000_L(int from, unsigned char *data, AprsWeatherRow *weather);
141 void init_weather(AprsWeatherRow *weather) { // clear weather data
143 weather->wx_sec_time = (time_t)0;
144 weather->wx_storm = 0;
145 weather->wx_time[0] = '\0';
146 weather->wx_course[0] = '\0';
147 weather->wx_speed[0] = '\0';
148 weather->wx_speed_sec_time = 0; // ??
149 weather->wx_gust[0] = '\0';
150 weather->wx_hurricane_radius[0] = '\0';
151 weather->wx_trop_storm_radius[0] = '\0';
152 weather->wx_whole_gale_radius[0] = '\0';
153 weather->wx_temp[0] = '\0';
154 weather->wx_rain[0] = '\0';
155 weather->wx_rain_total[0] = '\0';
156 weather->wx_snow[0] = '\0';
157 weather->wx_prec_24[0] = '\0';
158 weather->wx_prec_00[0] = '\0';
159 weather->wx_hum[0] = '\0';
160 weather->wx_baro[0] = '\0';
161 weather->wx_fuel_temp[0] = '\0';
162 weather->wx_fuel_moisture[0] = '\0';
163 weather->wx_type = '\0';
164 weather->wx_station[0] = '\0';
168 int tactical_keys_equal(void *key1, void *key2) {
170 if (strlen((char *)key1) == strlen((char *)key2)
171 && strncmp((char *)key1,(char *)key2,strlen((char *)key1))==0) {
180 // Multiply all the characters in the callsign, truncated to
181 // TACTICAL_HASH_SIZE
183 unsigned int tactical_hash_from_key(void *key) {
184 unsigned char *jj = key;
185 unsigned int tac_hash = 1;
187 while (*jj != '\0') {
188 tac_hash = tac_hash * (unsigned int)*jj++;
191 tac_hash = tac_hash % TACTICAL_HASH_SIZE;
199 /* valid characters for APRS weather data fields */
200 int is_aprs_chr(char ch) {
202 if (g_ascii_isdigit(ch) || ch==' ' || ch=='.' || ch=='-')
212 // Search station record by callsign
213 // Returns a station with a call equal or after the searched one
215 // We use a doubly-linked list for the stations, so we can traverse
216 // in either direction. We also use a 14-bit hash table created
217 // from the first two letters of the call to dump us into the
218 // beginning of the correct area that may hold the callsign, which
219 // reduces search time quite a bit. We end up doing a linear search
220 // only through a small area of the linked list.
222 // DK7IN: I don't look at case, objects and internet names could
225 int search_station_name(AprsDataRow **p_name, char *call, int exact) {
231 (*p_name) = n_first; // start of alphabet
233 if (call[0] == '\0') {
234 // If call we're searching for is empty, return n_first as
239 // We create the hash key out of the lower 7 bits of the first
240 // two characters, creating a 14-bit key (1 of 16384)
243 hash_key = (int)((call[0] & 0x7f) << 7);
244 hash_key = hash_key | (int)(call[1] & 0x7f);
246 // Look for a match using hash table lookup
248 (*p_name) = station_shortcuts[hash_key];
250 if ((*p_name) == NULL) { // No hash-table entry found.
253 // No index found for that letter. Walk the array until
254 // we find an entry that is filled. That'll be our
255 // potential insertion point (insertion into the list will
256 // occur just ahead of the hash entry).
257 for (mm = hash_key+1; mm < 16384; mm++) {
258 if (station_shortcuts[mm] != NULL)
260 (*p_name) = station_shortcuts[mm];
268 // If we got to this point, we either have a NULL pointer or a
269 // real hash-table pointer entry. A non-NULL pointer means that
270 // we have a match for the lower seven bits of the first two
271 // characters of the callsign. Check the rest of the callsign,
272 // and jump out of the loop if we get outside the linear search
273 // area (if first two chars are different).
275 kk = (int)strlen(call);
277 // Search linearly through list. Stop at end of list or break.
278 while ( (*p_name) != NULL) {
281 // Check entire string for exact match
282 result = strcmp( call, (*p_name)->call_sign );
285 // Check first part of string for match
286 result = strncmp( call, (*p_name)->call_sign, kk );
289 if (result < 0) { // We went past the right location.
295 else if (result == 0) { // Found a possible match
298 else { // Result > 0. We haven't found it yet.
300 (*p_name) = (*p_name)->n_next; // Next element in list
304 // Did we find anything?
305 if ( (*p_name) == NULL) {
307 return(ok); // Nope. No match found.
310 // If "exact" is set, check that the string lengths match as
311 // well. If not, we didn't find it.
312 if (exact && ok && strlen((*p_name)->call_sign) != strlen(call))
315 return(ok); // if not ok: p_name points to correct insert position in name list
321 * Check if current packet is a delayed echo
323 int is_trailpoint_echo(AprsDataRow *p_station) {
329 // Check whether we're to skip checking for dupes (reading in
330 // objects/items from file is one such case).
332 if (skip_dupe_checking) {
333 return(0); // Say that it isn't an echo
336 // Start at newest end of linked list and compare. Return if we're
337 // beyond the checktime.
338 ptr = p_station->newest_trackpoint;
341 return(0); // first point couldn't be an echo
343 checktime = p_station->sec_heard - TRAIL_ECHO_TIME*60;
345 while (ptr != NULL) {
347 if (ptr->sec < checktime)
348 return(0); // outside time frame, no echo found
350 if ((p_station->coord_lon == ptr->trail_long_pos)
351 && (p_station->coord_lat == ptr->trail_lat_pos)
352 && (p_station->speed == '\0' || ptr->speed < 0
353 || (long)(atof(p_station->speed)*18.52) == ptr->speed)
354 // current: char knots, trail: long 0.1m (-1 is undef)
355 && (p_station->course == '\0' || ptr->course <= 0
356 || atoi(p_station->course) == ptr->course)
357 // current: char, trail: int (-1 is undef)
358 && (p_station->altitude == '\0' || ptr->altitude <= -99999l
359 || atoi(p_station->altitude)*10 == ptr->altitude)) {
360 // current: char, trail: int (-99999l is undef)
362 return(1); // we found a delayed echo
367 return(0); // no echo found
372 * Keep track of last six digis that echo my transmission
374 void upd_echo(char *path) {
377 if (echo_digis[5][0] != '\0') {
379 xastir_snprintf(echo_digis[i],
385 echo_digis[5][0] = '\0';
387 for (i=0,j=0;i < (int)strlen(path);i++) {
394 j++; // first char of call
395 if (i > 0 && i-j <= 9) {
397 for (i=0;i<5;i++) { // look for free entry
398 if (echo_digis[i][0] == '\0')
401 substr(echo_digis[i],path+j,len);
407 // Store one trail point. Allocate storage for the new data.
409 // We now store track data in a doubly-linked list. Each record has a
410 // pointer to the previous and the next record in the list. The main
411 // station record has a pointer to the oldest and the newest end of the
412 // chain, and the chain can be traversed in either order.
414 int store_trail_point(AprsDataRow *p_station,
422 // TODO - trails are currently disabled
423 // This seems to fall over!!!
431 // Allocate storage for the new track point
432 ptr = malloc(sizeof(AprsTrackRow));
434 return(0); // Failed due to malloc
437 // Check whether we have any track data saved
438 if (p_station->newest_trackpoint == NULL) {
439 // new trail, do initialization
443 // Assign a new trail color 'cuz it's a new trail
444 p_station->trail_color = new_trail_color(p_station->call_sign);
447 // Start linking the record to the new end of the chain
448 ptr->prev = p_station->newest_trackpoint; // Link to record or NULL
449 ptr->next = NULL; // Newest end of chain
451 // Have an older record already?
452 if (p_station->newest_trackpoint != NULL) { // Yes
453 p_station->newest_trackpoint->next = ptr;
455 else { // No, this is our first record
456 p_station->oldest_trackpoint = ptr;
459 // Link it in as our newest record
460 p_station->newest_trackpoint = ptr;
462 ptr->trail_long_pos = lon;
463 ptr->trail_lat_pos = lat;
467 ptr->altitude = atoi(alt)*10;
469 ptr->altitude = -99999l;
471 if (speed[0] != '\0')
472 ptr->speed = (long)(atof(speed)*18.52);
476 if (course[0] != '\0')
477 ptr->course = (int)(atof(course) + 0.5); // Poor man's rounding
481 flag = '\0'; // init flags
483 if ((stn_flag & ST_DIRECT) != 0)
484 flag |= TR_LOCAL; // set "local" flag
486 if (ptr->prev != NULL) { // we have at least two points...
487 // Check whether distance between points is too far. We
488 // must convert from degrees to the Xastir coordinate system
489 // units, which are 100th of a second.
490 if ( abs(lon - ptr->prev->trail_long_pos) > (trail_segment_distance * 60*60*100) ||
491 abs(lat - ptr->prev->trail_lat_pos) > (trail_segment_distance * 60*60*100) ) {
493 // Set "new track" flag if there's
494 // "trail_segment_distance" degrees or more between
495 // points. Originally was hard-coded to one degree, now
496 // set by a slider in the timing dialog.
500 // Check whether trail went above our maximum time
501 // between points. If so, don't draw segment.
502 if (abs(sec - ptr->prev->sec) > (trail_segment_time *60)) {
504 // Set "new track" flag if long delay between
505 // reception of two points. Time is set by a slider
506 // in the timing dialog.
511 // Since we have more then 1 previous track point, ensure we don't go over the configured max
512 // (_aprs_std_pos_hist)
513 AprsTrackRow *ptr_tmp = ptr;
514 AprsTrackRow *ptr_tmp_previous = ptr;
517 while( (ptr_tmp = ptr_tmp->prev) )
519 if(trackCount > _aprs_std_pos_hist)
521 fprintf(stderr, "DEBUG: Deleting old track point\n");
523 ptr_tmp->prev->next = ptr_tmp->next;
526 ptr_tmp->next->prev = ptr_tmp->prev;
528 ptr_tmp_previous = ptr_tmp->prev;
530 ptr_tmp = ptr_tmp_previous;
539 ptr_tmp = ptr_tmp->prev;
544 // Set "new track" flag for first point received.
549 return(1); // We succeeded
555 * Substring function WITH a terminating NULL char, needs a string of at least size+1
557 void substr(char *dest, char *src, int size)
559 snprintf(dest, size+1, "%s", src);
565 * Remove element from name ordered list
567 void remove_name(AprsDataRow *p_rem) { // todo: return pointer to next element
568 int update_shortcuts = 0;
569 int hash_key; // We use a 14-bit hash key
572 // Do a quick check to see if we're removing a station record
573 // that is pointed to by our pointer shortcuts array.
574 // If so, update our pointer shortcuts after we're done.
576 // We create the hash key out of the lower 7 bits of the first
577 // two characters, creating a 14-bit key (1 of 16384)
579 hash_key = (int)((p_rem->call_sign[0] & 0x7f) << 7);
580 hash_key = hash_key | (int)(p_rem->call_sign[1] & 0x7f);
582 if (station_shortcuts[hash_key] == p_rem) {
583 // Yes, we're trying to remove a record that a hash key
584 // directly points to. We'll need to redo that hash key
585 // after we remove the record.
590 // Proceed to the station record removal
592 if (p_rem->n_prev == NULL) { // Appears to be first element in list
594 if (n_first == p_rem) { // Yes, head of list
596 // Make list head point to 2nd element in list (or NULL)
597 // so that we can delete the current record.
598 n_first = p_rem->n_next;
600 else { // No, not first element in list. Problem! The
601 // list pointers are inconsistent for some reason.
602 // The chain has been broken and we have dangling
606 "remove_name(): ERROR: p->n_prev == NULL but p != n_first\n");
608 abort(); // Cause a core dump at this point
609 // Perhaps we could do some repair to the list pointers here? Start
610 // at the other end of the chain and navigate back to this end, then
611 // fix up n_first to point to it? This is at the risk of a memory
612 // leak, but at least Xastir might continue to run.
616 else { // Not the first element in the list. Fix up pointers
617 // to skip the current record.
618 p_rem->n_prev->n_next = p_rem->n_next;
622 if (p_rem->n_next == NULL) { // Appears to be last element in list
624 if (n_last == p_rem) { // Yes, tail of list
626 // Make list tail point to previous element in list (or
627 // NULL) so that we can delete the current record.
628 n_last = p_rem->n_prev;
630 else { // No, not last element in list. Problem! The list
631 // pointers are inconsistent for some reason. The
632 // chain has been broken and we have dangling
636 "remove_name(): ERROR: p->n_next == NULL but p != n_last\n");
638 abort(); // Cause a core dump at this point
639 // Perhaps we could do some repair to the list pointers here? Start
640 // at the other end of the chain and navigate back to this end, then
641 // fix up n_last to point to it? This is at the risk of a memory
642 // leak, but at least Xastir might continue to run.
646 else { // Not the last element in the list. Fix up pointers to
647 // skip the current record.
648 p_rem->n_next->n_prev = p_rem->n_prev;
652 // Update our pointer shortcuts. Pass the removed hash_key to
653 // the function so that we can try to redo just that hash_key
655 if (update_shortcuts) {
656 //fprintf(stderr,"\t\t\t\t\t\tRemoval of hash key: %i\n", hash_key);
658 // The -1 tells the function to redo all of the hash table
659 // pointers because we deleted one of them. Later we could
660 // optimize this so that only the specific pointer is fixed
662 station_shortcuts_update_function(-1, NULL);
671 * Station Capabilities, Queries and Responses [APRS Reference, chapter 15]
674 // According to Bob Bruninga we should wait a random time between 0
675 // and 120 seconds before responding to a general query. We use the
676 // delayed-ack mechanism to add this randomness.
678 // NOTE: We may end up sending these to RF when the query came in
679 // over the internet. We should check that.
681 int process_query( /*@unused@*/ char *call_sign, /*@unused@*/ char *path,char *message,TAprsPort port, /*@unused@*/ int third_party) {
687 // Generate a random number between 0.0 and 1.0
688 randomize = rand() / (float)RAND_MAX;
690 // Convert to between 0 and 120 seconds
691 randomize = randomize * 120.0;
693 // Check for proper usage of the ?APRS? query
695 // NOTE: We need to add support in here for the radius circle as
696 // listed in the spec for general queries. Right now we respond to
697 // all queries, whether we're inside the circle or not. Spec says
700 // ?Query?Lat,Long,Radius
701 // 1 n 1 n 1 n 1 4 Bytes
703 // i.e. ?APRS? 34.02,-117.15,0200
705 // Note leading space in latitude as its value is positive.
706 // Lat/long are floating point degrees. N/E are positive, indicated
707 // by a leading space. S/W are negative. Radius is in miles
708 // expressed as a fixed 4-digit number in whole miles. All stations
709 // inside the specified circle should respond with a position report
710 // and a status report.
712 if (!ok && strncmp(message,"APRS?",5)==0) {
714 // Initiate a delayed transmit of our own posit.
715 // UpdateTime() uses posit_next_time to decide when to
716 // transmit, so we'll just muck with that.
718 if ( posit_next_time - sec_now() < randomize ) {
719 // Skip setting it, as we'll transmit soon anyway
722 posit_next_time = (size_t)(sec_now() + randomize);
726 // Check for illegal case for the ?APRS? query
727 if (!ok && g_strncasecmp(message,"APRS?",5)==0) {
730 // "%s just queried us with an illegal query: %s\n",
734 // "Consider sending a message, asking them to follow the spec\n");
737 // Check for proper usage of the ?WX? query
738 if (!ok && strncmp(message,"WX?",3)==0) {
739 // WX is not supported
744 // Check for illegal case for the ?WX? query
745 if (!ok && g_strncasecmp(message,"WX?",3)==0) {
748 // "%s just queried us with an illegal query: %s\n",
752 // "Consider sending a message, asking them to follow the spec\n");
761 * Get new trail color for a call
763 int new_trail_color(char *call) {
771 * Insert existing element into time ordered list before p_time
772 * The p_new record ends up being on the "older" side of p_time when
773 * all done inserting (closer in the list to the t_oldest pointer).
774 * If p_time == NULL, insert at newest end of list.
776 void insert_time(AprsDataRow *p_new, AprsDataRow *p_time) {
779 // Set up pointer to next record (or NULL), sorted by time
780 p_new->t_newer = p_time;
782 if (p_time == NULL) { // add to end of list (becomes newest station)
784 p_new->t_older = t_newest; // connect to previous end of list
786 if (t_newest == NULL) // if list empty, create list
787 t_oldest = p_new; // it's now our only station on the list
789 t_newest->t_newer = p_new; // list not empty, link original last record to our new one
791 t_newest = p_new; // end of list (newest record pointer) points to our new record
794 else { // Else we're inserting into the middle of the list somewhere
796 p_new->t_older = p_time->t_older;
798 if (p_time->t_older == NULL) // add to end of list (new record becomes oldest station)
801 p_time->t_older->t_newer = p_new; // else
803 p_time->t_older = p_new;
806 // TODO - this may need implementing?
808 if(_aprs_max_stations > 0 && stations_heard > _aprs_max_stations)
813 // fprintf(stderr, "DEBUG: oldest station deleted\n");
815 delete_station_memory(t_oldest);
824 // fprintf(stderr, "DEBUG: Stations in memory: %d\n", stations_heard);
826 stations_heard++; // Should really be done in the new station method
835 * Remove element from time ordered list
837 void remove_time(AprsDataRow *p_rem) { // todo: return pointer to next element
839 if (p_rem->t_older == NULL) { // Appears to be first element in list
841 if (t_oldest == p_rem) { // Yes, head of list (oldest)
843 // Make oldest list head point to 2nd element in list (or NULL)
844 // so that we can delete the current record.
845 t_oldest = p_rem->t_newer;
847 else { // No, not first (oldest) element in list. Problem!
848 // The list pointers are inconsistent for some
849 // reason. The chain has been broken and we have
850 // dangling pointers.
853 "remove_time(): ERROR: p->t_older == NULL but p != t_oldest\n");
855 abort(); // Cause a core dump at this point
856 // Perhaps we could do some repair to the list pointers here? Start
857 // at the other end of the chain and navigate back to this end, then
858 // fix up t_oldest to point to it? This is at the risk of a memory
859 // leak, but at least Xastir might continue to run.
863 else { // Not the first (oldest) element in the list. Fix up
864 // pointers to skip the current record.
865 p_rem->t_older->t_newer = p_rem->t_newer;
869 if (p_rem->t_newer == NULL) { // Appears to be last (newest) element in list
871 if (t_newest == p_rem) { // Yes, head of list (newest)
873 // Make newest list head point to previous element in
874 // list (or NULL) so that we can delete the current
876 t_newest = p_rem->t_older;
878 else { // No, not newest element in list. Problem! The
879 // list pointers are inconsistent for some reason.
880 // The chain has been broken and we have dangling
884 "remove_time(): ERROR: p->t_newer == NULL but p != t_newest\n");
886 abort(); // Cause a core dump at this point
887 // Perhaps we could do some repair to the list pointers here? Start
888 // at the other end of the chain and navigate back to this end, then
889 // fix up t_newest to point to it? This is at the risk of a memory
890 // leak, but at least Xastir might continue to run.
894 else { // Not the newest element in the list. Fix up pointers
895 // to skip the current record.
896 p_rem->t_newer->t_older = p_rem->t_older;
906 * Move station record before p_time in time ordered list
908 void move_station_time(AprsDataRow *p_curr, AprsDataRow *p_time) {
910 if (p_curr != NULL) { // need a valid record
912 insert_time(p_curr,p_time);
918 * Extract powergain and/or range from APRS info field:
919 * "PHG1234/", "PHG1234", or "RNG1234" from APRS data extension.
921 int extract_powergain_range(char *info, char *phgd) {
926 //fprintf(stderr,"Info:%s\n",info);
928 // Check whether two strings of interest are present and snag a
930 info2 = strstr(info,"RNG");
932 info2 = strstr(info,"PHG");
939 len = (int)strlen(info2);
941 if (len >= 9 && strncmp(info2,"PHG",3)==0
943 && info2[8]!='A' // trailing '/' not defined in Reference...
944 && g_ascii_isdigit(info2[3])
945 && g_ascii_isdigit(info2[4])
946 && g_ascii_isdigit(info2[5])
947 && g_ascii_isdigit(info2[6])) {
948 substr(phgd,info2,7);
950 for (i=0;i<=len-8;i++) // delete powergain from data extension field
951 info2[i] = info2[i+8];
954 if (len >= 7 && strncmp(info2,"PHG",3)==0
955 && g_ascii_isdigit(info2[3])
956 && g_ascii_isdigit(info2[4])
957 && g_ascii_isdigit(info2[5])
958 && g_ascii_isdigit(info2[6])) {
959 substr(phgd,info2,7);
961 for (i=0;i<=len-7;i++) // delete powergain from data extension field
962 info2[i] = info2[i+7];
964 else if (len >= 7 && strncmp(info2,"RNG",3)==0
965 && g_ascii_isdigit(info2[3])
966 && g_ascii_isdigit(info2[4])
967 && g_ascii_isdigit(info2[5])
968 && g_ascii_isdigit(info2[6])) {
969 substr(phgd,info2,7);
971 for (i=0;i<=len-7;i++) // delete powergain from data extension field
972 info2[i] = info2[i+7];
983 * Extract omnidf from APRS info field "DFS1234/" from APRS data extension
985 int extract_omnidf(char *info, char *phgd) {
989 len = (int)strlen(info);
990 if (len >= 8 && strncmp(info,"DFS",3)==0 && info[7]=='/' // trailing '/' not defined in Reference...
991 && g_ascii_isdigit(info[3]) && g_ascii_isdigit(info[5]) && g_ascii_isdigit(info[6])) {
993 for (i=0;i<=len-8;i++) // delete omnidf from data extension field
1005 // Extract speed and/or course from beginning of info field
1007 // Returns course in degrees, speed in KNOTS.
1009 int extract_speed_course(char *info, char *speed, char *course) {
1012 len = (int)strlen(info);
1016 for(i=0; found && i<7; i++) { // check data format
1017 if (i==3) { // check separator
1022 if( !( g_ascii_isdigit(info[i])
1023 || (info[i] == ' ') // Spaces and periods are allowed. Need these
1024 || (info[i] == '.') ) ) // here so that we can get the field deleted
1030 substr(course,info,3);
1031 substr(speed,info+4,3);
1032 for (i=0;i<=len-7;i++) // delete speed/course from info field
1033 info[i] = info[i+7];
1035 if (!found || atoi(course) < 1) { // course 0 means undefined
1036 // speed[0] ='\0'; // Don't do this! We can have a valid
1037 // speed without a valid course.
1040 else { // recheck data format looking for undefined fields
1041 for(i=0; i<2; i++) {
1042 if( !(g_ascii_isdigit(speed[i]) ) )
1044 if( !(g_ascii_isdigit(course[i]) ) )
1057 * Extract Area Object
1059 void extract_area(AprsDataRow *p_station, char *data) {
1062 AprsAreaObject temp_area;
1064 /* NOTE: If we are here, the symbol was the area symbol. But if this
1065 is a slightly corrupted packet, we shouldn't blow away the area info
1066 for this station, since it could be from a previously received good
1067 packet. So we will work on temp_area and only copy to p_station at
1068 the end, returning on any error as we parse. N7TAP */
1070 //fprintf(stderr,"Area Data: %s\n", data);
1072 len = (int)strlen(data);
1073 val = data[0] - '0';
1074 if (val >= 0 && val <= AREA_MAX) {
1075 temp_area.type = val;
1076 val = data[4] - '0';
1077 if (data[3] == '/') {
1078 if (val >=0 && val <= 9) {
1079 temp_area.color = val;
1085 else if (data[3] == '1') {
1086 if (val >=0 && val <= 5) {
1087 temp_area.color = 10 + val;
1095 if (isdigit((int)data[1]) && isdigit((int)data[2])) {
1096 val = (10 * (data[1] - '0')) + (data[2] - '0');
1101 temp_area.sqrt_lat_off = val;
1104 if (isdigit((int)data[5]) && isdigit((int)data[6])) {
1105 val = (10 * (data[5] - '0')) + (data[6] - '0');
1110 temp_area.sqrt_lon_off = val;
1112 for (i = 0; i <= len-7; i++) // delete area object from data extension field
1113 data[i] = data[i+7];
1116 if (temp_area.type == AREA_LINE_RIGHT || temp_area.type == AREA_LINE_LEFT) {
1117 if (data[0] == '{') {
1118 if (sscanf(data, "{%u}", &uval) == 1) {
1119 temp_area.corridor_width = uval & 0xffff;
1120 for (i = 0; i <= len; i++)
1124 for (i = 0; i <= (int)(len-uval); i++)
1125 data[i] = data[i+uval]; // delete corridor width
1128 temp_area.corridor_width = 0;
1133 temp_area.corridor_width = 0;
1137 temp_area.corridor_width = 0;
1144 memcpy(&(p_station->aprs_symbol.area_object), &temp_area, sizeof(AprsAreaObject));
1151 * Extract probability_max data from APRS info field: "Pmax1.23,"
1152 * Please note the ending comma. We use it to delimit the field.
1154 int extract_probability_max(char *info, char *prob_max, int prob_max_size) {
1161 len = (int)strlen(info);
1162 if (len < 6) { // Too short
1167 c = strstr(info,"Pmax");
1168 if (c == NULL) { // Pmax not found
1173 c = c+4; // Skip the Pmax part
1174 // Find the ending comma
1178 if (*d == ',') { // We're done
1185 // Check for string too long
1186 if ( ((d-c) > 10) && !done) { // Something is wrong, we should be done by now
1192 // Copy the substring across
1197 prob_max[d-c] = '\0';
1198 prob_max[10] = '\0'; // Just to make sure
1200 // Delete data from data extension field
1201 d++; // Skip the comma
1218 * Extract probability_min data from APRS info field: "Pmin1.23,"
1219 * Please note the ending comma. We use it to delimit the field.
1221 int extract_probability_min(char *info, char *prob_min, int prob_min_size) {
1227 len = (int)strlen(info);
1228 if (len < 6) { // Too short
1233 c = strstr(info,"Pmin");
1234 if (c == NULL) { // Pmin not found
1239 c = c+4; // Skip the Pmin part
1240 // Find the ending comma
1244 if (*d == ',') { // We're done
1251 // Check for string too long
1252 if ( ((d-c) > 10) && !done) { // Something is wrong, we should be done by now
1258 // Copy the substring across
1259 xastir_snprintf(prob_min,
1263 prob_min[d-c] = '\0';
1264 prob_min[10] = '\0'; // Just to make sure
1266 // Delete data from data extension field
1267 d++; // Skip the comma
1284 * Extract signpost data from APRS info field: "{123}", an APRS data extension
1285 * Format can be {1}, {12}, or {123}. Letters or digits are ok.
1287 int extract_signpost(char *info, char *signpost) {
1288 int i,found,len,done;
1296 len = (int)strlen(info);
1299 && ( (info[2] == '}' ) || (info[3] == '}' ) || (info[4] == '}' ) ) ) {
1303 while (!done) { // Snag up to three digits
1304 if (info[i] == '}') { // We're done
1305 found = i; // found = position of '}' character
1309 signpost[i-1] = info[i];
1314 if ( (i > 4) && !done) { // Something is wrong, we should be done by now
1320 substr(signpost,info+1,found-1);
1322 for (i=0;i<=len-found;i++) { // delete omnidf from data extension field
1323 info[i] = info[i+found];
1337 // Returns true if station fits the current altnet description.
1339 int is_altnet(AprsDataRow *p_station) {
1340 char temp_altnet_call[20+1];
1347 // Snag a possible altnet call out of the record for later use
1348 if (p_station->node_path_ptr != NULL)
1349 substr(temp_altnet_call, p_station->node_path_ptr, MAX_CALLSIGN);
1351 temp_altnet_call[0] = '\0';
1359 if ((net_ptr = strchr(temp_altnet_call, ',')))
1360 *net_ptr = '\0'; // Chop the string at the first ',' character
1362 for (altnet_match = (int)strlen(altnet_call); altnet && altnet_call[altnet_match-1] == '*'; altnet_match--);
1364 result = (!strncmp(temp_altnet_call, altnet_call, (size_t)altnet_match)
1365 || !strcmp(temp_altnet_call, "local")
1366 || !strncmp(temp_altnet_call, "SPC", 3)
1367 || !strcmp(temp_altnet_call, "SPECL")
1368 || ( is_my_station(p_station) ) ) ; // It's my callsign/SSID
1375 int is_num_or_sp(char ch)
1377 return((int)((ch >= '0' && ch <= '9') || ch == ' '));
1381 char *get_time(char *time_here) {
1382 struct tm *time_now;
1385 (void)time(&timenw);
1386 time_now = localtime(&timenw);
1387 (void)strftime(time_here,MAX_TIME,"%m%d%Y%H%M%S",time_now);
1392 static void clear_area(AprsDataRow *p_station) {
1393 p_station->aprs_symbol.area_object.type = AREA_NONE;
1394 p_station->aprs_symbol.area_object.color = AREA_GRAY_LO;
1395 p_station->aprs_symbol.area_object.sqrt_lat_off = 0;
1396 p_station->aprs_symbol.area_object.sqrt_lon_off = 0;
1397 p_station->aprs_symbol.area_object.corridor_width = 0;
1403 // Check for valid overlay characters: 'A-Z', '0-9', and 'a-j'. If
1404 // 'a-j', it's from a compressed posit, and we need to convert it to
1406 void overlay_symbol(char symbol, char data, AprsDataRow *fill) {
1408 if ( data != '/' && data !='\\') { // Symbol overlay
1410 if (data >= 'a' && data <= 'j') {
1411 // Found a compressed posit numerical overlay
1412 data = data - 'a'+'0'; // Convert to a digit
1414 if ( (data >= '0' && data <= '9')
1415 || (data >= 'A' && data <= 'Z') ) {
1416 // Found normal overlay character
1417 fill->aprs_symbol.aprs_type = '\\';
1418 fill->aprs_symbol.special_overlay = data;
1421 // Bad overlay character. Don't use it. Insert the
1422 // normal alternate table character instead.
1423 fill->aprs_symbol.aprs_type = '\\';
1424 fill->aprs_symbol.special_overlay='\0';
1427 else { // No overlay character
1428 fill->aprs_symbol.aprs_type = data;
1429 fill->aprs_symbol.special_overlay='\0';
1431 fill->aprs_symbol.aprs_symbol = symbol;
1436 * Extract Time from begin of line [APRS Reference, chapter 6]
1438 * If a time string is found in "data", it is deleted from the
1439 * beginning of the string.
1441 int extract_time(AprsDataRow *p_station, char *data, int type) {
1445 // todo: better check of time data ranges
1446 len = (int)strlen(data);
1447 if (type == APRS_WX2) {
1448 // 8 digit time from stand-alone positionless weather stations...
1450 // MMDDHHMM zulu time
1451 // MM 01-12 todo: better check of time data ranges
1456 for (i=0;ok && i<8;i++)
1457 if (!isdigit((int)data[i]))
1460 // substr(p_station->station_time,data+2,6);
1461 // p_station->station_time_type = 'z';
1462 for (i=0;i<=len-8;i++) // delete time from data
1463 data[i] = data[i+8];
1469 // Status messages only with optional zulu format
1470 // DK7IN: APRS ref says one of 'z' '/' 'h', but I found 'c' at HB9TJM-8 ???
1471 if (toupper(data[6])=='Z' || data[6]=='/' || toupper(data[6])=='H')
1473 for (i=0;ok && i<6;i++)
1474 if (!isdigit((int)data[i]))
1477 // substr(p_station->station_time,data,6);
1478 // p_station->station_time_type = data[6];
1479 for (i=0;i<=len-7;i++) // delete time from data
1480 data[i] = data[i+7];
1489 // Breaks up a string into substrings using comma as the delimiter.
1490 // Makes each entry in the array of char ptrs point to one
1491 // substring. Modifies incoming string and cptr[] array. Send a
1492 // character constant string to it and you'll get an instant
1493 // segfault (the function can't modify a char constant string).
1495 void split_string( char *data, char *cptr[], int max ) {
1498 char *current = data;
1501 // NULL each char pointer
1502 for (ii = 0; ii < max; ii++) {
1506 // Save the beginning substring address
1509 for (ii = 1; ii < max; ii++) {
1510 temp = strchr(current,','); // Find next comma
1512 if(!temp) { // No commas found
1513 return; // All done with string
1516 // Store pointer to next substring in array
1517 cptr[ii] = &temp[1];
1520 // Overwrite comma with end-of-string char and bump pointer by
1528 /* check data format 123 ___ ... */
1529 // We wish to count how many ' ' or '.' characters we find. If it
1530 // equals zero or the field width, it might be a weather field. If
1531 // not, then it might be part of a comment field or something else.
1533 int is_weather_data(char *data, int len) {
1538 for (i=0;ok && i<len;i++)
1539 if (!is_aprs_chr(data[i]))
1542 // Count filler characters. Must equal zero or field width to
1543 // be a weather field. There doesn't appear to be a case where
1544 // a single period is allowed in any weather-related fields.
1546 for (i=0;ok && i<len;i++) {
1547 if (data[i] == ' ' || data[i] == '.') {
1551 if (count != 0 && count != len) {
1563 /* convert latitude from string to long with 1/100 sec resolution */
1565 // Input is in [D]DMM.MM[MM]N format (degrees/decimal
1566 // minutes/direction)
1568 long convert_lat_s2l(char *lat) { /* N=0°, Ctr=90°, S=180° */
1576 // Find the decimal point if present
1577 p = strstr(lat, ".");
1579 if (p == NULL) // No decimal point found
1582 offset = p - lat; // Arithmetic on pointers
1585 return(0l); // Bad, no degrees or minutes
1587 case 1: // M.MM[MM]N
1588 return(0l); // Bad, no degrees
1590 case 2: // MM.MM[MM]N
1591 return(0l); // Bad, no degrees
1593 case 3: // DMM.MM[MM]N
1594 xastir_snprintf(copy,
1596 "0%s", // Add a leading '0'
1599 case 4: // DDMM.MM[MM]N
1600 xastir_snprintf(copy,
1602 "%s", // Copy verbatim
1612 && ( (char)toupper((int)copy[ 5])=='N'
1613 || (char)toupper((int)copy[ 6])=='N'
1614 || (char)toupper((int)copy[ 7])=='N'
1615 || (char)toupper((int)copy[ 8])=='N'
1616 || (char)toupper((int)copy[ 9])=='N'
1617 || (char)toupper((int)copy[10])=='N'
1618 || (char)toupper((int)copy[11])=='N'
1619 || (char)toupper((int)copy[ 5])=='S'
1620 || (char)toupper((int)copy[ 6])=='S'
1621 || (char)toupper((int)copy[ 7])=='S'
1622 || (char)toupper((int)copy[ 8])=='S'
1623 || (char)toupper((int)copy[ 9])=='S'
1624 || (char)toupper((int)copy[10])=='S'
1625 || (char)toupper((int)copy[11])=='S')) {
1627 substr(n, copy, 2); // degrees
1628 centi_sec=atoi(n)*60*60*100;
1630 substr(n, copy+2, 2); // minutes
1631 centi_sec += atoi(n)*60*100;
1633 substr(n, copy+5, 4); // fractional minutes
1634 // Keep the fourth digit if present, as it resolves to 0.6
1635 // of a 1/100 sec resolution. Two counts make one count in
1636 // the Xastir coordinate system.
1638 // Extend the digits to full precision by adding zeroes on
1640 strncat(n, "0000", sizeof(n) - strlen(n));
1642 // Get rid of the N/S character
1643 if (!isdigit((int)n[2]))
1645 if (!isdigit((int)n[3]))
1648 // Terminate substring at the correct digit
1650 //fprintf(stderr,"Lat: %s\n", n);
1652 // Add 0.5 (Poor man's rounding)
1653 centi_sec += (long)((atoi(n) * 0.6) + 0.5);
1655 if ( (char)toupper((int)copy[ 5])=='N'
1656 || (char)toupper((int)copy[ 6])=='N'
1657 || (char)toupper((int)copy[ 7])=='N'
1658 || (char)toupper((int)copy[ 8])=='N'
1659 || (char)toupper((int)copy[ 9])=='N'
1660 || (char)toupper((int)copy[10])=='N'
1661 || (char)toupper((int)copy[11])=='N') {
1662 centi_sec = -centi_sec;
1665 centi_sec += 90*60*60*100;
1674 /* convert longitude from string to long with 1/100 sec resolution */
1676 // Input is in [DD]DMM.MM[MM]W format (degrees/decimal
1677 // minutes/direction).
1679 long convert_lon_s2l(char *lon) { /* W=0°, Ctr=180°, E=360° */
1687 // Find the decimal point if present
1688 p = strstr(lon, ".");
1690 if (p == NULL) // No decimal point found
1693 offset = p - lon; // Arithmetic on pointers
1696 return(0l); // Bad, no degrees or minutes
1698 case 1: // M.MM[MM]N
1699 return(0l); // Bad, no degrees
1701 case 2: // MM.MM[MM]N
1702 return(0l); // Bad, no degrees
1704 case 3: // DMM.MM[MM]N
1705 xastir_snprintf(copy,
1707 "00%s", // Add two leading zeroes
1710 case 4: // DDMM.MM[MM]N
1711 xastir_snprintf(copy,
1713 "0%s", // Add leading '0'
1716 case 5: // DDDMM.MM[MM]N
1717 xastir_snprintf(copy,
1719 "%s", // Copy verbatim
1729 && ( (char)toupper((int)copy[ 6])=='W'
1730 || (char)toupper((int)copy[ 7])=='W'
1731 || (char)toupper((int)copy[ 8])=='W'
1732 || (char)toupper((int)copy[ 9])=='W'
1733 || (char)toupper((int)copy[10])=='W'
1734 || (char)toupper((int)copy[11])=='W'
1735 || (char)toupper((int)copy[12])=='W'
1736 || (char)toupper((int)copy[ 6])=='E'
1737 || (char)toupper((int)copy[ 7])=='E'
1738 || (char)toupper((int)copy[ 8])=='E'
1739 || (char)toupper((int)copy[ 9])=='E'
1740 || (char)toupper((int)copy[10])=='E'
1741 || (char)toupper((int)copy[11])=='E'
1742 || (char)toupper((int)copy[12])=='E')) {
1744 substr(n,copy,3); // degrees 013
1745 centi_sec=atoi(n)*60*60*100;
1747 substr(n,copy+3,2); // minutes 26
1748 centi_sec += atoi(n)*60*100;
1749 // 01326.66E 01326.660E
1751 substr(n,copy+6,4); // fractional minutes 66E 660E or 6601
1752 // Keep the fourth digit if present, as it resolves to 0.6
1753 // of a 1/100 sec resolution. Two counts make one count in
1754 // the Xastir coordinate system.
1756 // Extend the digits to full precision by adding zeroes on
1758 strncat(n, "0000", sizeof(n) - strlen(n));
1760 // Get rid of the E/W character
1761 if (!isdigit((int)n[2]))
1763 if (!isdigit((int)n[3]))
1766 n[4] = '\0'; // Make sure substring is terminated
1767 //fprintf(stderr,"Lon: %s\n", n);
1769 // Add 0.5 (Poor man's rounding)
1770 centi_sec += (long)((atoi(n) * 0.6) + 0.5);
1772 if ( (char)toupper((int)copy[ 6])=='W'
1773 || (char)toupper((int)copy[ 7])=='W'
1774 || (char)toupper((int)copy[ 8])=='W'
1775 || (char)toupper((int)copy[ 9])=='W'
1776 || (char)toupper((int)copy[10])=='W'
1777 || (char)toupper((int)copy[11])=='W'
1778 || (char)toupper((int)copy[12])=='W') {
1779 centi_sec = -centi_sec;
1782 centi_sec +=180*60*60*100;;
1789 APRS_Symbol *id_callsign(char *call_sign, char * to_call) {
1791 char *id = "/aUfbYX's><OjRkv";
1792 char hold[MAX_CALLSIGN+1];
1794 static APRS_Symbol symbol;
1796 symbol.aprs_symbol = '/';
1797 symbol.special_overlay = '\0';
1798 symbol.aprs_type ='/';
1799 ptr=strchr(call_sign,'-');
1800 if(ptr!=NULL) /* get symbol from SSID */
1801 if((index=atoi(ptr+1))<= 15)
1802 symbol.aprs_symbol = id[index];
1804 if (strncmp(to_call, "GPS", 3) == 0 || strncmp(to_call, "SPC", 3) == 0 || strncmp(to_call, "SYM", 3) == 0)
1806 substr(hold, to_call+3, 3);
1807 if ((ptr = strpbrk(hold, "->,")) != NULL)
1810 if (strlen(hold) >= 2) {
1813 symbol.aprs_type = '\\';
1816 if (('0' <= hold[1] && hold[1] <= '9') || ('A' <= hold[1] && hold[1] <= 'Z'))
1817 symbol.aprs_symbol = hold[1];
1822 symbol.aprs_type = '\\';
1827 symbol.aprs_symbol = '!';
1830 symbol.aprs_symbol = '"';
1833 symbol.aprs_symbol = '#';
1836 symbol.aprs_symbol = '$';
1839 symbol.aprs_symbol = '%';
1842 symbol.aprs_symbol = '&';
1845 symbol.aprs_symbol = '\'';
1848 symbol.aprs_symbol = '(';
1851 symbol.aprs_symbol = ')';
1854 symbol.aprs_symbol = '*';
1857 symbol.aprs_symbol = '+';
1860 symbol.aprs_symbol = ',';
1863 symbol.aprs_symbol = '-';
1866 symbol.aprs_symbol = '.';
1869 symbol.aprs_symbol = '/';
1875 symbol.aprs_type = '\\';
1880 symbol.aprs_symbol = '[';
1883 symbol.aprs_symbol = '\\';
1886 symbol.aprs_symbol = ']';
1889 symbol.aprs_symbol = '^';
1892 symbol.aprs_symbol = '_';
1895 symbol.aprs_symbol = '`';
1901 symbol.aprs_type = '\\';
1906 symbol.aprs_symbol = ':';
1909 symbol.aprs_symbol = ';';
1912 symbol.aprs_symbol = '<';
1915 symbol.aprs_symbol = '=';
1918 symbol.aprs_symbol = '>';
1921 symbol.aprs_symbol = '?';
1924 symbol.aprs_symbol = '@';
1930 symbol.aprs_type = '\\';
1935 symbol.aprs_symbol = '{';
1938 symbol.aprs_symbol = '|';
1941 symbol.aprs_symbol = '}';
1944 symbol.aprs_symbol = '~';
1950 symbol.aprs_type = '\\';
1953 if ('A' <= hold[1] && hold[1] <= 'Z')
1954 symbol.aprs_symbol = tolower((int)hold[1]);
1959 if (hold[2] >= 'a' && hold[2] <= 'j') {
1960 // Compressed mode numeric overlay
1961 symbol.special_overlay = hold[2] - 'a';
1963 else if ( (hold[2] >= '0' && hold[2] <= '9')
1964 || (hold[2] >= 'A' && hold[2] <= 'Z') ) {
1965 // Normal overlay character
1966 symbol.special_overlay = hold[2];
1969 // Bad overlay character found
1970 symbol.special_overlay = '\0';
1974 // No overlay character found
1975 symbol.special_overlay = '\0';
1985 * See if position is defined
1986 * 90°N 180°W (0,0 in internal coordinates) is our undefined position
1987 * 0N/0E is excluded from trails, could be excluded from map (#define ACCEPT_0N_0E)
1990 int position_defined(long lat, long lon, int strict) {
1992 if (lat == 0l && lon == 0l)
1993 return(0); // undefined location
1994 #ifndef ACCEPT_0N_0E
1996 #endif // ACCEPT_0N_0E
1997 if (lat == 90*60*60*100l && lon == 180*60*60*100l) // 0N/0E
1998 return(0); // undefined location
2004 // Extract data for $GPRMC, it fails if there is no position!!
2006 // GPRMC,UTC-Time,status(A/V),lat,N/S,lon,E/W,SOG,COG,UTC-Date,Mag-Var,E/W,Fix-Quality[*CHK]
2007 // 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]
2009 // The last field before the checksum is entirely optional, and in
2010 // fact first appeared in NMEA 2.3 (fairly recently). Most GPS's do
2011 // not currently put out that field. The field may be null or
2012 // nonexistent including the comma. Only "A" or "D" are considered
2013 // to be active and reliable fixes if this field is present.
2021 // $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
2022 // $GPRMC,104748.821,A,4301.1492,N,08803.0374,W,0.085048,102.36,010605,,*1A
2023 // $GPRMC,104749.821,A,4301.1492,N,08803.0377,W,0.054215,74.60,010605,,*2D
2025 int extract_RMC(AprsDataRow *p_station, char *data, char *call_sign, char *path, int *num_digits) {
2026 char temp_data[40]; // short term string storage, MAX_CALLSIGN, ... ???
2030 char *Substring[12]; // Pointers to substrings parsed by split_string()
2031 char temp_string[MAX_MESSAGE_LENGTH+1];
2035 // should we copy it before processing? it changes data: ',' gets substituted by '\0' !!
2036 ok = 0; // Start out as invalid. If we get enough info, we change this to a 1.
2038 if ( (data == NULL) || (strlen(data) < 34) ) { // Not enough data to parse position from.
2042 p_station->record_type = NORMAL_GPS_RMC;
2043 // Create a timestamp from the current time
2044 // get_time saves the time in temp_data
2045 xastir_snprintf(p_station->pos_time,
2046 sizeof(p_station->pos_time),
2048 get_time(temp_data));
2049 p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
2051 /* check aprs type on call sign */
2052 p_station->aprs_symbol = *id_callsign(call_sign, path);
2054 // Make a copy of the incoming data. The string passed to
2055 // split_string() gets destroyed.
2056 xastir_snprintf(temp_string,
2057 sizeof(temp_string),
2060 split_string(temp_string, Substring, 12);
2062 // The Substring[] array contains pointers to each substring in
2063 // the original data string.
2065 // GPRMC,034728,A,5101.016,N,11359.464,W,000.0,284.9,110701,018.0,E*7D
2066 // 0 1 2 3 4 5 6 7 8 9 10 11
2068 if (Substring[0] == NULL) // No GPRMC string
2071 if (Substring[1] == NULL) // No time string
2074 if (Substring[2] == NULL) // No valid fix char
2077 if (Substring[2][0] != 'A' && Substring[2][0] != 'V')
2079 // V is a warning but we can get good data still ?
2080 // DK7IN: got no position with 'V' !
2082 if (Substring[3] == NULL) // No latitude string
2085 if (Substring[4] == NULL) // No latitude N/S
2088 // Need to check lat_s for validity here. Note that some GPS's put out another digit of precision
2089 // (4801.1234) or leave one out (4801.12). Next character after digits should be a ','
2091 // Count digits after the decimal point for latitude
2092 if (strchr(Substring[3],'.')) {
2093 *num_digits = strlen(Substring[3]) - (int)(strchr(Substring[3],'.') - Substring[3]) - 1;
2099 temp_char = toupper((int)Substring[4][0]);
2101 if (temp_char != 'N' && temp_char != 'S') // Bad N/S
2104 xastir_snprintf(lat_s,
2110 if (Substring[5] == NULL) // No longitude string
2113 if (Substring[6] == NULL) // No longitude E/W
2116 // Need to check long_s for validity here. Should be all digits. Note that some GPS's put out another
2117 // digit of precision. (12201.1234). Next character after digits should be a ','
2119 temp_char = toupper((int)Substring[6][0]);
2121 if (temp_char != 'E' && temp_char != 'W') // Bad E/W
2124 xastir_snprintf(long_s,
2130 p_station->coord_lat = convert_lat_s2l(lat_s);
2131 p_station->coord_lon = convert_lon_s2l(long_s);
2133 // If we've made it this far, We have enough for a position now!
2136 // Now that we have a basic position, let's see what other data
2137 // can be parsed from the packet. The rest of it can still be
2138 // corrupt, so we're proceeding carefully under yellow alert on
2139 // impulse engines only.
2141 // GPRMC,034728,A,5101.016,N,11359.464,W,000.0,284.9,110701,018.0,E*7D
2142 // 0 1 2 3 4 5 6 7 8 9 10 11
2144 if (Substring[7] == NULL) { // No speed string
2145 p_station->speed[0] = '\0'; // No speed available
2149 xastir_snprintf(p_station->speed,
2153 // Is it always knots, otherwise we need a conversion!
2156 if (Substring[8] == NULL) { // No course string
2157 xastir_snprintf(p_station->course,
2158 sizeof(p_station->course),
2159 "000.0"); // No course available
2163 xastir_snprintf(p_station->course,
2174 // Extract data for $GPGLL
2176 // $GPGLL,4748.811,N,12219.564,W,033850,A*3C
2177 // lat, long, UTCtime in hhmmss, A=Valid, checksum
2179 // GPGLL,4748.811,N,12219.564,W,033850,A*3C
2182 int extract_GLL(AprsDataRow *p_station,char *data,char *call_sign, char *path, int *num_digits) {
2183 char temp_data[40]; // short term string storage, MAX_CALLSIGN, ... ???
2187 char *Substring[7]; // Pointers to substrings parsed by split_string()
2188 char temp_string[MAX_MESSAGE_LENGTH+1];
2192 ok = 0; // Start out as invalid. If we get enough info, we change this to a 1.
2194 if ( (data == NULL) || (strlen(data) < 28) ) // Not enough data to parse position from.
2197 p_station->record_type = NORMAL_GPS_GLL;
2198 // Create a timestamp from the current time
2199 // get_time saves the time in temp_data
2200 xastir_snprintf(p_station->pos_time,
2201 sizeof(p_station->pos_time),
2203 get_time(temp_data));
2204 p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
2206 /* check aprs type on call sign */
2207 p_station->aprs_symbol = *id_callsign(call_sign, path);
2209 // Make a copy of the incoming data. The string passed to
2210 // split_string() gets destroyed.
2211 xastir_snprintf(temp_string,
2212 sizeof(temp_string),
2215 split_string(temp_string, Substring, 7);
2217 // The Substring[] array contains pointers to each substring in
2218 // the original data string.
2220 if (Substring[0] == NULL) // No GPGGA string
2223 if (Substring[1] == NULL) // No latitude string
2226 if (Substring[2] == NULL) // No N/S string
2229 if (Substring[3] == NULL) // No longitude string
2232 if (Substring[4] == NULL) // No E/W string
2235 temp_char = toupper((int)Substring[2][0]);
2236 if (temp_char != 'N' && temp_char != 'S')
2239 xastir_snprintf(lat_s,
2244 // Need to check lat_s for validity here. Note that some GPS's put out another digit of precision
2245 // (4801.1234). Next character after digits should be a ','
2247 // Count digits after the decimal point for latitude
2248 if (strchr(Substring[1],'.')) {
2249 *num_digits = strlen(Substring[1]) - (int)(strchr(Substring[1],'.') - Substring[1]) - 1;
2255 temp_char = toupper((int)Substring[4][0]);
2256 if (temp_char != 'E' && temp_char != 'W')
2259 xastir_snprintf(long_s,
2264 // Need to check long_s for validity here. Should be all digits. Note that some GPS's put out another
2265 // digit of precision. (12201.1234). Next character after digits should be a ','
2267 p_station->coord_lat = convert_lat_s2l(lat_s);
2268 p_station->coord_lon = convert_lon_s2l(long_s);
2269 ok = 1; // We have enough for a position now
2271 xastir_snprintf(p_station->course,
2272 sizeof(p_station->course),
2273 "000.0"); // Fill in with dummy values
2274 p_station->speed[0] = '\0'; // Fill in with dummy values
2276 // A is valid, V is a warning but we can get good data still?
2277 // We don't currently check the data valid flag.
2285 // Extract data for $GPGGA
2287 // 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]
2288 // 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]
2297 // 6: Estimated (dead-reckoning) Fix
2298 // 7: Manual Input Mode
2299 // 8: Simulation Mode
2301 // $GPGGA,170834,4124.8963,N,08151.6838,W,1,05,1.5,280.2,M,-34.0,M,,,*75
2302 // $GPGGA,104438.833,4301.1439,N,08803.0338,W,1,05,1.8,185.8,M,-34.2,M,0.0,0000*40
2304 // nsat=Number of Satellites being tracked
2307 int extract_GGA(AprsDataRow *p_station,char *data,char *call_sign, char *path, int *num_digits) {
2308 char temp_data[40]; // short term string storage, MAX_CALLSIGN, ... ???
2312 char *Substring[15]; // Pointers to substrings parsed by split_string()
2313 char temp_string[MAX_MESSAGE_LENGTH+1];
2318 ok = 0; // Start out as invalid. If we get enough info, we change this to a 1.
2320 if ( (data == NULL) || (strlen(data) < 32) ) // Not enough data to parse position from.
2323 p_station->record_type = NORMAL_GPS_GGA;
2324 // Create a timestamp from the current time
2325 // get_time saves the time in temp_data
2326 xastir_snprintf(p_station->pos_time,
2327 sizeof(p_station->pos_time),
2329 get_time(temp_data));
2330 p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
2332 /* check aprs type on call sign */
2333 p_station->aprs_symbol = *id_callsign(call_sign, path);
2335 // Make a copy of the incoming data. The string passed to
2336 // split_string() gets destroyed.
2337 xastir_snprintf(temp_string,
2338 sizeof(temp_string),
2341 split_string(temp_string, Substring, 15);
2343 // The Substring[] array contains pointers to each substring in
2344 // the original data string.
2347 // 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]
2348 // 0 1 2 3 4 5 6 7 8 9 1 1 1 1 1
2351 if (Substring[0] == NULL) // No GPGGA string
2354 if (Substring[1] == NULL) // No time string
2357 if (Substring[2] == NULL) // No latitude string
2360 if (Substring[3] == NULL) // No latitude N/S
2363 // Need to check lat_s for validity here. Note that some GPS's put out another digit of precision
2364 // (4801.1234). Next character after digits should be a ','
2366 // Count digits after the decimal point for latitude
2367 if (strchr(Substring[2],'.')) {
2368 *num_digits = strlen(Substring[2]) - (int)(strchr(Substring[2],'.') - Substring[2]) - 1;
2374 temp_char = toupper((int)Substring[3][0]);
2376 if (temp_char != 'N' && temp_char != 'S') // Bad N/S
2379 xastir_snprintf(lat_s,
2385 if (Substring[4] == NULL) // No longitude string
2388 if (Substring[5] == NULL) // No longitude E/W
2391 // Need to check long_s for validity here. Should be all digits. Note that some GPS's put out another
2392 // digit of precision. (12201.1234). Next character after digits should be a ','
2394 temp_char = toupper((int)Substring[5][0]);
2396 if (temp_char != 'E' && temp_char != 'W') // Bad E/W
2399 xastir_snprintf(long_s,
2405 p_station->coord_lat = convert_lat_s2l(lat_s);
2406 p_station->coord_lon = convert_lon_s2l(long_s);
2408 // If we've made it this far, We have enough for a position now!
2412 // Now that we have a basic position, let's see what other data
2413 // can be parsed from the packet. The rest of it can still be
2414 // corrupt, so we're proceeding carefully under yellow alert on
2415 // impulse engines only.
2417 // Check for valid fix {
2418 if (Substring[6] == NULL
2419 || Substring[6][0] == '0' // Fix quality
2420 || Substring[7] == NULL // Sat number
2421 || Substring[8] == NULL // hdop
2422 || Substring[9] == NULL) { // Altitude in meters
2423 p_station->sats_visible[0] = '\0'; // Store empty sats visible
2424 p_station->altitude[0] = '\0';; // Store empty altitude
2425 return(ok); // A field between fix quality and altitude is missing
2428 // Need to check for validity of this number. Should be 0-12? Perhaps a few more with WAAS, GLONASS, etc?
2429 temp_num = atoi(Substring[7]);
2430 if (temp_num < 0 || temp_num > 30) {
2431 return(ok); // Number of satellites not valid
2435 xastir_snprintf(p_station->sats_visible,
2436 sizeof(p_station->sats_visible),
2442 // Check for valid number for HDOP instead of just throwing it away?
2445 xastir_snprintf(p_station->altitude,
2446 sizeof(p_station->altitude),
2448 Substring[9]); // Get altitude
2450 // Need to check for valid altitude before conversion
2452 // unit is in meters, if not adjust value ???
2454 if (Substring[10] == NULL) // No units for altitude
2457 if (Substring[10][0] != 'M') {
2458 //fprintf(stderr,"ERROR: should adjust altitude for meters\n");
2459 //} else { // Altitude units wrong. Assume altitude bad
2460 p_station->altitude[0] = '\0';
2468 static void extract_multipoints(AprsDataRow *p_station,
2471 int remove_string) {
2472 // If they're in there, the multipoints start with the
2473 // sequence <space><rbrace><lower><digit> and end with a <lbrace>.
2474 // In addition, there must be no spaces in there, and there
2475 // must be an even number of characters (after the lead-in).
2487 //fprintf(stderr,"Data: %s\t\t", data);
2489 data_size = strlen(data);
2491 end = data + (strlen(data) - 7); // 7 == 3 lead-in chars, plus 2 points
2493 p_station->num_multipoints = 0;
2496 for (p = data; !found && p <= end; ++p) {
2497 if (*p == ' ' && *(p+1) == RBRACE && islower((int)*(p+2)) && isdigit((int)*(p+3)) &&
2498 (p2 = strchr(p+4, LBRACE)) != NULL && ((p2 - p) % 2) == 1) {
2504 // Start looking at the beginning of the data.
2508 // Look for the opening string.
2510 while (!found && p < end && (p = strstr(p, START_STR)) != NULL) {
2511 // The opening string was found. Check the following information.
2513 if (islower((int)*(p+2)) && g_ascii_isdigit(*(p+3)) && (p2 = strchr(p+4, LBRACE)) != NULL && ((p2 - p) % 2) == 1) {
2514 // It all looks good!
2519 // The following characters are not right. Advance and
2529 char *m_start = p; // Start of multipoint string
2532 // The second character (the lowercase) indicates additional style information,
2533 // such as color, line type, etc.
2535 p_station->style = *(p+2);
2537 // The third character (the digit) indicates the way the points should be
2538 // used. They may be used to draw a closed polygon, a series of line segments,
2541 p_station->type = *(p+3);
2543 // The fourth character indicates the scale of the coordinates that
2544 // follow. It may range from '!' to 'z'. The value represents the
2545 // unit of measure (1, 0.1, 0.001, etc., in degrees) used in the offsets.
2547 // Use the following formula to convert the char to the value:
2548 // (10 ^ ((c - 33) / 20)) / 10000 degrees
2550 // Finally we have to convert to Xastir units. Xastir stores coordinates
2551 // as hudredths of seconds. There are 360,000 of those per degree, so we
2552 // need to multiply by that factor so our numbers will be converted to
2557 if (*p < '!' || *p > 'z') {
2558 fprintf(stderr,"extract_multipoints: invalid scale character %d\n", *p);
2564 d = pow(10.0, ((d - 33) / 20)) / 10000.0 * 360000.0;
2565 multiplier = (long)d;
2569 // The remaining characters are in pairs. Each pair is the
2570 // offset lat and lon for one of the points. (The offset is
2571 // from the actual location of the object.) Convert each
2572 // character to its numeric value and save it.
2574 while (*p != LBRACE && p_station->num_multipoints < MAX_MULTIPOINTS) {
2575 // The characters are in the range '"' (34 decimal) to 'z' (122). They
2576 // encode values in the range -44 to +44. To convert to the correct
2577 // value 78 is subtracted from the character's value.
2579 int lat_val = *p - 78;
2580 int lon_val = *(p+1) - 78;
2582 // Check for correct values.
2584 if (lon_val < -44 || lon_val > 44 || lat_val < -44 || lat_val > 44) {
2585 char temp[MAX_LINE_SIZE+1];
2588 // Filter the string so we don't send strange
2589 // chars to the xterm
2590 for (i = 0; i < (int)strlen(data); i++) {
2591 temp[i] = data[i] & 0x7f;
2592 if ( (temp[i] < 0x20) || (temp[i] > 0x7e) )
2595 temp[strlen(data)] = '\0';
2597 fprintf(stderr,"extract_multipoints: invalid value in (filtered) \"%s\": %d,%d\n",
2602 p_station->num_multipoints = 0; // forget any points we already set
2603 ok = 0; // Failure to decode
2607 // Malloc the storage area for this if we don't have
2609 if (p_station->multipoint_data == NULL) {
2611 p_station->multipoint_data = malloc(sizeof(AprsMultipointRow));
2612 if (p_station->multipoint_data == NULL) {
2613 p_station->num_multipoints = 0;
2614 fprintf(stderr,"Couldn't malloc AprsMultipointRow'\n");
2619 // Add the offset to the object's position to obtain the position of the point.
2620 // Note that we're working in Xastir coordinates, and in North America they
2621 // are exactly opposite to lat/lon (larger numbers are farther east and south).
2622 // An offset with a positive value means that the point should be north and/or
2623 // west of the object, so we have to *subtract* the offset to get the correct
2624 // placement in Xastir coordinates.
2625 // TODO: Consider what we should do in the other geographic quadrants. Should we
2626 // check here for the correct sign of the offset? Or should the program that
2627 // creates the offsets take that into account?
2629 p_station->multipoint_data->multipoints[p_station->num_multipoints][0]
2630 = p_station->coord_lon - (lon_val * multiplier);
2631 p_station->multipoint_data->multipoints[p_station->num_multipoints][1]
2632 = p_station->coord_lat - (lat_val * multiplier);
2635 ++p_station->num_multipoints;
2636 } // End of while loop
2639 if (ok && remove_string) {
2640 // We've successfully decoded a multipoint object?
2641 // Remove the multipoint strings (and the sequence
2642 // number at the end if present) from the data string.
2643 // m_start points to the first character (a space). 'p'
2644 // should be pointing at the LBRACE character.
2646 // Make 'p' point to just after the end of the chars
2647 while ( (p < data+strlen(data)) && (*p != ' ') ) {
2650 // The string that 'p' points to now may be empty
2652 // Truncate "data" at the starting brace - 1
2655 // Now we have two strings inside "data". Copy the 2nd
2656 // string directly onto the end of the first.
2657 strncat(data, p, data_size+1);
2659 // The multipoint string and sequence number should be
2660 // erased now from "data".
2661 //fprintf(stderr,"New Data: %s\n", data);
2671 // Returns time in seconds since the Unix epoch.
2673 time_t sec_now(void) {
2677 ret = time(&timenw);
2682 int is_my_station(AprsDataRow *p_station) {
2683 // if station is owned by me (including SSID)
2684 return(p_station->flag & ST_MYSTATION);
2687 int is_my_object_item(AprsDataRow *p_station) {
2688 // If object/item is owned by me (including SSID)
2689 return(p_station->flag & ST_MYOBJITEM);
2693 * Display text in the status line, text is removed after timeout
2695 void statusline(char *status_text,int update) {
2699 XmTextFieldSetString (text, status_text);
2700 last_statusline = sec_now(); // Used for auto-ID timeout
2706 * Extract text inserted by TNC X-1J4 from start of info line
2708 void extract_TNC_text(char *info) {
2711 if (strncasecmp(info,"thenet ",7) == 0) { // 1st match
2712 len = strlen(info)-1;
2713 for (i=7;i<len;i++) {
2718 if (i>7 && info[i] == ')' && info[i+1] == ' ') { // found
2720 for (j=0;i<=len;i++,j++) {
2730 * Check for valid path and change it to TAPR format
2731 * Add missing asterisk for stations that we must have heard via a digi
2732 * Extract port for KAM TNCs
2733 * Handle igate injection ID formats: "callsign-ssid,I" & "callsign-0ssid"
2736 * KC2ELS-1*>SX0PWT,RELAY,WIDE:`2`$l##>/>"4)}
2739 * KC2ELS-1*>RELAY>WIDE>SX0PWT:`2`$l##>/>"4)}
2742 int valid_path(char *path) {
2744 int type,ast,allast,ins;
2748 len = (int)strlen(path);
2749 type = 0; // 0: unknown, 1: AEA '>', 2: TAPR2 ',', 3: mixed
2754 // There are some multi-port TNCs that deliver the port at the end
2755 // of the path. For now we discard this information. If there is
2756 // multi-port TNC support some day, we should write the port into
2759 // KPC-9612: /0 /1 /2
2760 if (len > 2 && path[len-2] == '/') {
2762 if (ch == 'V' || ch == 'H' || ch == '0' || ch == '1' || ch == '2') {
2764 len = (int)strlen(path);
2769 // One way of adding igate injection ID is to add "callsign-ssid,I".
2770 // We need to remove the ",I" portion so it doesn't count as another
2771 // digi here. This should be at the end of the path.
2772 if (len > 2 && path[len-2] == ',' && path[len-1] == 'I') { // Found ",I"
2773 //fprintf(stderr,"%s\n",path);
2774 //fprintf(stderr,"Found ',I'\n");
2776 len = (int)strlen(path);
2777 //fprintf(stderr,"%s\n\n",path);
2779 // Now look for the same thing but with a '*' character at the end.
2780 // This should be at the end of the path.
2781 if (len > 3 && path[len-3] == ',' && path[len-2] == 'I' && path[len-1] == '*') { // Found ",I*"
2782 //fprintf(stderr,"%s\n",path);
2783 //fprintf(stderr,"Found ',I*'\n");
2785 len = (int)strlen(path);
2786 //fprintf(stderr,"%s\n\n",path);
2790 // Another method of adding igate injection ID is to add a '0' in front of
2791 // the SSID. For WE7U it would change to WE7U-00, for WE7U-15 it would
2792 // change to WE7U-015. Take out this zero so the rest of the decoding will
2793 // work. This should be at the end of the path.
2794 // Also look for the same thing but with a '*' character at the end.
2796 for (i=len-1; i>len-6; i--) {
2797 if (path[i] == '-' && path[i+1] == '0') {
2798 //fprintf(stderr,"%s\n",path);
2799 for (j=i+1; j<len; j++) {
2800 path[j] = path[j+1]; // Shift everything left by one
2802 len = (int)strlen(path);
2803 //fprintf(stderr,"%s\n\n",path);
2805 // Check whether we just chopped off the '0' from "-0".
2806 // If so, chop off the dash as well.
2807 if (path[i] == '-' && path[i+1] == '\0') {
2808 //fprintf(stderr,"%s\tChopping off dash\n",path);
2810 len = (int)strlen(path);
2811 //fprintf(stderr,"%s\n",path);
2813 // Check for "-*", change to '*' only
2814 if (path[i] == '-' && path[i+1] == '*') {
2815 //fprintf(stderr,"%s\tChopping off dash\n",path);
2818 len = (int)strlen(path);
2819 //fprintf(stderr,"%s\n",path);
2821 // Check for "-0" or "-0*". Change to "" or "*".
2822 if ( path[i] == '-' && path[i+1] == '0' ) {
2823 //fprintf(stderr,"%s\tShifting left by two\n",path);
2824 for (j=i; j<len; j++) {
2825 path[j] = path[j+2]; // Shift everything left by two
2827 len = (int)strlen(path);
2828 //fprintf(stderr,"%s\n",path);
2834 for (i=0,j=0; i<len; i++) {
2837 if (ch == '>' || ch == ',') { // found digi call separator
2838 // We're at the start of a callsign entry in the path
2840 if (ast > 1 || (ast == 1 && i-j > 10) || (ast == 0 && (i == j || i-j > 9))) {
2841 return(0); // more than one asterisk in call or wrong call size
2843 ast = 0; // reset local asterisk counter
2845 j = i+1; // set to start of next call
2847 type |= 0x02; // set TAPR2 flag
2849 type |= 0x01; // set AEA flag (found '>')
2850 hops++; // count hops
2853 else { // digi call character or asterisk
2854 // We're in the middle of a callsign entry
2857 ast++; // count asterisks in call
2858 allast++; // count asterisks in path
2860 else if ((ch <'A' || ch > 'Z') // Not A-Z
2861 && (ch <'a' || ch > 'z') // Not a-z
2862 && (ch <'0' || ch > '9') // Not 0-9
2864 // Note that Q-construct and internet callsigns can
2865 // have a-z in them, AX.25 callsigns cannot unless
2866 // they are in a 3rd-party packet.
2868 return(0); // wrong character in path
2872 if (ast > 1 || (ast == 1 && i-j > 10) || (ast == 0 && (i == j || i-j > 9))) {
2873 return(0); // more than one asterisk or wrong call size
2877 return(0); // wrong format, both '>' and ',' in path
2880 if (hops > 9) { // [APRS Reference chapter 3]
2881 return(0); // too much hops, destination + 0-8 digipeater addresses
2890 for (i=0; i<len; i++) {
2891 if (path[i] == '>') {
2892 path[i] = ','; // Exchange separator character
2893 delimiters[k++] = i; // Save the delimiter indexes
2897 // We also need to move the destination callsign to the end.
2898 // AEA has them in a different order than TAPR-2 format.
2899 // We'll move the destination address between delimiters[0]
2900 // and [1] to the end of the string.
2902 //fprintf(stderr,"Orig. Path:%s\n",path);
2903 // Save the destination
2904 xastir_snprintf(dest,sizeof(dest),"%s",&path[delimiters[--k]+1]);
2905 dest[strlen(path) - delimiters[k] - 1] = '\0'; // Terminate it
2906 dest[14] = '\0'; // Just to make sure
2907 path[delimiters[k]] = '\0'; // Delete it from the original path
2908 //fprintf(stderr,"Destination: %s\n",dest);
2911 // KC2ELS-1*>SX0PWT,RELAY,WIDE:`2`$l##>/>"4)}
2914 // KC2ELS-1*>RELAY>WIDE>SX0PWT:`2`$l##>/>"4)}
2917 // We now need to insert the destination into the middle of
2918 // the string. Save part of it in another variable first.
2919 xastir_snprintf(rest,
2923 //fprintf(stderr,"Rest:%s\n",rest);
2924 xastir_snprintf(path,len+1,"%s,%s",dest,rest);
2925 //fprintf(stderr,"New Path:%s\n",path);
2928 if (allast < 1) { // try to insert a missing asterisk
2932 for (i=0; i<len; i++) {
2934 for (j=i; j<len; j++) { // search for separator
2939 if (hops > 0 && (j - i) == 5) { // WIDE3
2940 if ( path[ i ] == 'W' && path[i+1] == 'I' && path[i+2] == 'D'
2941 && path[i+3] == 'E' && path[i+4] >= '0' && path[i+4] <= '9') {
2947 Don't do this! It can mess up relay/wide1-1 digipeating by adding
2948 an asterisk later in the path than the first unused digi.
2949 if (hops > 0 && (j - i) == 7) { // WIDE3-2
2950 if ( path[ i ] == 'W' && path[i+1] == 'I' && path[i+2] == 'D'
2951 && path[i+3] == 'E' && path[i+4] >= '0' && path[i+4] <= '9'
2952 && path[i+5] == '-' && path[i+6] >= '0' && path[i+6] <= '9'
2953 && (path[i+4] != path[i+6]) ) {
2959 if (hops > 0 && (j - i) == 6) { // TRACE3
2960 if ( path[ i ] == 'T' && path[i+1] == 'R' && path[i+2] == 'A'
2961 && path[i+3] == 'C' && path[i+4] == 'E'
2962 && path[i+5] >= '0' && path[i+5] <= '9') {
2971 Don't do this! It can mess up relay/wide1-1 digipeating by adding
2972 an asterisk later in the path than the first unused digi.
2973 if (hops > 0 && (j - i) == 8) { // TRACE3-2
2974 if ( path[ i ] == 'T' && path[i+1] == 'R' && path[i+2] == 'A'
2975 && path[i+3] == 'C' && path[i+4] == 'E' && path[i+5] >= '0'
2976 && path[i+5] <= '9' && path[i+6] == '-' && path[i+7] >= '0'
2977 && path[i+7] <= '9' && (path[i+5] != path[i+7]) ) {
2987 i = j; // skip to start of next call
2990 for (i=len;i>=ins;i--) {
2991 path[i+1] = path[i]; // generate space for '*'
2992 // we work on a separate path copy which is long enough to do it
2994 path[ins] = '*'; // and insert it
2997 return(1); // Path is good
3003 char *remove_leading_spaces(char *data) {
3010 if (strlen(data) == 0)
3014 // Count the leading space characters
3015 for (i = 0; i < (int)strlen(data); i++) {
3016 if (data[i] == ' ') {
3019 else { // Found a non-space
3024 // Check whether entire string was spaces
3025 if (count == (int)strlen(data)) {
3029 else if (count > 0) { // Found some spaces
3031 for( j = count; j < (int)strlen(data); j++ ) {
3032 data[i++] = data[j]; // Move string left
3034 data[i] = '\0'; // Terminate the new string
3040 int is_num_chr(char ch) {
3041 return((int)isdigit(ch));
3044 char *remove_trailing_spaces(char *data) {
3050 if (strlen(data) == 0)
3053 for(i=strlen(data)-1;i>=0;i--)
3064 char *remove_trailing_asterisk(char *data) {
3070 if (strlen(data) == 0)
3073 // Should the test here be i>=0 ??
3074 for(i=strlen(data)-1;i>0;i--) {
3081 //--------------------------------------------------------------------
3082 //Removes all control codes ( <0x20 or >0x7e ) from a string, including
3083 // CR's, LF's, tab's, etc.
3085 void makePrintable(char *cp) {
3087 int len = (int)strlen(cp);
3088 unsigned char *ucp = (unsigned char *)cp;
3090 for (i=0, j=0; i<=len; i++) {
3091 ucp[i] &= 0x7f; // Clear 8th bit
3092 if ( ((ucp[i] >= (unsigned char)0x20) && (ucp[i] <= (unsigned char)0x7e))
3093 || ((char)ucp[i] == '\0') ) // Check for printable or terminating 0
3094 ucp[j++] = ucp[i] ; // Copy to (possibly) new location if printable
3102 * Check for a valid AX.25 call
3103 * Valid calls consist of up to 6 uppercase alphanumeric characters
3104 * plus optional SSID (four-bit integer) [APRS Reference, AX.25 Reference]
3106 int valid_call(char *call) {
3108 int i, del, has_num, has_chr;
3114 len = (int)strlen(call);
3117 return(0); // wrong size
3119 while (call[0]=='c' && call[1]=='m' && call[2]=='d' && call[3]==':') {
3120 // Erase TNC prompts from beginning of callsign. This may
3121 // not be the right place to do this, but it came in handy
3122 // here, so that's where I put it. -- KB6MER
3124 for(i=0; call[i+4]; i++)
3136 return(0); // Too long for valid call (6-2 max e.g. KB6MER-12)
3139 for (i=len-2;ok && i>0 && i>=len-3;i--) { // search for optional SSID
3141 del = i; // found the delimiter
3143 if (del) { // we have a SSID, so check it
3144 if (len-del == 2) { // 2 char SSID
3145 if (call[del+1] < '1' || call[del+1] > '9') // -1 ... -9
3148 else { // 3 char SSID
3149 if (call[del+1] != '1' || call[del+2] < '0' || call[del+2] > '5') // -10 ... -15
3155 len = del; // length of base call
3157 for (i=0;ok && i<len;i++) { // check for uppercase alphanumeric
3160 if (c >= 'A' && c <= 'Z')
3161 has_chr = 1; // we need at least one char
3162 else if (c >= '0' && c <= '9')
3163 has_num = 1; // we need at least one number
3165 ok = 0; // wrong character in call
3168 // if (!has_num || !has_chr) // with this we also discard NOCALL etc.
3172 ok = (ok && strcmp(call,"NOCALL") != 0); // check for errors
3173 ok = (ok && strcmp(call,"ERROR!") != 0);
3174 ok = (ok && strcmp(call,"WIDE") != 0);
3175 ok = (ok && strcmp(call,"RELAY") != 0);
3176 ok = (ok && strcmp(call,"MAIL") != 0);
3183 * Check whether callsign is mine. "exact == 1" checks the SSID
3184 * for a match as well. "exact == 0" checks only the base
3187 int is_my_call(char *call, int exact) {
3192 // U.S. special-event callsigns can be as short as three
3193 // characters, any less and we don't have a valid callsign. We
3194 // don't check for that restriction here though.
3197 // We're looking for an exact match
3198 ok = (int)( !strcmp(call,_aprs_mycall ) );
3199 //fprintf(stderr,"My exact call found: %s\n",call);
3202 // We're looking for a similar match. Compare only up to
3203 // the '-' in each (if present).
3206 p_del = index(call,'-');
3208 len1 = (int)strlen(call);
3210 len1 = p_del - call;
3212 p_del = index(_aprs_mycall,'-');
3214 len2 = (int)strlen(_aprs_mycall);
3216 len2 = p_del - _aprs_mycall;
3218 ok = (int)(len1 == len2 && !strncmp(call,_aprs_mycall,(size_t)len1));
3219 //fprintf(stderr,"My base call found: %s\n",call);
3229 * Check for a valid internet name.
3230 * Accept darned-near anything here as long as it is the proper
3231 * length and printable.
3233 int valid_inet_name(char *name, char *info, char *origin, int origin_size) {
3237 len = (int)strlen(name);
3239 if (len > 9 || len == 0) // max 9 printable ASCII characters
3240 return(0); // wrong size
3243 if (!isprint((int)name[i]))
3244 return(0); // not printable
3246 // Modifies "origin" if a match found
3248 if (len >= 5 && strncmp(name,"aprsd",5) == 0) {
3249 snprintf(origin, origin_size, "INET");
3250 origin[4] = '\0'; // Terminate it
3251 return(1); // aprsdXXXX is ok
3254 // Modifies "origin" if a match found
3256 if (len == 6) { // check for NWS
3259 if (name[i] <'A' || name[i] > 'Z') // 6 uppercase characters
3261 ok = ok && (info != NULL); // check if we can test info
3263 ptr = strstr(info,":NWS-"); // "NWS-" in info field (non-compressed alert)
3267 ptr = strstr(info,":NWS_"); // "NWS_" in info field (compressed alert)
3272 snprintf(origin, origin_size, "INET-NWS");
3274 return(1); // weather alerts
3278 return(1); // Accept anything else if we get to this point in
3279 // the code. After all, the message came from the
3280 // internet, not from RF.
3286 * Extract third-party traffic from information field before processing
3288 int extract_third_party(char *call,
3298 p_call = NULL; // to make the compiler happy...
3299 p_path = NULL; // to make the compiler happy...
3301 if (!is_my_call(call,1)) { // Check SSID also
3302 // todo: add reporting station call to database ??
3303 // but only if not identical to reported call
3304 (*info) = (*info) +1; // strip '}' character
3305 p_call = strtok((*info),">"); // extract call
3306 if (p_call != NULL) {
3307 p_path = strtok(NULL,":"); // extract path
3308 if (p_path != NULL) {
3309 (*info) = strtok(NULL,""); // rest is information field
3310 if ((*info) != NULL) // the above looks dangerous, but works on same string
3311 if (strlen(p_path) < 100)
3312 ok = 1; // we have found all three components
3324 ok = valid_path(path); // check the path and convert it to TAPR format
3325 // Note that valid_path() also removes igate injection identifiers
3329 if (ok) { // check callsign
3330 (void)remove_trailing_asterisk(p_call); // is an asterisk valid here ???
3331 if (valid_inet_name(p_call,(*info),origin,origin_size)) { // accept some of the names used in internet
3332 // Treat it as object with special origin
3338 else if (valid_call(p_call)) { // accept real AX.25 calls
3355 * Extract Compressed Position Report Data Formats from begin of line
3356 * [APRS Reference, chapter 9]
3358 * If a position is found, it is deleted from the data. If a
3359 * compressed position is found, delete the three csT bytes as well,
3360 * even if all spaces.
3361 * Returns 0 if the packet is NOT a properly compressed position
3362 * packet, returns 1 if ok.
3364 int extract_comp_position(AprsDataRow *p_station, char **info, /*@unused@*/ int type) {
3366 int x1, x2, x3, x4, y1, y2, y3, y4;
3380 // Check leading char. Must be one of these:
3389 || ( L >= 'A' && L <= 'Z' )
3390 || ( L >= 'a' && L <= 'j' ) ) {
3391 // We're good so far
3394 // Note one of the symbol table or overlay characters, so
3395 // there's something funky about this packet. It's not a
3396 // properly formatted compressed position.
3400 //fprintf(stderr,"my_data: %s\n",my_data);
3402 // If c = space, csT bytes are ignored. Minimum length: 8
3403 // bytes for lat/lon, 2 for symbol, 3 for csT for a total of 13.
3404 len = strlen(my_data);
3405 ok = (int)(len >= 13);
3408 y1 = (int)my_data[1] - '!';
3409 y2 = (int)my_data[2] - '!';
3410 y3 = (int)my_data[3] - '!';
3411 y4 = (int)my_data[4] - '!';
3412 x1 = (int)my_data[5] - '!';
3413 x2 = (int)my_data[6] - '!';
3414 x3 = (int)my_data[7] - '!';
3415 x4 = (int)my_data[8] - '!';
3418 if (my_data[10] == ' ') // Space
3419 c = -1; // This causes us to ignore csT
3421 c = (int)my_data[10] - '!';
3422 s = (int)my_data[11] - '!';
3423 T = (int)my_data[12] - '!';
3427 // Convert ' ' to '0'. Not specified in APRS Reference! Do
3429 if (x1 == -1) x1 = '\0';
3430 if (x2 == -1) x2 = '\0';
3431 if (x3 == -1) x3 = '\0';
3432 if (x4 == -1) x4 = '\0';
3433 if (y1 == -1) y1 = '\0';
3434 if (y2 == -1) y2 = '\0';
3435 if (y3 == -1) y3 = '\0';
3436 if (y4 == -1) y4 = '\0';
3438 ok = (int)(ok && (x1 >= '\0' && x1 < 91)); // /YYYYXXXX$csT
3439 ok = (int)(ok && (x2 >= '\0' && x2 < 91)); // 0123456789012
3440 ok = (int)(ok && (x3 >= '\0' && x3 < 91));
3441 ok = (int)(ok && (x4 >= '\0' && x4 < 91));
3442 ok = (int)(ok && (y1 >= '\0' && y1 < 91));
3443 ok = (int)(ok && (y2 >= '\0' && y2 < 91));
3444 ok = (int)(ok && (y3 >= '\0' && y3 < 91));
3445 ok = (int)(ok && (y4 >= '\0' && y4 < 91));
3447 T &= 0x3F; // DK7IN: force Compression Byte to valid format
3448 // mask off upper two unused bits, they should be zero!?
3450 ok = (int)(ok && (c == -1 || ((c >=0 && c < 91) && (s >= 0 && s < 91) && (T >= 0 && T < 64))));
3453 lat = (((y1 * 91 + y2) * 91 + y3) * 91 + y4 ) / 380926.0; // in deg, 0: 90°N
3454 lon = (((x1 * 91 + x2) * 91 + x3) * 91 + x4 ) / 190463.0; // in deg, 0: 180°W
3455 lat *= 60 * 60 * 100; // in 1/100 sec
3456 lon *= 60 * 60 * 100; // in 1/100 sec
3458 // The below check should _not_ be done. Compressed
3459 // format can resolve down to about 1 foot worldwide
3461 //if ((((long)(lat+4) % 60) > 8) || (((long)(lon+4) % 60) > 8))
3462 // ok = 0; // check max resolution 0.01 min to
3463 // catch even more errors
3468 overlay_symbol(my_data[9], my_data[0], p_station); // Symbol / Table
3470 // Callsign check here includes checking SSID for an exact
3472 // if (!is_my_call(p_station->call_sign,1)) { // don't change my position, I know it better...
3473 if ( !(is_my_station(p_station)) ) { // don't change my position, I know it better...
3475 // Record the uncompressed lat/long that we just
3477 p_station->coord_lat = (long)((lat)); // in 1/100 sec
3478 p_station->coord_lon = (long)((lon)); // in 1/100 sec
3481 if (c >= 0) { // ignore csT if c = ' '
3482 if (c < 90) { // Found course/speed or altitude bytes
3483 if ((T & 0x18) == 0x10) { // check for GGA (with altitude)
3484 xastir_snprintf(p_station->altitude, sizeof(p_station->altitude), "%06.0f",pow(1.002,(double)(c*91+s))*0.3048);
3486 else { // Found compressed course/speed bytes
3488 // Convert 0 degrees to 360 degrees so that
3489 // Xastir will see it as a valid course and do
3490 // dead-reckoning properly on this station
3495 // Compute course in degrees
3496 xastir_snprintf(p_station->course,
3497 sizeof(p_station->course),
3501 // Compute speed in knots
3502 xastir_snprintf(p_station->speed,
3503 sizeof(p_station->speed),
3505 pow( 1.08,(double)s ) - 1.0);
3507 //fprintf(stderr,"Decoded speed:%s, course:%s\n",p_station->speed,p_station->course);
3511 else { // Found pre-calculated radio range bytes
3513 // pre-calculated radio range
3514 range = 2 * pow(1.08,(double)s); // miles
3516 // DK7IN: dirty hack... but better than nothing
3517 if (s <= 5) // 2.9387 mi
3518 xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "000");
3519 else if (s <= 17) // 7.40 mi
3520 xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "111");
3521 else if (s <= 36) // 31.936 mi
3522 xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "222");
3523 else if (s <= 75) // 642.41 mi
3524 xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "333");
3525 else // max 90: 2037.8 mi
3526 xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "444");
3530 (*info) += skip; // delete position from comment
3534 //fprintf(stderr," extract_comp_position end: %s\n",*info);
3541 * Extract bearing and number/range/quality from beginning of info field
3543 int extract_bearing_NRQ(char *info, char *bearing, char *nrq) {
3546 len = (int)strlen(info);
3550 for(i=1; found && i<8; i++) // check data format
3551 if(!(isdigit((int)info[i]) || (i==4 && info[i]=='/')))
3555 substr(bearing,info+1,3);
3556 substr(nrq,info+5,3);
3558 for (i=0;i<=len-8;i++) // delete bearing/nrq from info field
3559 info[i] = info[i+8];
3572 // APRS Data Extensions [APRS Reference p.27]
3573 // .../... Course & Speed, may be followed by others (see p.27)
3574 // .../... Wind Dir and Speed
3575 // PHG.... Station Power and Effective Antenna Height/Gain
3576 // RNG.... Pre-Calculated Radio Range
3577 // DFS.... DF Signal Strength and Effective Antenna Height/Gain
3578 // T../C.. Area Object Descriptor
3580 /* Extract one of several possible APRS Data Extensions */
3581 void process_data_extension(AprsDataRow *p_station, char *data, /*@unused@*/ int type) {
3588 if (p_station->aprs_symbol.aprs_type == '\\' && p_station->aprs_symbol.aprs_symbol == 'l') {
3589 /* This check needs to come first because the area object extension can look
3590 exactly like what extract_speed_course will attempt to decode. */
3591 extract_area(p_station, data);
3594 clear_area(p_station); // we got a packet with a non area symbol, so clear the data
3596 if (extract_speed_course(data,temp1,temp2)) { // ... from Mic-E, etc.
3597 //fprintf(stderr,"extracted speed/course\n");
3599 if (atof(temp2) > 0) {
3600 //fprintf(stderr,"course is non-zero\n");
3601 xastir_snprintf(p_station->speed,
3602 sizeof(p_station->speed),
3605 xastir_snprintf(p_station->course, // in degrees
3606 sizeof(p_station->course),
3611 if (extract_bearing_NRQ(data, bearing, nrq)) { // Beam headings from DF'ing
3612 //fprintf(stderr,"extracted bearing and NRQ\n");
3613 xastir_snprintf(p_station->bearing,
3614 sizeof(p_station->bearing),
3617 xastir_snprintf(p_station->NRQ,
3618 sizeof(p_station->NRQ),
3621 p_station->signal_gain[0] = '\0'; // And blank out the shgd values
3624 // Don't try to extract speed & course if a compressed
3625 // object. Test for beam headings for compressed packets
3627 else if (extract_bearing_NRQ(data, bearing, nrq)) { // Beam headings from DF'ing
3629 //fprintf(stderr,"extracted bearing and NRQ\n");
3630 xastir_snprintf(p_station->bearing,
3631 sizeof(p_station->bearing),
3634 xastir_snprintf(p_station->NRQ,
3635 sizeof(p_station->NRQ),
3638 p_station->signal_gain[0] = '\0'; // And blank out the shgd values
3641 if (extract_powergain_range(data,temp1)) {
3643 //fprintf(stderr,"Found power_gain: %s\n", temp1);
3645 xastir_snprintf(p_station->power_gain,
3646 sizeof(p_station->power_gain),
3650 if (extract_bearing_NRQ(data, bearing, nrq)) { // Beam headings from DF'ing
3651 //fprintf(stderr,"extracted bearing and NRQ\n");
3652 xastir_snprintf(p_station->bearing,
3653 sizeof(p_station->bearing),
3656 xastir_snprintf(p_station->NRQ,
3657 sizeof(p_station->NRQ),
3660 p_station->signal_gain[0] = '\0'; // And blank out the shgd values
3664 if (extract_omnidf(data,temp1)) {
3665 xastir_snprintf(p_station->signal_gain,
3666 sizeof(p_station->signal_gain),
3668 temp1); // Grab the SHGD values
3669 p_station->bearing[0] = '\0'; // And blank out the bearing/NRQ values
3670 p_station->NRQ[0] = '\0';
3672 // The spec shows speed/course before DFS, but example packets that
3673 // come with DOSaprs show DFSxxxx/speed/course. We'll take care of
3674 // that possibility by trying to decode speed/course again.
3675 if (extract_speed_course(data,temp1,temp2)) { // ... from Mic-E, etc.
3676 //fprintf(stderr,"extracted speed/course\n");
3677 if (atof(temp2) > 0) {
3678 //fprintf(stderr,"course is non-zero\n");
3679 xastir_snprintf(p_station->speed,
3680 sizeof(p_station->speed),
3683 xastir_snprintf(p_station->course,
3684 sizeof(p_station->course),
3686 temp2); // in degrees
3690 // The spec shows that omnidf and bearing/NRQ can be in the same
3691 // packet, which makes no sense, but we'll try to decode it that
3693 if (extract_bearing_NRQ(data, bearing, nrq)) { // Beam headings from DF'ing
3694 //fprintf(stderr,"extracted bearing and NRQ\n");
3695 xastir_snprintf(p_station->bearing,
3696 sizeof(p_station->bearing),
3699 xastir_snprintf(p_station->NRQ,
3700 sizeof(p_station->NRQ),
3703 //p_station->signal_gain[0] = '\0'; // And blank out the shgd values
3709 if (extract_signpost(data, temp2)) {
3710 //fprintf(stderr,"extracted signpost data\n");
3711 xastir_snprintf(p_station->signpost,
3712 sizeof(p_station->signpost),
3717 if (extract_probability_min(data, temp3, sizeof(temp3))) {
3718 //fprintf(stderr,"extracted probability_min data: %s\n",temp3);
3719 xastir_snprintf(p_station->probability_min,
3720 sizeof(p_station->probability_min),
3725 if (extract_probability_max(data, temp3, sizeof(temp3))) {
3726 //fprintf(stderr,"extracted probability_max data: %s\n",temp3);
3727 xastir_snprintf(p_station->probability_max,
3728 sizeof(p_station->probability_max),
3737 * Extract altitude from APRS info field "/A=012345" in feet
3739 int extract_altitude(char *info, char *altitude) {
3740 int i,ofs,found,len;
3743 len = (int)strlen(info);
3744 for(ofs=0; !found && ofs<len-8; ofs++) // search for start sequence
3745 if (strncmp(info+ofs,"/A=",3)==0) {
3747 // Are negative altitudes even defined? Yes! In Mic-E spec to -10,000 meters
3748 if(!isdigit((int)info[ofs+3]) && info[ofs+3]!='-') // First char must be digit or '-'
3750 for(i=4; found && i<9; i++) // check data format for next 5 chars
3751 if(!isdigit((int)info[ofs+i]))
3755 ofs--; // was one too much on exit from for loop
3756 substr(altitude,info+ofs+3,6);
3757 for (i=ofs;i<=len-9;i++) // delete altitude from info field
3758 info[i] = info[i+9];
3768 /* extract all available information from info field */
3769 void process_info_field(AprsDataRow *p_station, char *info, /*@unused@*/ int type) {
3770 char temp_data[6+1];
3771 // char time_data[MAX_TIME];
3773 if (extract_altitude(info,temp_data)) { // get altitude
3774 xastir_snprintf(p_station->altitude, sizeof(p_station->altitude), "%.2f",atof(temp_data)*0.3048);
3775 //fprintf(stderr,"%.2f\n",atof(temp_data)*0.3048);
3777 // do other things...
3786 * Extract Uncompressed Position Report from begin of line
3788 * If a position is found, it is deleted from the data.
3790 int extract_position(AprsDataRow *p_station, char **info, int type) {
3794 char temp_grid[8+1];
3800 if (type != APRS_GRID){ // Not a grid
3801 ok = (int)(strlen(my_data) >= 19);
3802 ok = (int)(ok && my_data[4]=='.' && my_data[14]=='.'
3803 && (toupper(my_data[7]) =='N' || toupper(my_data[7]) =='S')
3804 && (toupper(my_data[17])=='E' || toupper(my_data[17])=='W'));
3805 // errors found: [4]: X [7]: n s [17]: w e
3807 ok = is_num_chr(my_data[0]); // 5230.31N/01316.88E>
3808 ok = (int)(ok && is_num_chr(my_data[1])); // 0123456789012345678
3809 ok = (int)(ok && is_num_or_sp(my_data[2]));
3810 ok = (int)(ok && is_num_or_sp(my_data[3]));
3811 ok = (int)(ok && is_num_or_sp(my_data[5]));
3812 ok = (int)(ok && is_num_or_sp(my_data[6]));
3813 ok = (int)(ok && is_num_chr(my_data[9]));
3814 ok = (int)(ok && is_num_chr(my_data[10]));
3815 ok = (int)(ok && is_num_chr(my_data[11]));
3816 ok = (int)(ok && is_num_or_sp(my_data[12]));
3817 ok = (int)(ok && is_num_or_sp(my_data[13]));
3818 ok = (int)(ok && is_num_or_sp(my_data[15]));
3819 ok = (int)(ok && is_num_or_sp(my_data[16]));
3823 overlay_symbol(my_data[18], my_data[8], p_station);
3824 p_station->pos_amb = 0;
3825 // spaces in latitude set position ambiguity, spaces in longitude do not matter
3826 // we will adjust the lat/long to the center of the rectangle of ambiguity
3827 if (my_data[2] == ' ') { // nearest degree
3828 p_station->pos_amb = 4;
3829 my_data[2] = my_data[12] = '3';
3830 my_data[3] = my_data[5] = my_data[6] = '0';
3831 my_data[13] = my_data[15] = my_data[16] = '0';
3833 else if (my_data[3] == ' ') { // nearest 10 minutes
3834 p_station->pos_amb = 3;
3835 my_data[3] = my_data[13] = '5';
3836 my_data[5] = my_data[6] = '0';
3837 my_data[15] = my_data[16] = '0';
3839 else if (my_data[5] == ' ') { // nearest minute
3840 p_station->pos_amb = 2;
3841 my_data[5] = my_data[15] = '5';
3845 else if (my_data[6] == ' ') { // nearest 1/10th minute
3846 p_station->pos_amb = 1;
3847 my_data[6] = my_data[16] = '5';
3850 xastir_snprintf(temp_lat,
3854 temp_lat[7] = toupper(my_data[7]);
3857 xastir_snprintf(temp_lon,
3861 temp_lon[8] = toupper(my_data[17]);
3864 // Callsign check here also checks SSID for an exact
3866 // if (!is_my_call(p_station->call_sign,1)) { // don't change my position, I know it better...
3867 if ( !(is_my_station(p_station)) ) { // don't change my position, I know it better...
3869 p_station->coord_lat = convert_lat_s2l(temp_lat); // ...in case of position ambiguity
3870 p_station->coord_lon = convert_lon_s2l(temp_lon);
3873 (*info) += 19; // delete position from comment
3876 else { // It is a grid
3877 // first sanity checks, need more
3878 ok = (int)(is_num_chr(my_data[2]));
3879 ok = (int)(ok && is_num_chr(my_data[3]));
3880 ok = (int)(ok && ((my_data[0]>='A')&&(my_data[0]<='R')));
3881 ok = (int)(ok && ((my_data[1]>='A')&&(my_data[1]<='R')));
3883 xastir_snprintf(temp_grid,
3887 // this test treats >6 digit grids as 4 digit grids; >6 are uncommon.
3888 // the spec mentioned 4 or 6, I'm not sure >6 is even allowed.
3889 if ( (temp_grid[6] != ']') || (temp_grid[4] == 0) || (temp_grid[5] == 0)){
3890 p_station->pos_amb = 6; // 1deg lat x 2deg lon
3895 p_station->pos_amb = 5; // 2.5min lat x 5min lon
3896 temp_grid[4] = toupper(temp_grid[4]);
3897 temp_grid[5] = toupper(temp_grid[5]);
3899 // These equations came from what I read in the qgrid source code and
3900 // various mailing list archives.
3901 gridlon= (20.*((float)temp_grid[0]-65.) + 2.*((float)temp_grid[2]-48.) + 5.*((float)temp_grid[4]-65.)/60.) - 180.;
3902 gridlat= (10.*((float)temp_grid[1]-65.) + ((float)temp_grid[3]-48.) + 5.*(temp_grid[5]-65.)/120.) - 90.;
3903 // could check for my callsign here, and avoid changing it...
3904 p_station->coord_lat = (unsigned long)(32400000l + (360000.0 * (-gridlat)));
3905 p_station->coord_lon = (unsigned long)(64800000l + (360000.0 * gridlon));
3906 p_station->aprs_symbol.aprs_type = '/';
3907 p_station->aprs_symbol.aprs_symbol = 'G';
3908 } // is it valid grid or not - "ok"
3909 // could cut off the grid square from the comment here, but why bother?
3910 } // is it grid or not
3918 // Add a status line to the linked-list of status records
3919 // associated with a station. Note that a blank status line is
3920 // allowed, but we don't store that unless we have seen a non-blank
3921 // status line previously.
3923 void add_status(AprsDataRow *p_station, char *status_string) {
3924 AprsCommentRow *ptr;
3929 len = strlen(status_string);
3931 // Eliminate line-end chars
3933 if ( (status_string[len-1] == '\n')
3934 || (status_string[len-1] == '\r') ) {
3935 status_string[len-1] = '\0';
3940 (void)remove_trailing_spaces(status_string);
3941 (void)remove_leading_spaces(status_string);
3943 len = strlen(status_string);
3945 // Check for valid pointer
3946 if (p_station != NULL) {
3948 // We should probably create a new station record for this station
3949 // if there isn't one. This allows us to collect as much info about
3950 // a station as we can until a posit comes in for it. Right now we
3951 // don't do this. If we decide to do this in the future, we also
3952 // need a method to find out the info about that station without
3953 // having to click on an icon, 'cuz the symbol won't be on our map
3954 // until we have a posit.
3956 //fprintf(stderr,"Station:%s\tStatus:%s\n",p_station->call_sign,status_string);
3958 // Check whether we have any data stored for this station
3959 if (p_station->status_data == NULL) {
3961 // No status stored yet and new status is non-NULL,
3962 // so add it to the list.
3966 else { // We have status data stored already
3967 // Check for an identical string
3968 AprsCommentRow *ptr2;
3971 ptr = p_station->status_data;
3973 while (ptr != NULL) {
3975 // Note that both text_ptr and comment_string can be
3978 if (strcasecmp(ptr->text_ptr, status_string) == 0) {
3979 // Found a matching string
3980 //fprintf(stderr,"Found match:
3981 //%s:%s\n",p_station->call_sign,status_string);
3983 // Instead of updating the timestamp, we'll delete the record from
3984 // the list and add it to the top in the code below. Make sure to
3985 // tweak the "ii" pointer so that we don't end up shortening the
3986 // list unnecessarily.
3987 if (ptr == p_station->status_data) {
3989 // Only update the timestamp: We're at the
3990 // beginning of the list already.
3991 ptr->sec_heard = sec_now();
3993 return; // No need to add a new record
3995 else { // Delete the record
3996 AprsCommentRow *ptr3;
3998 // Keep a pointer to the record
4001 // Close the chain, skipping this record
4002 ptr2->next = ptr3->next;
4004 // Skip "ptr" over the record we wish to
4009 free(ptr3->text_ptr);
4012 // Muck with the counter 'cuz we just
4013 // deleted one record
4017 ptr2 = ptr; // Back one record
4025 // No matching string found, or new timestamp found for
4026 // old record. Add it to the top of the list.
4028 //fprintf(stderr,"No match:
4029 //%s:%s\n",p_station->call_sign,status_string);
4031 // We counted the records. If we have more than
4032 // MAX_STATUS_LINES records we'll delete/free the last
4033 // one to make room for the next. This keeps us from
4034 // storing unique status records ad infinitum for active
4035 // stations, limiting the total space used.
4037 if (ii >= MAX_STATUS_LINES) {
4038 // We know we didn't get a match, and that our list
4039 // is full (as full as we want it to be). Traverse
4040 // the list again, looking for ptr2->next->next ==
4041 // NULL. If found, free last record and set the
4042 // ptr2->next pointer to NULL.
4043 ptr2 = p_station->status_data;
4044 while (ptr2->next->next != NULL) {
4047 // At this point, we have a pointer to the last
4048 // record in ptr2->next. Free it and the text
4050 free(ptr2->next->text_ptr);
4056 if (add_it) { // We add to the beginning so we don't have
4057 // to traverse the linked list. This also
4058 // puts new records at the beginning of the
4059 // list to keep them in sorted order.
4061 ptr = p_station->status_data; // Save old pointer to records
4062 p_station->status_data = (AprsCommentRow *)malloc(sizeof(AprsCommentRow));
4063 CHECKMALLOC(p_station->status_data);
4065 p_station->status_data->next = ptr; // Link in old records or NULL
4067 // Malloc the string space we'll need, attach it to our
4069 p_station->status_data->text_ptr = (char *)malloc(sizeof(char) * (len+1));
4070 CHECKMALLOC(p_station->status_data->text_ptr);
4072 // Fill in the string
4073 xastir_snprintf(p_station->status_data->text_ptr,
4078 // Fill in the timestamp
4079 p_station->status_data->sec_heard = sec_now();
4081 //fprintf(stderr,"Station:%s\tStatus:%s\n\n",p_station->call_sign,p_station->status_data->text_ptr);
4088 // Add a comment line to the linked-list of comment records
4089 // associated with a station. Note that a blank comment is allowed
4090 // and necessary for the times when we wish to blank out the comment
4091 // on an object/item, but we don't store that unless we have seen a
4092 // non-blank comment line previously.
4094 void add_comment(AprsDataRow *p_station, char *comment_string) {
4095 AprsCommentRow *ptr;
4100 len = strlen(comment_string);
4102 // Eliminate line-end chars
4104 if ( (comment_string[len-1] == '\n')
4105 || (comment_string[len-1] == '\r') ) {
4106 comment_string[len-1] = '\0';
4111 (void)remove_trailing_spaces(comment_string);
4112 (void)remove_leading_spaces(comment_string);
4114 len = strlen(comment_string);
4116 // Check for valid pointer
4117 if (p_station != NULL) {
4119 // Check whether we have any data stored for this station
4120 if (p_station->comment_data == NULL) {
4122 // No comments stored yet and new comment is
4123 // non-NULL, so add it to the list.
4127 else { // We have comment data stored already
4128 // Check for an identical string
4129 AprsCommentRow *ptr2;
4132 ptr = p_station->comment_data;
4134 while (ptr != NULL) {
4136 // Note that both text_ptr and comment_string can be
4139 if (strcasecmp(ptr->text_ptr, comment_string) == 0) {
4140 // Found a matching string
4141 //fprintf(stderr,"Found match: %s:%s\n",p_station->call_sign,comment_string);
4143 // Instead of updating the timestamp, we'll delete the record from
4144 // the list and add it to the top in the code below. Make sure to
4145 // tweak the "ii" pointer so that we don't end up shortening the
4146 // list unnecessarily.
4147 if (ptr == p_station->comment_data) {
4148 // Only update the timestamp: We're at the
4149 // beginning of the list already.
4150 ptr->sec_heard = sec_now();
4152 return; // No need to add a new record
4154 else { // Delete the record
4155 AprsCommentRow *ptr3;
4157 // Keep a pointer to the record
4160 // Close the chain, skipping this record
4161 ptr2->next = ptr3->next;
4163 // Skip "ptr" over the record we with to
4168 free(ptr3->text_ptr);
4171 // Muck with the counter 'cuz we just
4172 // deleted one record
4176 ptr2 = ptr; // Keep this pointer one record back as
4185 // No matching string found, or new timestamp found for
4186 // old record. Add it to the top of the list.
4188 //fprintf(stderr,"No match: %s:%s\n",p_station->call_sign,comment_string);
4190 // We counted the records. If we have more than
4191 // MAX_COMMENT_LINES records we'll delete/free the last
4192 // one to make room for the next. This keeps us from
4193 // storing unique comment records ad infinitum for
4194 // active stations, limiting the total space used.
4196 if (ii >= MAX_COMMENT_LINES) {
4198 // We know we didn't get a match, and that our list
4199 // is full (as we want it to be). Traverse the list
4200 // again, looking for ptr2->next->next == NULL. If
4201 // found, free that last record and set the
4202 // ptr2->next pointer to NULL.
4203 ptr2 = p_station->comment_data;
4204 while (ptr2->next->next != NULL) {
4207 // At this point, we have a pointer to the last
4208 // record in ptr2->next. Free it and the text
4210 free(ptr2->next->text_ptr);
4216 if (add_it) { // We add to the beginning so we don't have
4217 // to traverse the linked list. This also
4218 // puts new records at the beginning of the
4219 // list to keep them in sorted order.
4221 ptr = p_station->comment_data; // Save old pointer to records
4222 p_station->comment_data = (AprsCommentRow *)malloc(sizeof(AprsCommentRow));
4223 CHECKMALLOC(p_station->comment_data);
4225 p_station->comment_data->next = ptr; // Link in old records or NULL
4227 // Malloc the string space we'll need, attach it to our
4229 p_station->comment_data->text_ptr = (char *)malloc(sizeof(char) * (len+1));
4230 CHECKMALLOC(p_station->comment_data->text_ptr);
4232 // Fill in the string
4233 xastir_snprintf(p_station->comment_data->text_ptr,
4238 // Fill in the timestamp
4239 p_station->comment_data->sec_heard = sec_now();
4246 // Extract single weather data item from "data". Returns it in
4247 // "temp". Modifies "data" to remove the found data from the
4248 // string. Returns a 1 if found, 0 if not found.
4251 // If the item is contained in the string but does not contain a
4252 // value then regard the item as "not found" in the weather string.
4254 int extract_weather_item(char *data, char type, int datalen, char *temp) {
4255 int i,ofs,found,len;
4258 //fprintf(stderr,"%s\n",data);
4261 len = (int)strlen(data);
4262 for(ofs=0; !found && ofs<len-datalen; ofs++) // search for start sequence
4263 if (data[ofs]==type) {
4265 if (!is_weather_data(data+ofs+1, datalen))
4268 if (found) { // ofs now points after type character
4269 substr(temp,data+ofs,datalen);
4270 for (i=ofs-1;i<len-datalen;i++) // delete item from info field
4271 data[i] = data[i+datalen+1];
4272 if((temp[0] == ' ') || (temp[0] == '.')) {
4273 // found it, but it doesn't contain a value!
4274 // Clean up and report "not found" - PE1DNN
4280 // fprintf(stderr,"extract_weather_item: %s\n",temp);
4292 // test-extract single weather data item from information field. In
4293 // other words: Does not change the input string, but does test
4294 // whether the data is present. Returns a 1 if found, 0 if not
4298 // If the item is contained in the string but does not contain a
4299 // value then regard the item as "not found" in the weather string.
4301 int test_extract_weather_item(char *data, char type, int datalen) {
4305 len = (int)strlen(data);
4306 for(ofs=0; !found && ofs<len-datalen; ofs++) // search for start sequence
4307 if (data[ofs]==type) {
4309 if (!is_weather_data(data+ofs+1, datalen))
4313 // We really should test for numbers here (with an optional
4314 // leading '-'), and test across the length of the substring.
4316 if(found && ((data[ofs+1] == ' ') || (data[ofs+1] == '.'))) {
4317 // found it, but it doesn't contain a value!
4318 // report "not found" - PE1DNN
4322 //fprintf(stderr,"test_extract: %c %d\n",type,found);
4327 int get_weather_record(AprsDataRow *fill) { // get or create weather storage
4330 if (fill->weather_data == NULL) { // new weather data, allocate storage and init
4331 fill->weather_data = malloc(sizeof(AprsWeatherRow));
4332 if (fill->weather_data == NULL) {
4333 fprintf(stderr,"Couldn't allocate memory in get_weather_record()\n");
4337 init_weather(fill->weather_data);
4347 // raw weather report in information field
4348 // positionless weather report in information field
4349 // complete weather report with lat/lon
4350 // see APRS Reference page 62ff
4352 // Added 'F' for Fuel Temp and 'f' for Fuel Moisture in order to
4353 // decode these two new parameters used for RAWS weather station
4356 // By the time we call this function we've already extracted any
4357 // time/position info at the beginning of the string.
4359 int extract_weather(AprsDataRow *p_station, char *data, int compr) {
4360 char time_data[MAX_TIME];
4363 AprsWeatherRow *weather;
4369 // Try copying the string to a temporary string, then do some
4370 // extractions to see if a few weather items are present? This
4371 // would allow us to have the weather items in any order, and if
4372 // enough of them were present, we consider it to be a weather
4373 // packet? We'd need to qualify all of the data to make sure we had
4374 // the proper number of digits for each. The trick is to make sure
4375 // we don't decide it's a weather packet if it's not. We don't know
4376 // what people might send in packets in the future.
4378 if (compr) { // compressed position report
4379 // Look for weather data in fixed locations first
4380 if (strlen(data) >= 8
4381 && data[0] =='g' && is_weather_data(&data[1],3)
4382 && data[4] =='t' && is_weather_data(&data[5],3)) {
4384 // Snag WX course/speed from compressed position data.
4385 // This speed is in knots. This assumes that we've
4386 // already extracted speed/course from the compressed
4387 // packet. extract_comp_position() extracts
4388 // course/speed as well.
4389 xastir_snprintf(speed,
4393 xastir_snprintf(course,
4399 //fprintf(stderr,"Found compressed wx\n");
4401 // Look for weather data in non-fixed locations (RAWS WX
4403 else if ( strlen(data) >= 8
4404 && test_extract_weather_item(data,'g',3)
4405 && test_extract_weather_item(data,'t',3) ) {
4407 // Snag WX course/speed from compressed position data.
4408 // This speed is in knots. This assumes that we've
4409 // already extracted speed/course from the compressed
4410 // packet. extract_comp_position() extracts
4411 // course/speed as well.
4412 xastir_snprintf(speed,
4416 xastir_snprintf(course,
4422 //fprintf(stderr,"Found compressed WX in non-fixed locations! %s:%s\n",
4423 // p_station->call_sign,data);
4426 else { // No weather data found
4429 //fprintf(stderr,"No compressed wx\n");
4432 else { // Look for non-compressed weather data
4433 // Look for weather data in defined locations first
4434 if (strlen(data)>=15 && data[3]=='/'
4435 && is_weather_data(data,3) && is_weather_data(&data[4],3)
4436 && data[7] =='g' && is_weather_data(&data[8], 3)
4437 && data[11]=='t' && is_weather_data(&data[12],3)) { // Complete Weather Report
4439 // Get speed/course. Speed is in knots.
4440 (void)extract_speed_course(data,speed,course);
4443 // Either one not found? Try again.
4444 if ( (speed[0] == '\0') || (course[0] == '\0') ) {
4446 // Try to get speed/course from 's' and 'c' fields
4447 // (another wx format). Speed is in mph.
4448 (void)extract_weather_item(data,'c',3,course); // wind direction (in degrees)
4449 (void)extract_weather_item(data,'s',3,speed); // sustained one-minute wind speed (in mph)
4453 //fprintf(stderr,"Found Complete Weather Report\n");
4455 // Look for date/time and weather in fixed locations first
4456 else if (strlen(data)>=16
4457 && data[0] =='c' && is_weather_data(&data[1], 3)
4458 && data[4] =='s' && is_weather_data(&data[5], 3)
4459 && data[8] =='g' && is_weather_data(&data[9], 3)
4460 && data[12]=='t' && is_weather_data(&data[13],3)) { // Positionless Weather Data
4461 //fprintf(stderr,"Found positionless wx data\n");
4462 // Try to snag speed/course out of first 7 bytes. Speed
4464 (void)extract_speed_course(data,speed,course);
4467 // Either one not found? Try again.
4468 if ( (speed[0] == '\0') || (course[0] == '\0') ) {
4469 //fprintf(stderr,"Trying again for course/speed\n");
4470 // Also try to get speed/course from 's' and 'c' fields
4471 // (another wx format)
4472 (void)extract_weather_item(data,'c',3,course); // wind direction (in degrees)
4473 (void)extract_weather_item(data,'s',3,speed); // sustained one-minute wind speed (in mph)
4477 //fprintf(stderr,"Found weather\n");
4479 // Look for weather data in non-fixed locations (RAWS WX
4481 else if (strlen (data) >= 16
4482 && test_extract_weather_item(data,'h',2)
4483 && test_extract_weather_item(data,'g',3)
4484 && test_extract_weather_item(data,'t',3) ) {
4486 // Try to snag speed/course out of first 7 bytes. Speed
4488 (void)extract_speed_course(data,speed,course);
4491 // Either one not found? Try again.
4492 if ( (speed[0] == '\0') || (course[0] == '\0') ) {
4494 // Also try to get speed/course from 's' and 'c' fields
4495 // (another wx format)
4496 (void)extract_weather_item(data,'c',3,course); // wind direction (in degrees)
4497 (void)extract_weather_item(data,'s',3,speed); // sustained one-minute wind speed (in mph)
4501 //fprintf(stderr,"Found WX in non-fixed locations! %s:%s\n",
4502 // p_station->call_sign,data);
4504 else { // No weather data found
4507 //fprintf(stderr,"No wx found\n");
4512 ok = get_weather_record(p_station); // get existing or create new weather record
4516 weather = p_station->weather_data;
4518 // Copy into weather speed variable. Convert knots to mph
4521 xastir_snprintf(weather->wx_speed,
4522 sizeof(weather->wx_speed),
4524 atoi(speed) * 1.1508); // Convert knots to mph
4527 // Already in mph. Copy w/no conversion.
4528 xastir_snprintf(weather->wx_speed,
4529 sizeof(weather->wx_speed),
4534 xastir_snprintf(weather->wx_course,
4535 sizeof(weather->wx_course),
4539 if (compr) { // course/speed was taken from normal data, delete that
4540 // fix me: we delete a potential real speed/course now
4541 // we should differentiate between normal and weather data in compressed position decoding...
4542 // p_station->speed_time[0] = '\0';
4543 p_station->speed[0] = '\0';
4544 p_station->course[0] = '\0';
4547 (void)extract_weather_item(data,'g',3,weather->wx_gust); // gust (peak wind speed in mph in the last 5 minutes)
4549 (void)extract_weather_item(data,'t',3,weather->wx_temp); // temperature (in deg Fahrenheit), could be negative
4551 (void)extract_weather_item(data,'r',3,weather->wx_rain); // rainfall (1/100 inch) in the last hour
4553 (void)extract_weather_item(data,'p',3,weather->wx_prec_24); // rainfall (1/100 inch) in the last 24 hours
4555 (void)extract_weather_item(data,'P',3,weather->wx_prec_00); // rainfall (1/100 inch) since midnight
4557 if (extract_weather_item(data,'h',2,weather->wx_hum)) // humidity (in %, 00 = 100%)
4558 xastir_snprintf(weather->wx_hum, sizeof(weather->wx_hum), "%03d",(atoi(weather->wx_hum)+99)%100+1);
4560 if (extract_weather_item(data,'b',5,weather->wx_baro)) // barometric pressure (1/10 mbar / 1/10 hPascal)
4561 xastir_snprintf(weather->wx_baro,
4562 sizeof(weather->wx_baro),
4564 (float)(atoi(weather->wx_baro)/10.0));
4566 // If we parsed a speed/course, a second 's' parameter means
4567 // snowfall. Try to parse it, but only in the case where
4568 // we've parsed speed out of this packet already.
4569 if ( (speed[0] != '\0') && (course[0] != '\0') ) {
4570 (void)extract_weather_item(data,'s',3,weather->wx_snow); // snowfall, inches in the last 24 hours
4573 (void)extract_weather_item(data,'L',3,temp); // luminosity (in watts per square meter) 999 and below
4575 (void)extract_weather_item(data,'l',3,temp); // luminosity (in watts per square meter) 1000 and above
4577 (void)extract_weather_item(data,'#',3,temp); // raw rain counter
4579 (void)extract_weather_item(data,'F',3,weather->wx_fuel_temp); // Fuel Temperature in °F (RAWS)
4581 if (extract_weather_item(data,'f',2,weather->wx_fuel_moisture))// Fuel Moisture (RAWS) (in %, 00 = 100%)
4582 xastir_snprintf(weather->wx_fuel_moisture,
4583 sizeof(weather->wx_fuel_moisture),
4585 (atoi(weather->wx_fuel_moisture)+99)%100+1);
4587 // extract_weather_item(data,'w',3,temp); // ?? text wUII
4589 // now there should be the name of the weather station...
4591 // Create a timestamp from the current time
4592 xastir_snprintf(weather->wx_time,
4593 sizeof(weather->wx_time),
4595 get_time(time_data));
4597 // Set the timestamp in the weather record so that we can
4598 // decide whether or not to "ghost" the weather data later.
4599 weather->wx_sec_time=sec_now();
4600 // weather->wx_data=1; // we don't need this
4602 // case ('.'):/* skip */
4608 // weather->wx_type=data[wx_strpos];
4609 // if(strlen(data)>wx_strpos+1)
4610 // xastir_snprintf(weather->wx_station,
4611 // sizeof(weather->wx_station),
4613 // data+wx_strpos+1);
4621 // Initial attempt at decoding tropical storm, tropical depression,
4622 // and hurricane data.
4624 // This data can be in an Object report, but can also be in an Item
4625 // or position report.
4626 // "/TS" = Tropical Storm
4627 // "/HC" = Hurricane
4628 // "/TD" = Tropical Depression
4630 // "/ST" = Super Typhoon
4631 // "/SC" = Severe Cyclone
4633 // The symbol will be either "\@" for current position, or "/@" for
4634 // predicted position.
4636 int extract_storm(AprsDataRow *p_station, char *data, int compr) {
4637 char time_data[MAX_TIME];
4639 AprsWeatherRow *weather;
4641 char speed[4]; // Speed in knots
4645 // Should probably encode the storm type in the weather object and
4646 // print it out in plain text in the Station Info dialog.
4648 if ((p = strstr(data, "/TS")) != NULL) {
4649 // We have a Tropical Storm
4650 //fprintf(stderr,"Tropical Storm! %s\n",data);
4652 else if ((p = strstr(data, "/TD")) != NULL) {
4653 // We have a Tropical Depression
4654 //fprintf(stderr,"Tropical Depression! %s\n",data);
4656 else if ((p = strstr(data, "/HC")) != NULL) {
4657 // We have a Hurricane
4658 //fprintf(stderr,"Hurricane! %s\n",data);
4660 else if ((p = strstr(data, "/TY")) != NULL) {
4661 // We have a Typhoon
4662 //fprintf(stderr,"Hurricane! %s\n",data);
4664 else if ((p = strstr(data, "/ST")) != NULL) {
4665 // We have a Super Typhoon
4666 //fprintf(stderr,"Hurricane! %s\n",data);
4668 else if ((p = strstr(data, "/SC")) != NULL) {
4669 // We have a Severe Cyclone
4670 //fprintf(stderr,"Hurricane! %s\n",data);
4672 else { // Not one of the three we're trying to decode
4677 //fprintf(stderr,"\n%s\n",data);
4679 // Back up 7 spots to try to extract the next items
4682 // Attempt to extract course/speed. Speed in knots.
4683 if (!extract_speed_course(p2,speed,course)) {
4684 // No speed/course to extract
4685 //fprintf(stderr,"No speed/course found\n");
4690 else { // Not enough characters for speed/course. Must have
4691 // guessed wrong on what type of data it is.
4692 //fprintf(stderr,"No speed/course found 2\n");
4698 //fprintf(stderr,"%s\n",data);
4702 // If we got this far, we have speed/course and know what type
4704 //fprintf(stderr,"Speed: %s, Course: %s\n",speed,course);
4706 ok = get_weather_record(p_station); // get existing or create new weather record
4710 // p_station->speed_time[0] = '\0';
4712 p_station->weather_data->wx_storm = 1; // We found a storm
4714 // Note that speed is in knots. If we were stuffing it into
4715 // "wx_speed" we'd have to convert it to MPH.
4716 if (strcmp(speed," ") != 0 && strcmp(speed,"...") != 0) {
4717 xastir_snprintf(p_station->speed,
4718 sizeof(p_station->speed),
4723 p_station->speed[0] = '\0';
4726 if (strcmp(course," ") != 0 && strcmp(course,"...") != 0)
4727 xastir_snprintf(p_station->course,
4728 sizeof(p_station->course),
4732 p_station->course[0] = '\0';
4734 weather = p_station->weather_data;
4736 p2++; // Skip the description text, "/TS", "/HC", "/TD", "/TY", "/ST", or "/SC"
4738 // Extract the sustained wind speed in knots
4739 if(extract_weather_item(p2,'/',3,weather->wx_speed))
4740 // Convert from knots to MPH
4741 xastir_snprintf(weather->wx_speed,
4742 sizeof(weather->wx_speed),
4744 (float)(atoi(weather->wx_speed)) * 1.1508);
4746 //fprintf(stderr,"%s\n",data);
4748 // Extract gust speed in knots
4749 if (extract_weather_item(p2,'^',3,weather->wx_gust)) // gust (peak wind speed in knots)
4750 // Convert from knots to MPH
4751 xastir_snprintf(weather->wx_gust,
4752 sizeof(weather->wx_gust),
4754 (float)(atoi(weather->wx_gust)) * 1.1508);
4756 //fprintf(stderr,"%s\n",data);
4758 // Pressure is already in millibars/hPa. No conversion
4760 if (extract_weather_item(p2,'/',4,weather->wx_baro)) // barometric pressure (1/10 mbar / 1/10 hPascal)
4761 xastir_snprintf(weather->wx_baro,
4762 sizeof(weather->wx_baro),
4764 (float)(atoi(weather->wx_baro)));
4766 //fprintf(stderr,"%s\n",data);
4768 (void)extract_weather_item(p2,'>',3,weather->wx_hurricane_radius); // Nautical miles
4770 //fprintf(stderr,"%s\n",data);
4772 (void)extract_weather_item(p2,'&',3,weather->wx_trop_storm_radius); // Nautical miles
4774 //fprintf(stderr,"%s\n",data);
4776 (void)extract_weather_item(p2,'%',3,weather->wx_whole_gale_radius); // Nautical miles
4778 //fprintf(stderr,"%s\n",data);
4780 // Create a timestamp from the current time
4781 xastir_snprintf(weather->wx_time,
4782 sizeof(weather->wx_time),
4784 get_time(time_data));
4786 // Set the timestamp in the weather record so that we can
4787 // decide whether or not to "ghost" the weather data later.
4788 weather->wx_sec_time=sec_now();
4796 * Free station memory for one entry
4798 void delete_station_memory(AprsDataRow *p_del) {
4809 * Create new uninitialized element in station list
4810 * and insert it before p_name after p_time entries.
4812 * Returns NULL if malloc error.
4814 /*@null@*/ AprsDataRow *insert_new_station(AprsDataRow *p_name, AprsDataRow *p_time) {
4818 p_new = (AprsDataRow *)malloc(sizeof(AprsDataRow));
4820 if (p_new != NULL) { // we really got the memory
4821 p_new->call_sign[0] = '\0'; // just to be sure
4822 p_new->n_next = NULL;
4823 p_new->n_prev = NULL;
4824 p_new->t_newer = NULL;
4825 p_new->t_older = NULL;
4826 insert_name(p_new,p_name); // insert element into name ordered list
4827 insert_time(p_new,p_time); // insert element into time ordered list
4829 else { // p_new == NULL
4830 fprintf(stderr,"ERROR: we got no memory for station storage\n");
4834 return(p_new); // return pointer to new element
4838 // Station storage is done in a double-linked list. In fact there are two such
4839 // pointer structures, one for sorting by name and one for sorting by time.
4840 // We store both the pointers to the next and to the previous elements. DK7IN
4843 * Setup station storage structure
4845 void init_station_data(void) {
4847 station_count = 0; // empty station list
4848 n_first = NULL; // pointer to next element in name sorted list
4849 n_last = NULL; // pointer to previous element in name sorted list
4850 t_oldest = NULL; // pointer to oldest element in time sorted list
4851 t_newest = NULL; // pointer to newest element in time sorted list
4852 last_sec = sec_now(); // check value for detecting changed seconds in time
4853 next_time_sn = 0; // serial number for unique time index
4854 // current_trail_color = 0x00; // first trail color used will be 0x01
4855 last_station_remove = sec_now(); // last time we checked for stations to remove
4861 * Initialize station data
4863 void init_station(AprsDataRow *p_station) {
4864 // the list pointers should already be set
4866 p_station->oldest_trackpoint = NULL; // no trail
4867 p_station->newest_trackpoint = NULL; // no trail
4868 p_station->trail_color = 0;
4869 p_station->weather_data = NULL; // no weather
4870 p_station->coord_lat = 0l; // 90°N \ undefined
4871 p_station->coord_lon = 0l; // 180°W / position
4872 p_station->pos_amb = 0; // No ambiguity
4873 p_station->error_ellipse_radius = 600; // In cm, default 6 meters
4874 p_station->lat_precision = 60; // In 100ths of seconds latitude (60 = 0.01 minutes)
4875 p_station->lon_precision = 60; // In 100ths of seconds longitude (60 = 0.01 minutes)
4876 p_station->call_sign[0] = '\0'; // ?????
4877 p_station->tactical_call_sign = NULL;
4878 p_station->sec_heard = 0;
4879 p_station->time_sn = 0;
4880 p_station->flag = 0; // set all flags to inactive
4881 p_station->object_retransmit = -1; // transmit forever
4882 p_station->last_transmit_time = sec_now(); // Used for object/item decaying algorithm
4883 p_station->transmit_time_increment = 0; // Used in data_add()
4884 // p_station->last_modified_time = 0; // Used for object/item dead-reckoning
4885 p_station->record_type = '\0';
4886 p_station->heard_via_tnc_port = 0;
4887 p_station->heard_via_tnc_last_time = 0;
4888 p_station->last_port_heard = 0;
4889 p_station->num_packets = 0;
4890 p_station->aprs_symbol.aprs_type = '\0';
4891 p_station->aprs_symbol.aprs_symbol = '\0';
4892 p_station->aprs_symbol.special_overlay = '\0';
4893 p_station->aprs_symbol.area_object.type = AREA_NONE;
4894 p_station->aprs_symbol.area_object.color = AREA_GRAY_LO;
4895 p_station->aprs_symbol.area_object.sqrt_lat_off = 0;
4896 p_station->aprs_symbol.area_object.sqrt_lon_off = 0;
4897 p_station->aprs_symbol.area_object.corridor_width = 0;
4898 // p_station->station_time_type = '\0';
4899 p_station->origin[0] = '\0'; // no object
4900 p_station->packet_time[0] = '\0';
4901 p_station->node_path_ptr = NULL;
4902 p_station->pos_time[0] = '\0';
4903 // p_station->altitude_time[0] = '\0';
4904 p_station->altitude[0] = '\0';
4905 // p_station->speed_time[0] = '\0';
4906 p_station->speed[0] = '\0';
4907 p_station->course[0] = '\0';
4908 p_station->bearing[0] = '\0';
4909 p_station->NRQ[0] = '\0';
4910 p_station->power_gain[0] = '\0';
4911 p_station->signal_gain[0] = '\0';
4912 p_station->signpost[0] = '\0';
4913 p_station->probability_min[0] = '\0';
4914 p_station->probability_max[0] = '\0';
4915 // p_station->station_time[0] = '\0';
4916 p_station->sats_visible[0] = '\0';
4917 p_station->status_data = NULL;
4918 p_station->comment_data = NULL;
4919 p_station->df_color = -1;
4921 // Show that there are no other points associated with this
4922 // station. We could also zero all the entries of the
4923 // multipoints[][] array, but nobody should be looking there
4924 // unless this is non-zero.
4927 p_station->num_multipoints = 0;
4928 p_station->multipoint_data = NULL;
4933 void init_tactical_hash(int clobber) {
4935 // make sure we don't leak
4936 if (tactical_hash) {
4938 hashtable_destroy(tactical_hash, 1);
4939 tactical_hash=create_hashtable(TACTICAL_HASH_SIZE,
4940 tactical_hash_from_key,
4941 tactical_keys_equal);
4945 tactical_hash=create_hashtable(TACTICAL_HASH_SIZE,
4946 tactical_hash_from_key,
4947 tactical_keys_equal);
4954 char *get_tactical_from_hash(char *callsign) {
4957 if (callsign == NULL || *callsign == '\0') {
4958 fprintf(stderr,"Empty callsign passed to get_tactical_from_hash()\n");
4962 if (!tactical_hash) { // no table to search
4963 //fprintf(stderr,"Creating hash table\n");
4964 init_tactical_hash(1); // so create one
4968 // fprintf(stderr," searching for %s...",callsign);
4970 result=hashtable_search(tactical_hash,callsign);
4973 // fprintf(stderr,"\t\tFound it, %s, len=%d, %s\n",
4975 // strlen(callsign),
4978 // fprintf(stderr,"\t\tNot found, %s, len=%d\n",
4980 // strlen(callsign));
4987 // Distance calculation (Great Circle) using the Haversine formula
4988 // (2-parameter arctan version), which gives better accuracy than
4989 // the "Law of Cosines" for short distances. It should be
4990 // equivalent to the "Law of Cosines for Spherical Trigonometry" for
4991 // longer distances. Haversine is a great-circle calculation.
4994 // Inputs: lat1/long1/lat2/long2 in radians (double)
4996 // Outputs: Distance in meters between them (double)
4998 double calc_distance_haversine_radian(double lat1, double lon1, double lat2, double lon2) {
5001 double R = EARTH_RADIUS_METERS;
5002 #define square(x) (x)*(x)
5007 a = square((sin(dlat/2.0))) + cos(lat1) * cos(lat2) * square((sin(dlon/2.0)));
5008 c = 2.0 * atan2(sqrt(a), sqrt(1.0-a));
5020 * Insert existing element into name ordered list before p_name.
5021 * If p_name is NULL then we add it to the end instead.
5023 void insert_name(AprsDataRow *p_new, AprsDataRow *p_name) {
5025 // Set up pointer to next record (or NULL), sorted by name
5026 p_new->n_next = p_name;
5028 if (p_name == NULL) { // Add to end of list
5030 p_new->n_prev = n_last;
5032 if (n_last == NULL) // If we have an empty list
5033 n_first = p_new; // Add it to the head of the list
5035 else // List wasn't empty, add to the end of the list.
5036 n_last->n_next = p_new;
5041 else { // Insert new record ahead of p_name record
5043 p_new->n_prev = p_name->n_prev;
5045 if (p_name->n_prev == NULL) // add to begin of list
5048 p_name->n_prev->n_next = p_new;
5050 p_name->n_prev = p_new;
5056 // Update all of the pointers so that they accurately reflect the
5057 // current state of the station database.
5059 // NOTE: This part of the code could be made smarter so that the
5060 // pointers are updated whenever they are found to be out of whack,
5061 // instead of zeroing all of them and starting from scratch each
5062 // time. Alternate: Follow the current pointer if non-NULL then go
5063 // up/down the list to find the current switchover point between
5066 // Better: Tie into the station insert function. If a new letter
5067 // is inserted, or a new station at the beginning of a letter group,
5068 // run this function to keep things up to date. That way we won't
5069 // have to traverse in both directions to find a callsign in the
5070 // search_station_name() function.
5072 // If hash_key_in is -1, we need to redo all of the hash keys. If
5073 // it is between 0 and 16383, then we need to redo just that one
5074 // hash key. The 2nd parameter is either NULL for a removed record,
5075 // or a pointer to a new station record in the case of an addition.
5077 void station_shortcuts_update_function(int hash_key_in, AprsDataRow *p_rem) {
5080 int prev_hash_key = 0x0000;
5084 // I just changed the function so that we can pass in the hash_key
5085 // that we wish to update: We should be able to speed things up by
5086 // updating one hash key instead of all 16384 pointers.
5088 if ( (hash_key_in != -1)
5089 && (hash_key_in >= 0)
5090 && (hash_key_in < 16384) ) {
5092 // We're adding/changing a hash key entry
5093 station_shortcuts[hash_key_in] = p_rem;
5094 //fprintf(stderr,"%i ",hash_key_in);
5096 else { // We're removing a hash key entry.
5098 // Clear and rebuild the entire hash table.
5100 //??????????????????????????????????????????????????
5101 // Clear all of the pointers before we begin????
5102 //??????????????????????????????????????????????????
5103 for (ii = 0; ii < 16384; ii++) {
5104 station_shortcuts[ii] = NULL;
5107 ptr = n_first; // Start of list
5110 // Loop through entire list, writing the pointer into the
5111 // station_shortcuts array whenever a new character is
5112 // encountered. Do this until the end of the array or the end
5115 while ( (ptr != NULL) && (prev_hash_key < 16384) ) {
5117 // We create the hash key out of the lower 7 bits of the
5118 // first two characters, creating a 14-bit key (1 of 16384)
5120 hash_key = (int)((ptr->call_sign[0] & 0x7f) << 7);
5121 hash_key = hash_key | (int)(ptr->call_sign[1] & 0x7f);
5123 if (hash_key > prev_hash_key) {
5125 // We found the next hash_key. Store the pointer at the
5126 // correct location.
5127 if (hash_key < 16384) {
5128 station_shortcuts[hash_key] = ptr;
5131 prev_hash_key = hash_key;
5144 * Create new initialized element for call in station list
5145 * and insert it before p_name after p_time entries.
5147 * Returns NULL if mallc error.
5149 /*@null@*/ AprsDataRow *add_new_station(AprsDataRow *p_name, AprsDataRow *p_time, char *call) {
5151 int hash_key; // We use a 14-bit hash key
5152 char *tactical_call;
5156 if (call[0] == '\0') {
5157 // Do nothing. No update needed. Callsign is empty.
5162 if(_aprs_show_new_station_alert)
5164 const gchar *msg = g_strdup_printf("New station: %s", call);
5165 hildon_banner_show_information(_window, NULL, msg);
5168 p_new = insert_new_station(p_name,p_time); // allocate memory
5170 if (p_new == NULL) {
5172 // Couldn't allocate space for the station
5176 init_station(p_new); // initialize new station record
5179 xastir_snprintf(p_new->call_sign,
5180 sizeof(p_new->call_sign),
5187 // Do some quick checks to see if we just inserted a new hash
5188 // key or inserted at the beginning of a hash key (making the
5189 // old pointer incorrect). If so, update our pointers to match.
5191 // We create the hash key out of the lower 7 bits of the first
5192 // two characters, creating a 14-bit key (1 of 16384)
5194 hash_key = (int)((call[0] & 0x7f) << 7);
5195 hash_key = hash_key | (int)(call[1] & 0x7f);
5197 if (station_shortcuts[hash_key] == NULL) {
5198 // New hash key entry point found. Fill in the pointer.
5199 station_shortcuts_update_function(hash_key, p_new);
5201 else if (p_new->n_prev == NULL) {
5202 // We just inserted at the beginning of the list. Assume
5203 // that we inserted at the beginning of our hash_key
5205 station_shortcuts_update_function(hash_key, p_new);
5208 // Check whether either of the first two chars of the new
5209 // callsign and the previous callsign are different. If so,
5210 // we need to update the hash table entry for our new record
5211 // 'cuz we're at the start of a new hash table entry.
5212 if (p_new->n_prev->call_sign[0] != call[0]
5213 || p_new->n_prev->call_sign[1] != call[1]) {
5215 station_shortcuts_update_function(hash_key, p_new);
5219 // Check whether we have a tactical call to assign to this
5220 // station in our tactical hash table.
5222 tactical_call = get_tactical_from_hash(call);
5225 // If tactical call found and not blank
5226 if (tactical_call && tactical_call[0] != '\0') {
5228 // Malloc some memory to hold it in the station record.
5229 p_new->tactical_call_sign = (char *)malloc(MAX_TACTICAL_CALL+1);
5230 CHECKMALLOC(p_new->tactical_call_sign);
5232 snprintf(p_new->tactical_call_sign,
5233 MAX_TACTICAL_CALL+1,
5237 //if (tactical_call[0] == '\0')
5238 // fprintf(stderr,"Blank tactical call\n");
5241 return(p_new); // return pointer to new element
5247 * Add data from APRS information field to station database
5248 * Returns a 1 if successful
5250 int data_add(gint type,
5257 gint station_is_mine,
5258 gint object_is_mine) {
5261 AprsDataRow *p_station;
5262 AprsDataRow *p_time;
5263 char call[MAX_CALLSIGN+1];
5265 long last_lat, last_lon;
5266 char last_alt[MAX_ALTITUDE];
5267 char last_speed[MAX_SPEED+1];
5268 char last_course[MAX_COURSE+1];
5269 time_t last_stn_sec;
5271 char temp_data[40]; // short term string storage, MAX_CALLSIGN, ...
5272 // long l_lat, l_lon;
5274 // char station_id[600];
5277 AprsWeatherRow *weather;
5284 char *p = NULL; // KC2ELS - used for WIDEn-N
5286 int object_is_mine_previous = 0;
5287 int new_origin_is_mine = 0;
5288 int num_digits = 0; // Number of digits after decimal point in NMEA string
5290 // TODO update based on time
5291 // static time_t lastScreenUpdate;
5294 // call and path had been validated before
5295 // Check "data" against the max APRS length, and dump the packet if too long.
5296 if ( (data != NULL) && (strlen(data) > MAX_INFO_FIELD_SIZE) ) {
5297 // Overly long packet. Throw it away.
5299 return(0); // Not an ok packet
5302 // Check for some reasonable string in call_sign parameter
5303 if (call_sign == NULL || strlen(call_sign) == 0) {
5309 if (origin && is_my_call(origin, 1)) {
5310 new_origin_is_mine++; // The new object/item is owned by me
5313 weather = NULL; // only to make the compiler happy...
5320 new_station = (char)FALSE; // to make the compiler happy...
5323 last_stn_sec = sec_now();
5325 last_speed[0] = '\0';
5326 last_course[0] = '\0';
5330 p_time = NULL; // add to end of time sorted list (newest)
5334 if (search_station_name(&p_station,call,1))
5335 { // If we found the station in our list
5338 // fprintf(stderr, "DEBUG: Station:,Found,:%s:\n", call);
5340 // Check whether it's already a locally-owned object/item
5341 if (is_my_object_item(p_station)) {
5343 // We don't want to re-order it in the time-ordered list
5344 // so that it'll expire from the queue normally. Don't
5345 // call "move_station_time()" here.
5347 // We need an exception later in this function for the
5348 // case where we've moved an object/item (by how much?).
5349 // We need to update the time in this case so that it'll
5350 // expire later (in fact it could already be expired
5351 // when we move it). We should be able to move expired
5352 // objects/items to make them active again. Perhaps
5353 // some other method as well?
5355 new_station = (char)FALSE;
5356 object_is_mine_previous++;
5359 move_station_time(p_station,p_time); // update time, change position in time sorted list
5360 new_station = (char)FALSE; // we have seen this one before
5363 if (is_my_station(p_station)) {
5364 station_is_mine++; // Station/object/item is owned/controlled by me
5369 // fprintf(stderr, "DEBUG: Station:,New,:%s:\n", call);
5371 p_station = add_new_station(p_station,p_time,call); // create storage
5372 new_station = (char)TRUE; // for new station
5377 if (p_station != NULL)
5380 last_lat = p_station->coord_lat; // remember last position
5381 last_lon = p_station->coord_lon;
5382 last_stn_sec = p_station->sec_heard;
5386 p_station->altitude);
5387 snprintf(last_speed,
5391 snprintf(last_course,
5392 sizeof(last_course),
5395 last_flag = p_station->flag;
5397 // Wipe out old data so that it doesn't hang around forever
5398 p_station->altitude[0] = '\0';
5399 p_station->speed[0] = '\0';
5400 p_station->course[0] = '\0';
5402 ok = 1; // succeed as default
5407 case (APRS_MICE): // Mic-E format
5408 case (APRS_FIXED): // '!'
5409 case (APRS_MSGCAP): // '='
5411 if (!extract_position(p_station,&data,type)) { // uncompressed lat/lon
5413 if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
5416 p_station->pos_amb = 0; // No ambiguity in compressed posits
5421 // Create a timestamp from the current time
5422 snprintf(p_station->pos_time,
5423 sizeof(p_station->pos_time),
5424 "%s", get_time(temp_data));
5425 (void)extract_storm(p_station,data,compr_pos);
5426 (void)extract_weather(p_station,data,compr_pos); // look for weather data first
5427 process_data_extension(p_station,data,type); // PHG, speed, etc.
5428 process_info_field(p_station,data,type); // altitude
5430 if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
5431 extract_multipoints(p_station, data, type, 1);
5433 add_comment(p_station,data);
5435 p_station->record_type = NORMAL_APRS;
5436 if (type == APRS_MSGCAP)
5437 p_station->flag |= ST_MSGCAP; // set "message capable" flag
5439 p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
5441 // Assign a non-default value for the error
5443 if (type == APRS_MICE || !compr_pos) {
5444 p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
5445 p_station->lat_precision = 60;
5446 p_station->lon_precision = 60;
5449 p_station->error_ellipse_radius = 600; // Default of 6m
5450 p_station->lat_precision = 6;
5451 p_station->lon_precision = 6;
5458 case (APRS_DOWN): // '/'
5459 ok = extract_time(p_station, data, type); // we need a time
5461 if (!extract_position(p_station,&data,type)) { // uncompressed lat/lon
5463 if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
5466 p_station->pos_amb = 0; // No ambiguity in compressed posits
5472 // Create a timestamp from the current time
5473 xastir_snprintf(p_station->pos_time,
5474 sizeof(p_station->pos_time),
5476 get_time(temp_data));
5477 process_data_extension(p_station,data,type); // PHG, speed, etc.
5478 process_info_field(p_station,data,type); // altitude
5480 if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
5481 extract_multipoints(p_station, data, type, 1);
5483 add_comment(p_station,data);
5485 p_station->record_type = DOWN_APRS;
5486 p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
5488 // Assign a non-default value for the error
5491 p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
5492 p_station->lat_precision = 60;
5493 p_station->lon_precision = 60;
5496 p_station->error_ellipse_radius = 600; // Default of 6m
5497 p_station->lat_precision = 6;
5498 p_station->lon_precision = 6;
5504 case (APRS_DF): // '@'
5505 case (APRS_MOBILE): // '@'
5507 ok = extract_time(p_station, data, type); // we need a time
5509 if (!extract_position(p_station,&data,type)) { // uncompressed lat/lon
5511 if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
5514 p_station->pos_amb = 0; // No ambiguity in compressed posits
5519 process_data_extension(p_station,data,type); // PHG, speed, etc.
5520 process_info_field(p_station,data,type); // altitude
5522 if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
5523 extract_multipoints(p_station, data, type, 1);
5525 add_comment(p_station,data);
5527 if(type == APRS_MOBILE)
5528 p_station->record_type = MOBILE_APRS;
5530 p_station->record_type = DF_APRS;
5531 //@ stations have messaging per spec
5532 p_station->flag |= (ST_MSGCAP); // set "message capable" flag
5534 // Assign a non-default value for the error
5537 p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
5538 p_station->lat_precision = 60;
5539 p_station->lon_precision = 60;
5542 p_station->error_ellipse_radius = 600; // Default of 6m
5543 p_station->lat_precision = 6;
5544 p_station->lon_precision = 6;
5551 ok = extract_position(p_station, &data, type);
5555 process_info_field(p_station,data,type); // altitude
5557 if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
5558 extract_multipoints(p_station, data, type, 1);
5560 add_comment(p_station,data);
5562 // Assign a non-default value for the error
5564 // p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
5567 // This needs to change based on the number of grid letters/digits specified
5568 // p_station->lat_precision = 60;
5569 // p_station->lon_precision = 60;
5575 case (STATION_CALL_DATA):
5577 p_station->record_type = NORMAL_APRS;
5581 case (APRS_STATUS): // '>' Status Reports [APRS Reference, chapter 16]
5583 (void)extract_time(p_station, data, type); // we need a time
5584 // todo: could contain Maidenhead or beam heading+power
5586 if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
5587 extract_multipoints(p_station, data, type, 1);
5589 add_status(p_station,data);
5591 p_station->flag |= (ST_STATUS); // set "Status" flag
5592 p_station->record_type = NORMAL_APRS; // ???
5596 case (OTHER_DATA): // Other Packets [APRS Reference, chapter 19]
5598 // non-APRS beacons, treated as status reports until we get a real one
5600 if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
5601 extract_multipoints(p_station, data, type, 1);
5603 if ((p_station->flag & (~ST_STATUS)) == 0) { // only store if no status yet
5605 add_status(p_station,data);
5607 p_station->record_type = NORMAL_APRS; // ???
5614 // If old match is a killed Object (owner doesn't
5615 // matter), new one is an active Object and owned by
5616 // us, remove the old record and create a new one
5617 // for storing this Object. Do the same for Items
5618 // in the next section below.
5620 // The easiest implementation might be to remove the
5621 // old record and then call this routine again with
5622 // the same parameters, which will cause a brand-new
5623 // record to be created.
5625 // The new record we're processing is an active
5626 // object, as data_add() won't be called on a killed
5629 // if ( is_my_call(origin,1) // If new Object is owned by me (including SSID)
5630 if (new_origin_is_mine
5631 && !(p_station->flag & ST_ACTIVE)
5632 && (p_station->flag & ST_OBJECT) ) { // Old record was a killed Object
5633 remove_name(p_station); // Remove old killed Object
5634 // redo_list = (int)TRUE;
5635 return( data_add(type, call_sign, path, data, port, origin, third_party, 1, 1) );
5638 ok = extract_time(p_station, data, type); // we need a time
5640 if (!extract_position(p_station,&data,type)) { // uncompressed lat/lon
5642 if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
5645 p_station->pos_amb = 0; // No ambiguity in compressed posits
5648 p_station->flag |= ST_OBJECT; // Set "Object" flag
5651 // If object was owned by me but another station
5652 // is transmitting it now, write entries into
5653 // the object.log file showing that we don't own
5654 // this object anymore.
5656 // if ( (is_my_call(p_station->origin,1)) // If station was owned by me (including SSID)
5657 // && (!is_my_call(origin,1)) ) { // But isn't now
5660 if (is_my_object_item(p_station) // If station was owned by me (include SSID)
5661 && !new_origin_is_mine) { // But isn't now
5663 disown_object_item(call_sign, origin);
5667 // If station is owned by me (including SSID)
5668 // but it's a new object/item
5669 // if ( (is_my_call(p_station->origin,1))
5670 if (new_origin_is_mine
5671 && (p_station->transmit_time_increment == 0) ) {
5672 // This will get us transmitting this object
5673 // on the decaying algorithm schedule.
5674 // We've transmitted it once if we've just
5675 // gotten to this code.
5676 p_station->transmit_time_increment = OBJECT_CHECK_RATE;
5677 //fprintf(stderr,"data_add(): Setting transmit_time_increment to %d\n", OBJECT_CHECK_RATE);
5680 // Create a timestamp from the current time
5681 xastir_snprintf(p_station->pos_time,
5682 sizeof(p_station->pos_time),
5684 get_time(temp_data));
5686 xastir_snprintf(p_station->origin,
5687 sizeof(p_station->origin),
5689 origin); // define it as object
5690 (void)extract_storm(p_station,data,compr_pos);
5691 (void)extract_weather(p_station,data,compr_pos); // look for wx info
5692 process_data_extension(p_station,data,type); // PHG, speed, etc.
5693 process_info_field(p_station,data,type); // altitude
5695 if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
5696 extract_multipoints(p_station, data, type, 0);
5698 add_comment(p_station,data);
5700 // the last char always was missing...
5701 //p_station->comments[ strlen(p_station->comments) - 1 ] = '\0'; // Wipe out '\n'
5702 // moved that to decode_ax25_line
5703 // and don't added a '\n' in interface.c
5704 p_station->record_type = NORMAL_APRS;
5705 p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
5707 // Assign a non-default value for the error
5710 p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
5711 p_station->lat_precision = 60;
5712 p_station->lon_precision = 60;
5715 p_station->error_ellipse_radius = 600; // Default of 6m
5716 p_station->lat_precision = 6;
5717 p_station->lon_precision = 6;
5724 // If old match is a killed Item (owner doesn't
5725 // matter), new one is an active Item and owned by
5726 // us, remove the old record and create a new one
5727 // for storing this Item. Do the same for Objects
5728 // in the previous section above.
5730 // The easiest implementation might be to remove the
5731 // old record and then call this routine again with
5732 // the same parameters, which will cause a brand-new
5733 // record to be created.
5735 // The new record we're processing is an active
5736 // Item, as data_add() won't be called on a killed
5739 // if ( is_my_call(origin,1) // If new Item is owned by me (including SSID)
5740 if (new_origin_is_mine
5741 && !(p_station->flag & ST_ACTIVE)
5742 && (p_station->flag & ST_ITEM) ) { // Old record was a killed Item
5744 remove_name(p_station); // Remove old killed Item
5745 // redo_list = (int)TRUE;
5746 return( data_add(type, call_sign, path, data, port, origin, third_party, 1, 1) );
5749 if (!extract_position(p_station,&data,type)) { // uncompressed lat/lon
5751 if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
5754 p_station->pos_amb = 0; // No ambiguity in compressed posits
5756 p_station->flag |= ST_ITEM; // Set "Item" flag
5759 // If item was owned by me but another station
5760 // is transmitting it now, write entries into
5761 // the object.log file showing that we don't own
5762 // this item anymore.
5765 // if ( (is_my_call(p_station->origin,1)) // If station was owned by me (including SSID)
5766 // && (!is_my_call(origin,1)) ) { // But isn't now
5767 if (is_my_object_item(p_station)
5768 && !new_origin_is_mine) { // But isn't now
5770 disown_object_item(call_sign,origin);
5774 // If station is owned by me (including SSID)
5775 // but it's a new object/item
5776 // if ( (is_my_call(p_station->origin,1))
5777 if (is_my_object_item(p_station)
5778 && (p_station->transmit_time_increment == 0) ) {
5779 // This will get us transmitting this object
5780 // on the decaying algorithm schedule.
5781 // We've transmitted it once if we've just
5782 // gotten to this code.
5783 p_station->transmit_time_increment = OBJECT_CHECK_RATE;
5784 //fprintf(stderr,"data_add(): Setting transmit_time_increment to %d\n", OBJECT_CHECK_RATE);
5787 // Create a timestamp from the current time
5788 xastir_snprintf(p_station->pos_time,
5789 sizeof(p_station->pos_time),
5791 get_time(temp_data));
5792 xastir_snprintf(p_station->origin,
5793 sizeof(p_station->origin),
5795 origin); // define it as item
5796 (void)extract_storm(p_station,data,compr_pos);
5797 (void)extract_weather(p_station,data,compr_pos); // look for wx info
5798 process_data_extension(p_station,data,type); // PHG, speed, etc.
5799 process_info_field(p_station,data,type); // altitude
5801 if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
5802 extract_multipoints(p_station, data, type, 0);
5804 add_comment(p_station,data);
5806 // the last char always was missing...
5807 //p_station->comments[ strlen(p_station->comments) - 1 ] = '\0'; // Wipe out '\n'
5808 // moved that to decode_ax25_line
5809 // and don't added a '\n' in interface.c
5810 p_station->record_type = NORMAL_APRS;
5811 p_station->flag &= (~ST_MSGCAP); // clear "message capable" flag
5813 // Assign a non-default value for the error
5816 p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
5817 p_station->lat_precision = 60;
5818 p_station->lon_precision = 60;
5821 p_station->error_ellipse_radius = 600; // Default of 6m
5822 p_station->lat_precision = 6;
5823 p_station->lon_precision = 6;
5828 case (APRS_WX1): // weather in '@' or '/' packet
5830 ok = extract_time(p_station, data, type); // we need a time
5832 if (!extract_position(p_station,&data,type)) { // uncompressed lat/lon
5834 if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
5837 p_station->pos_amb = 0; // No ambiguity in compressed posits
5842 (void)extract_storm(p_station,data,compr_pos);
5843 (void)extract_weather(p_station,data,compr_pos);
5844 p_station->record_type = (char)APRS_WX1;
5846 process_info_field(p_station,data,type); // altitude
5848 if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
5849 extract_multipoints(p_station, data, type, 1);
5851 add_comment(p_station,data);
5853 // Assign a non-default value for the error
5856 p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
5857 p_station->lat_precision = 60;
5858 p_station->lon_precision = 60;
5861 p_station->error_ellipse_radius = 600; // Default of 6m
5862 p_station->lat_precision = 6;
5863 p_station->lon_precision = 6;
5868 case (APRS_WX2): // '_'
5870 ok = extract_time(p_station, data, type); // we need a time
5872 (void)extract_storm(p_station,data,compr_pos);
5873 (void)extract_weather(p_station,data,0); // look for weather data first
5874 p_station->record_type = (char)APRS_WX2;
5877 process_info_field(p_station,data,type); // altitude
5879 if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
5880 extract_multipoints(p_station, data, type, 1);
5884 case (APRS_WX4): // '#' Peet Bros U-II (km/h)
5885 case (APRS_WX6): // '*' Peet Bros U-II (mph)
5886 case (APRS_WX3): // '!' Peet Bros Ultimeter 2000 (data logging mode)
5887 case (APRS_WX5): // '$ULTW' Peet Bros Ultimeter 2000 (packet mode)
5890 if (get_weather_record(p_station)) { // get existing or create new weather record
5891 weather = p_station->weather_data;
5892 if (type == APRS_WX3) // Peet Bros Ultimeter 2000 data logging mode
5894 decode_U2000_L (1,(unsigned char *)data,weather);
5896 else if (type == APRS_WX5) // Peet Bros Ultimeter 2000 packet mode
5897 decode_U2000_P(1,(unsigned char *)data,weather);
5898 else // Peet Bros Ultimeter-II
5899 decode_Peet_Bros(1,(unsigned char *)data,weather,type);
5900 p_station->record_type = (char)type;
5901 // Create a timestamp from the current time
5902 xastir_snprintf(weather->wx_time,
5903 sizeof(weather->wx_time),
5905 get_time(temp_data));
5906 weather->wx_sec_time = sec_now();
5913 // GPRMC, digits after decimal point
5914 // ---------------------------------
5915 // 2 = 25.5 meter error ellipse
5916 // 3 = 6.0 meter error ellipse
5917 // 4+ = 6.0 meter error ellipse
5920 case (GPS_RMC): // $GPRMC
5923 // Change this function to return HDOP and the number of characters
5924 // after the decimal point.
5925 ok = extract_RMC(p_station,data,call_sign,path,&num_digits);
5928 // Assign a non-default value for the error
5932 // Degrade based on the precision provided in the sentence. If only
5933 // 2 digits after decimal point, give it 2550 as a radius (25.5m).
5934 // Best (smallest) circle should be 600 as we have no augmentation
5935 // flag to check here for anything better.
5937 switch (num_digits) {
5940 p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
5941 p_station->lat_precision = 6000;
5942 p_station->lon_precision = 6000;
5946 p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
5947 p_station->lat_precision = 600;
5948 p_station->lon_precision = 600;
5952 p_station->error_ellipse_radius = 600; // Default of 6m
5953 p_station->lat_precision = 60;
5954 p_station->lon_precision = 60;
5958 p_station->error_ellipse_radius = 600; // Default of 6m
5959 p_station->lat_precision = 6;
5960 p_station->lon_precision = 6;
5967 p_station->error_ellipse_radius = 600; // Default of 6m
5968 p_station->lat_precision = 0;
5969 p_station->lon_precision = 0;
5973 p_station->error_ellipse_radius = 600; // Default of 6m
5974 p_station->lat_precision = 60;
5975 p_station->lon_precision = 60;
5982 // GPGGA, digits after decimal point, w/o augmentation
5983 // ---------------------------------------------------
5984 // 2 = 25.5 meter error ellipse
5985 // 3 = 6.0 meter error ellipse unless HDOP>4, then 10.0 meters
5986 // 4+ = 6.0 meter error ellipse unless HDOP>4, then 10.0 meters
5989 // GPGGA, digits after decimal point, w/augmentation
5990 // --------------------------------------------------
5991 // 2 = 25.5 meter error ellipse
5992 // 3 = 2.5 meter error ellipse unless HDOP>4, then 10.0 meters
5993 // 4+ = 0.6 meter error ellipse unless HDOP>4, then 10.0 meters
5996 case (GPS_GGA): // $GPGGA
5999 // Change this function to return HDOP and the number of characters
6000 // after the decimal point.
6001 ok = extract_GGA(p_station,data,call_sign,path,&num_digits);
6004 // Assign a non-default value for the error
6008 // Degrade based on the precision provided in the sentence. If only
6009 // 2 digits after decimal point, give it 2550 as a radius (25.5m).
6010 // 3 digits: 6m w/o augmentation unless HDOP >4 = 10m, 2.5m w/augmentation.
6011 // 4+ digits: 6m w/o augmentation unless HDOP >4 = 10m, 0.6m w/augmentation.
6013 switch (num_digits) {
6016 p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
6017 p_station->lat_precision = 6000;
6018 p_station->lon_precision = 6000;
6022 p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
6023 p_station->lat_precision = 600;
6024 p_station->lon_precision = 600;
6028 p_station->error_ellipse_radius = 600; // Default of 6m
6029 p_station->lat_precision = 60;
6030 p_station->lon_precision = 60;
6034 p_station->error_ellipse_radius = 600; // Default of 6m
6035 p_station->lat_precision = 6;
6036 p_station->lon_precision = 6;
6043 p_station->error_ellipse_radius = 600; // Default of 6m
6044 p_station->lat_precision = 0;
6045 p_station->lon_precision = 0;
6049 p_station->error_ellipse_radius = 600; // Default of 6m
6050 p_station->lat_precision = 60;
6051 p_station->lon_precision = 60;
6058 // GPGLL, digits after decimal point
6059 // ---------------------------------
6060 // 2 = 25.5 meter error ellipse
6061 // 3 = 6.0 meter error ellipse
6062 // 4+ = 6.0 meter error ellipse
6065 case (GPS_GLL): // $GPGLL
6066 ok = extract_GLL(p_station,data,call_sign,path,&num_digits);
6069 // Assign a non-default value for the error
6073 // Degrade based on the precision provided in the sentence. If only
6074 // 2 digits after decimal point, give it 2550 as a radius, otherwise
6077 switch (num_digits) {
6080 p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
6081 p_station->lat_precision = 6000;
6082 p_station->lon_precision = 6000;
6086 p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
6087 p_station->lat_precision = 600;
6088 p_station->lon_precision = 600;
6092 p_station->error_ellipse_radius = 600; // Default of 6m
6093 p_station->lat_precision = 60;
6094 p_station->lon_precision = 60;
6098 p_station->error_ellipse_radius = 600; // Default of 6m
6099 p_station->lat_precision = 6;
6100 p_station->lon_precision = 6;
6107 p_station->error_ellipse_radius = 600; // Default of 6m
6108 p_station->lat_precision = 0;
6109 p_station->lon_precision = 0;
6113 p_station->error_ellipse_radius = 600; // Default of 6m
6114 p_station->lat_precision = 60;
6115 p_station->lon_precision = 60;
6124 fprintf(stderr,"ERROR: UNKNOWN TYPE in data_add\n");
6129 // Left this one in, just in case. Perhaps somebody might
6130 // attach a multipoint string onto the end of a packet we
6131 // might not expect. For this case we need to check whether
6132 // we have multipoints first, as we don't want to erase the
6133 // work we might have done with a previous call to
6134 // extract_multipoints().
6135 if (ok && (p_station->coord_lat > 0)
6136 && (p_station->coord_lon > 0)
6137 && (p_station->num_multipoints == 0) ) { // No multipoints found yet
6139 extract_multipoints(p_station, data, type, 0);
6143 if (!ok) { // non-APRS beacon, treat it as Other Packet [APRS Reference, chapter 19]
6145 // GPRMC etc. without a position is here too, but it should not be stored as status!
6147 // store it as status report until we get a real one
6148 if ((p_station->flag & (~ST_STATUS)) == 0) { // only store it if no status yet
6150 add_status(p_station,data-1);
6152 p_station->record_type = NORMAL_APRS; // ???
6160 curr_sec = sec_now();
6163 // data packet is valid
6164 // announce own echo, we soon discard that packet...
6165 // if (!new_station && is_my_call(p_station->call_sign,1) // Check SSID as well
6167 && is_my_station(p_station) // Check SSID as well
6168 && strchr(path,'*') != NULL) {
6170 upd_echo(path); // store digi that echoes my signal...
6171 statusline("Echo from digipeater",0); // Echo from digipeater
6174 // check if data is just a secondary echo from another digi
6175 if ((last_flag & ST_VIATNC) == 0
6176 || (curr_sec - last_stn_sec) > 15
6177 || p_station->coord_lon != last_lon
6178 || p_station->coord_lat != last_lat)
6180 store = 1; // don't store secondary echos
6183 if (!ok && new_station)
6184 delete_station_memory(p_station); // remove unused record
6188 // we now have valid data to store into database
6189 // make time index unique by adding a serial number
6191 if (station_is_mine) {
6192 // This station/object/item is owned by me. Set the
6193 // flag which shows that we own/control this
6194 // station/object/item. We use this flag later in lieu
6195 // of the is_my_call() function in order to speed things
6198 p_station->flag |= ST_MYSTATION;
6201 // Check whether it's a locally-owned object/item
6203 || ( new_origin_is_mine
6204 && (p_station->flag & ST_ACTIVE)
6205 && (p_station->flag & ST_OBJECT) ) ) {
6207 p_station->flag |= ST_MYOBJITEM;
6209 // Do nothing else. We don't want to update the
6210 // last-heard time so that it'll expire from the queue
6211 // normally, unless it is a new object/item.
6214 p_station->sec_heard = curr_sec;
6217 // We need an exception later in this function for the
6218 // case where we've moved an object/item (by how much?).
6219 // We need to update the time in this case so that it'll
6220 // expire later (in fact it could already be expired
6221 // when we move it). We should be able to move expired
6222 // objects/items to make them active again. Perhaps
6223 // some other method as well?.
6226 // Reset the "my object" flag
6227 p_station->flag &= ~ST_MYOBJITEM;
6229 p_station->sec_heard = curr_sec; // Give it a new timestamp
6232 if (curr_sec != last_sec) { // todo: if old time calculate time_sn from database
6233 last_sec = curr_sec;
6234 next_time_sn = 0; // restart time serial number
6237 p_station->time_sn = next_time_sn++; // todo: warning if serial number too high
6238 if (port == APRS_PORT_TTY) { // heard via TNC
6239 if (!third_party) { // Not a third-party packet
6240 p_station->flag |= ST_VIATNC; // set "via TNC" flag
6241 p_station->heard_via_tnc_last_time = curr_sec;
6242 p_station->heard_via_tnc_port = port;
6244 else { // Third-party packet
6245 // Leave the previous setting of "flag" alone.
6246 // Specifically do NOT set the ST_VIATNC flag if it
6247 // was a third-party packet.
6250 else { // heard other than TNC
6251 if (new_station) { // new station
6252 p_station->flag &= (~ST_VIATNC); // clear "via TNC" flag
6253 //fprintf(stderr,"New_station: Cleared ST_VIATNC flag: %s\n", p_station->call_sign);
6254 p_station->heard_via_tnc_last_time = 0l;
6257 p_station->last_port_heard = port;
6259 // Create a timestamp from the current time
6260 snprintf(p_station->packet_time, sizeof(p_station->packet_time),
6261 "%s", get_time(temp_data)); // get_time returns value in temp_data
6263 p_station->flag |= ST_ACTIVE;
6265 p_station->flag |= ST_3RD_PT; // set "third party" flag
6267 p_station->flag &= (~ST_3RD_PT); // clear "third party" flag
6268 if (origin != NULL && strcmp(origin,"INET") == 0) // special treatment for inet names
6269 snprintf(p_station->origin,
6270 sizeof(p_station->origin),
6272 origin); // to keep them separated from calls
6273 if (origin != NULL && strcmp(origin,"INET-NWS") == 0) // special treatment for NWS
6274 snprintf(p_station->origin,
6275 sizeof(p_station->origin),
6277 origin); // to keep them separated from calls
6278 if (origin == NULL || origin[0] == '\0') // normal call
6279 p_station->origin[0] = '\0'; // undefine possible former object with same name
6282 //--------------------------------------------------------------------
6285 // Okay, here are the standards for ST_DIRECT:
6286 // 1. The packet must have been received via TNC.
6287 // 2. The packet must not have any * flags.
6288 // 3. If present, the first WIDEn-N (or TRACEn-N) must have n=N.
6289 // A station retains the ST_DIRECT setting. If
6290 // "st_direct_timeout" seconds have passed since we set
6291 // that bit then APRSD queries and displays based on the
6292 // ST_DIRECT bit will skip that station.
6294 // In order to make this scheme work for stations that straddle both
6295 // RF and INET, we need to make sure that node_path_ptr doesn't get
6296 // overwritten with an INET path if there's an RF path already in
6297 // there and it has been less than st_direct_timeout seconds since
6298 // the station was last heard on RF.
6300 if ((port == APRS_PORT_TTY) // Heard via TNC
6301 && !third_party // Not a 3RD-Party packet
6302 && path != NULL // Path is not NULL
6303 && strchr(path,'*') == NULL) { // No asterisk found
6305 // Look for WIDE or TRACE
6306 if ((((p = strstr(path,"WIDE")) != NULL)
6308 (((p = strstr(path,"TRACE")) != NULL)
6311 // Look for n=N on WIDEn-N/TRACEn-N digi field
6312 if ((*p != '\0') && isdigit((int)*p)) {
6313 if ((*(p+1) != '\0') && (*(p+1) == '-')) {
6314 if ((*(p+2) != '\0') && g_ascii_isdigit(*(p+2))) {
6315 if (*(p) == *(p+2)) {
6344 // This packet was heard direct. Set the ST_DIRECT bit
6345 // and save the timestamp away.
6346 p_station->direct_heard = curr_sec;
6347 p_station->flag |= (ST_DIRECT);
6350 // This packet was NOT heard direct. Check whether we
6351 // need to expire the ST_DIRECT bit. A lot of fixed
6352 // stations transmit every 30 minutes. One hour gives
6353 // us time to receive a direct packet from them among
6354 // all the digipeated packets.
6356 if ((p_station->flag & ST_DIRECT) != 0 &&
6357 curr_sec > (p_station->direct_heard + st_direct_timeout)) {
6358 p_station->flag &= (~ST_DIRECT);
6362 // If heard on TNC, overwrite node_path_ptr if any of these
6363 // conditions are met:
6364 // *) direct == 1 (packet was heard direct)
6365 // *) ST_DIRECT flag == 0 (packet hasn't been heard
6367 // *) ST_DIRECT is set, st_direct_timeout has expired
6368 // (packet hasn't been heard direct recently)
6370 // These rules will allow us to keep directly heard paths
6371 // saved for at least an hour (st_direct_timeout), and not
6372 // get overwritten with digipeated paths during that time.
6374 if ((port == APRS_PORT_TTY) // Heard via TNC
6375 && !third_party // Not a 3RD-Party packet
6376 && path != NULL) { // Path is not NULL
6378 // Heard on TNC interface and not third party. Check
6379 // the other conditions listed in the comments above to
6380 // decide whether we should overwrite the node_path_ptr
6383 if ( direct // This packet was heard direct
6384 || (p_station->flag & ST_DIRECT) == 0 // Not heard direct lately
6385 || ( (p_station->flag & ST_DIRECT) != 0 // Not heard direct lately
6386 && (curr_sec > (p_station->direct_heard+st_direct_timeout) ) ) ) {
6388 // Free any old path we might have
6389 if (p_station->node_path_ptr != NULL)
6390 free(p_station->node_path_ptr);
6391 // Malloc and store the new path
6392 p_station->node_path_ptr = (char *)malloc(strlen(path) + 1);
6393 CHECKMALLOC(p_station->node_path_ptr);
6395 substr(p_station->node_path_ptr,path,strlen(path));
6398 // If a 3rd-party packet heard on TNC, overwrite
6399 // node_path_ptr only if heard_via_tnc_last_time is older
6400 // than one hour (zero counts as well!), plus clear the
6401 // ST_DIRECT and ST_VIATNC bits in this case. This makes us
6402 // keep the RF path around for at least one hour after the
6403 // station is heard.
6405 else if ((port == APRS_PORT_TTY) // Heard via TNC
6406 && third_party // It's a 3RD-Party packet
6407 && path != NULL) { // Path is not NULL
6409 // 3rd-party packet heard on TNC interface. Check if
6410 // heard_via_tnc_last_time is older than an hour. If
6411 // so, overwrite the path and clear a few bits to show
6412 // that it has timed out on RF and we're now receiving
6413 // that station from an igate.
6415 if (curr_sec > (p_station->heard_via_tnc_last_time + 60*60)) {
6417 // Yep, more than one hour old or is a zero,
6418 // overwrite the node_path_ptr variable with the new
6419 // one. We're only hearing this station on INET
6422 // Free any old path we might have
6423 if (p_station->node_path_ptr != NULL)
6424 free(p_station->node_path_ptr);
6425 // Malloc and store the new path
6426 p_station->node_path_ptr = (char *)malloc(strlen(path) + 1);
6427 CHECKMALLOC(p_station->node_path_ptr);
6429 substr(p_station->node_path_ptr,path,strlen(path));
6431 // Clear the ST_VIATNC bit
6432 p_station->flag &= ~ST_VIATNC;
6435 // If direct_heard is over an hour old, clear the
6436 // ST_DIRECT flag. We're only hearing this station on
6439 if (curr_sec > (p_station->direct_heard + st_direct_timeout)) {
6441 // Yep, more than one hour old or is a zero, clear
6442 // the ST_DIRECT flag.
6443 p_station->flag &= ~ST_DIRECT;
6447 // If heard on INET then overwrite node_path_ptr only if
6448 // heard_via_tnc_last_time is older than one hour (zero
6449 // counts as well!), plus clear the ST_DIRECT and ST_VIATNC
6450 // bits in this case. This makes us keep the RF path around
6451 // for at least one hour after the station is heard.
6453 else if (port != APRS_PORT_TTY // From an INET interface
6454 && !third_party // Not a 3RD-Party packet
6455 && path != NULL) { // Path is not NULL
6457 // Heard on INET interface. Check if
6458 // heard_via_tnc_last_time is older than an hour. If
6459 // so, overwrite the path and clear a few bits to show
6460 // that it has timed out on RF and we're now receiving
6461 // that station from the INET feeds.
6463 if (curr_sec > (p_station->heard_via_tnc_last_time + 60*60)) {
6465 // Yep, more than one hour old or is a zero,
6466 // overwrite the node_path_ptr variable with the new
6467 // one. We're only hearing this station on INET
6470 // Free any old path we might have
6471 if (p_station->node_path_ptr != NULL)
6472 free(p_station->node_path_ptr);
6473 // Malloc and store the new path
6474 p_station->node_path_ptr = (char *)malloc(strlen(path) + 1);
6475 CHECKMALLOC(p_station->node_path_ptr);
6477 substr(p_station->node_path_ptr,path,strlen(path));
6479 // Clear the ST_VIATNC bit
6480 p_station->flag &= ~ST_VIATNC;
6484 // If direct_heard is over an hour old, clear the
6485 // ST_DIRECT flag. We're only hearing this station on
6488 if (curr_sec > (p_station->direct_heard + st_direct_timeout)) {
6490 // Yep, more than one hour old or is a zero, clear
6491 // the ST_DIRECT flag.
6492 p_station->flag &= ~ST_DIRECT;
6497 //---------------------------------------------------------------------
6499 p_station->num_packets += 1;
6504 if (strlen(p_station->speed) > 0 && atof(p_station->speed) > 0) {
6505 p_station->flag |= (ST_MOVING); // it has a speed, so it's moving
6509 if (p_station->coord_lat != 0 && p_station->coord_lon != 0) { // discard undef positions from screen
6510 if (!altnet || is_altnet(p_station) ) {
6513 // TODO - check needed
6518 else { // we had seen this station before...
6520 if (found_pos && position_defined(p_station->coord_lat,p_station->coord_lon,1)) { // ignore undefined and 0N/0E
6521 if (p_station->newest_trackpoint != NULL) {
6522 moving = 1; // it's moving if it has a trail
6525 if (strlen(p_station->speed) > 0 && atof(p_station->speed) > 0) {
6526 moving = 1; // declare it moving, if it has a speed
6529 // Here's where we detect movement
6530 if (position_defined(last_lat,last_lon,1)
6531 && (p_station->coord_lat != last_lat || p_station->coord_lon != last_lon)) {
6532 moving = 1; // it's moving if it has changed the position
6541 p_station->flag |= (ST_MOVING);
6542 // we have a moving station, process trails
6543 if (atoi(p_station->speed) < TRAIL_MAX_SPEED) { // reject high speed data (undef gives 0)
6544 // we now may already have the 2nd position, so store the old one first
6545 if (p_station->newest_trackpoint == NULL) {
6546 if (position_defined(last_lat,last_lon,1)) { // ignore undefined and 0N/0E
6547 (void)store_trail_point(p_station,
6557 //if ( p_station->coord_lon != last_lon
6558 // || p_station->coord_lat != last_lat ) {
6559 // we don't store redundant points (may change this
6562 // There are often echoes delayed 15 minutes
6563 // or so it looks ugly on the trail, so I
6564 // want to discard them This also discards
6565 // immediate echoes. Duplicates back in time
6566 // up to TRAIL_ECHO_TIME minutes are
6569 if (!is_trailpoint_echo(p_station)) {
6570 (void)store_trail_point(p_station,
6571 p_station->coord_lon,
6572 p_station->coord_lat,
6573 p_station->sec_heard,
6574 p_station->altitude,
6580 // Check whether it's a locally-owned object/item
6581 if (object_is_mine) {
6583 // Update time, change position in
6584 // time-sorted list to change
6586 move_station_time(p_station,p_time);
6588 // Give it a new timestamp
6589 p_station->sec_heard = curr_sec;
6590 //fprintf(stderr,"Updating last heard time\n");
6595 // if (track_station_on == 1) // maybe we are tracking a station
6596 // track_station(da,tracking_station_call,p_station);
6599 // now do the drawing to the screen
6600 ok_to_display = !altnet || is_altnet(p_station); // Optimization step, needed twice below.
6602 // screen_update = 0;
6603 // if (changed_pos == 1 && Display_.trail && ((p_station->flag & ST_INVIEW) != 0)) {
6604 // if (ok_to_display) {
6605 // draw_trail(da,p_station,1); // update trail
6606 // screen_update = 1;
6611 if (changed_pos == 1 || !position_defined(last_lat,last_lon,0)) {
6612 if (ok_to_display) {
6614 screen_update = 1; // TODO -check needed
6618 } // defined position
6621 if (screen_update) {
6622 if (p_station->last_port_heard == APRS_PORT_TTY) { // Data from local TNC
6624 redraw_on_new_data = 2; // Update all symbols NOW!
6627 redraw_on_new_data = 0; // Update each 60 secs
6635 plot_aprs_station( p_station, TRUE );
6640 } // End of data_add() function
6643 int is_xnum_or_dash(char *data, int max){
6649 if(!(isxdigit((int)data[i]) || data[i]=='-')) {
6660 * Decode Mic-E encoded data
6662 int decode_Mic_E(char *call_sign,char *path,char *info,int port,int third_party) {
6672 int north,west,long_offset;
6674 char temp[MAX_LINE_SIZE+1]; // Note: Must be big in case we get long concatenated packets
6675 char new_info[MAX_LINE_SIZE+1]; // Note: Must be big in case we get long concatenated packets
6678 int msg1,msg2,msg3,msg;
6685 // MIC-E Data Format [APRS Reference, chapter 10]
6687 // todo: error check
6688 // drop wrong positions from receive errors...
6689 // drop 0N/0E position (p.25)
6691 /* First 7 bytes of info[] contains the APRS data type ID, */
6692 /* longitude, speed, course. */
6693 /* The 6-byte destination field of path[] contains latitude, */
6694 /* N/S bit, E/W bit, longitude offset, message code. */
6697 MIC-E Destination Field Format:
6698 -------------------------------
6699 Ar1DDDD0 Br1DDDD0 Cr1MMMM0 Nr1MMMM0 Lr1HHHH0 Wr1HHHH0 CrrSSID0
6700 D = Latitude Degrees.
6701 M = Latitude Minutes.
6702 H = Latitude Hundredths of Minutes.
6703 ABC = Message bits, complemented.
6704 N = N/S latitude bit (N=1).
6705 W = E/W longitude bit (W=1).
6706 L = 100's of longitude degrees (L=1 means add 100 degrees to longitude
6708 C = Command/Response flag (see AX.25 specification).
6709 r = reserved for future use (currently 0).
6712 /****************************************************************************
6713 * I still don't handle: *
6714 * Custom message bits *
6715 * SSID special routing *
6716 * Beta versions of the MIC-E (which use a slightly different format). *
6718 * DK7IN : lat/long with custom msg works, altitude/course/speed works *
6719 *****************************************************************************/
6722 // Note that the first MIC-E character was not passed to us, so we're
6723 // starting just past it.
6724 // Check for valid symbol table character. Should be '/' or '\'
6727 if ( info[7] == '/' // Primary table
6728 || info[7] == '\\' // Alternate table
6729 || (info[7] >= '0' && info[7] <= '9') // Overlay char
6730 || (info[7] >= 'A' && info[7] <= 'Z') ) { // Overlay char
6732 // We're good, keep going
6735 else { // Symbol table or overlay char incorrect
6737 if (info[6] == '/' || info[6] == '\\') { // Found it back one char in string
6738 // Don't print out the full info string here because it
6739 // can contain unprintable characters. In fact, we
6740 // should check the chars we do print out to make sure
6741 // they're printable, else print a space char.
6744 return(1); // No good, not MIC-E format or corrupted packet. Return 1
6745 // so that it won't get added to the database at all.
6748 // Check for valid symbol. Should be between '!' and '~' only.
6749 if (info[6] < '!' || info[6] > '~') {
6751 return(1); // No good, not MIC-E format or corrupted packet. Return 1
6752 // so that it won't get added to the database at all.
6755 // Check for minimum MIC-E size.
6756 if (strlen(info) < 8) {
6758 return(1); // No good, not MIC-E format or corrupted packet. Return 1
6759 // so that it won't get added to the database at all.
6762 // Check for 8-bit characters in the first eight slots. Not
6763 // allowed per Mic-E chapter of the spec.
6764 for (ii = 0; ii < 8; ii++) {
6765 if ((unsigned char)info[ii] > 0x7f) {
6766 // 8-bit data was found in the lat/long/course/speed
6767 // portion. Bad packet. Drop it.
6768 //fprintf(stderr, "%s: 8-bits found in Mic-E packet initial portion. Dropping it.\n", call_sign);
6773 // Check whether we have more data. If flag character is 0x1d
6774 // (8-bit telemetry flag) then don't do the 8-bit check below.
6775 if (strlen(info) > 8) {
6777 // Check for the 8-bit telemetry flag
6778 if ((unsigned char)info[8] == 0x1d) {
6779 // 8-bit telemetry found, skip the check loop below
6781 else { // 8-bit telemetry flag was not found. Check that
6782 // we only have 7-bit characters through the rest of
6785 for (ii = 8; ii < (int)strlen(info); ii++) {
6787 if ((unsigned char)info[ii] > 0x7f) {
6788 // 8-bit data was found. Bad packet. Drop it.
6789 //fprintf(stderr, "%s: 8-bits found in Mic-E packet final portion (not 8-bit telemetry). Dropping it.\n", call_sign);
6796 //fprintf(stderr,"Path1:%s\n",path);
6798 msg1 = (int)( ((unsigned char)path[0] & 0x40) >>4 );
6799 msg2 = (int)( ((unsigned char)path[1] & 0x40) >>5 );
6800 msg3 = (int)( ((unsigned char)path[2] & 0x40) >>6 );
6801 msg = msg1 | msg2 | msg3; // We now have the complemented message number in one variable
6802 msg = msg ^ 0x07; // And this is now the normal message number
6803 msgtyp = 0; // DK7IN: Std message, I have to add custom msg decoding
6805 //fprintf(stderr,"Msg: %d\n",msg);
6807 /* Snag the latitude from the destination field, Assume TAPR-2 */
6808 /* DK7IN: latitude now works with custom message */
6809 s_b1 = (unsigned char)( (path[0] & 0x0f) + (char)0x2f );
6810 //fprintf(stderr,"path0:%c\ts_b1:%c\n",path[0],s_b1);
6811 if (path[0] & 0x10) // A-J
6812 s_b1 += (unsigned char)1;
6814 if (s_b1 > (unsigned char)0x39) // K,L,Z
6815 s_b1 = (unsigned char)0x20;
6816 //fprintf(stderr,"s_b1:%c\n",s_b1);
6818 s_b2 = (unsigned char)( (path[1] & 0x0f) + (char)0x2f );
6819 //fprintf(stderr,"path1:%c\ts_b2:%c\n",path[1],s_b2);
6820 if (path[1] & 0x10) // A-J
6821 s_b2 += (unsigned char)1;
6823 if (s_b2 > (unsigned char)0x39) // K,L,Z
6824 s_b2 = (unsigned char)0x20;
6825 //fprintf(stderr,"s_b2:%c\n",s_b2);
6827 s_b3 = (unsigned char)( (path[2] & (char)0x0f) + (char)0x2f );
6828 if (path[2] & 0x10) // A-J
6829 s_b3 += (unsigned char)1;
6831 if (s_b3 > (unsigned char)0x39) // K,L,Z
6832 s_b3 = (unsigned char)0x20;
6834 s_b4 = (unsigned char)( (path[3] & 0x0f) + (char)0x30 );
6835 if (s_b4 > (unsigned char)0x39) // L,Z
6836 s_b4 = (unsigned char)0x20;
6839 s_b5 = (unsigned char)( (path[4] & 0x0f) + (char)0x30 );
6840 if (s_b5 > (unsigned char)0x39) // L,Z
6841 s_b5 = (unsigned char)0x20;
6844 s_b6 = (unsigned char)( (path[5] & 0x0f) + (char)0x30 );
6845 //fprintf(stderr,"path5:%c\ts_b6:%c\n",path[5],s_b6);
6846 if (s_b6 > (unsigned char)0x39) // L,Z
6847 s_b6 = (unsigned char)0x20;
6848 //fprintf(stderr,"s_b6:%c\n",s_b6);
6850 s_b7 = (unsigned char)path[6]; // SSID, not used here
6851 //fprintf(stderr,"path6:%c\ts_b7:%c\n",path[6],s_b7);
6853 //fprintf(stderr,"\n");
6855 // Special tests for 'L' due to position ambiguity deviances in
6856 // the APRS spec table. 'L' has the 0x40 bit set, but they
6857 // chose in the spec to have that represent position ambiguity
6858 // _without_ the North/West/Long Offset bit being set. Yuk!
6859 // Please also note that the tapr.org Mic-E document (not the
6860 // APRS spec) has the state of the bit wrong in columns 2 and 3
6861 // of their table. Reverse them.
6865 north = (int)((path[3] & 0x40) == (char)0x40); // N/S Lat Indicator
6870 long_offset = (int)((path[4] & 0x40) == (char)0x40); // Longitude Offset
6875 west = (int)((path[5] & 0x40) == (char)0x40); // W/E Long Indicator
6877 //fprintf(stderr,"north:%c->%d\tlat:%c->%d\twest:%c->%d\n",path[3],north,path[4],long_offset,path[5],west);
6879 /* Put the latitude string into the temp variable */
6880 snprintf(temp, sizeof(temp), "%c%c%c%c.%c%c%c%c",s_b1,s_b2,s_b3,s_b4,s_b5,s_b6,
6881 (north ? 'N': 'S'), info[7]); // info[7] = symbol table
6883 /* Compute degrees longitude */
6884 snprintf(new_info, sizeof(new_info), "%s", temp);
6885 d = (int) info[0]-28;
6890 if ((180<=d)&&(d<=189)) // ??
6893 if ((190<=d)&&(d<=199)) // ??
6896 /* Compute minutes longitude */
6897 m = (int) info[1]-28;
6901 /* Compute hundredths of minutes longitude */
6902 h = (int) info[2]-28;
6903 /* Add the longitude string into the temp variable */
6904 xastir_snprintf(temp, sizeof(temp), "%03d%02d.%02d%c%c",d,m,h,(west ? 'W': 'E'), info[6]);
6907 sizeof(new_info) - strlen(new_info));
6909 /* Compute speed in knots */
6910 speed = (int)( ( info[3] - (char)28 ) * (char)10 );
6911 speed += ( (int)( (info[4] - (char)28) / (char)10) );
6913 speed -= 800; // in knots
6915 /* Compute course */
6916 course = (int)( ( ( (info[4] - (char)28) % 10) * (char)100) + (info[5] - (char)28) );
6921 fprintf(stderr,"info[4]-28 mod 10 - 4 = %d\n",( ( (int)info[4]) - 28) % 10 - 4);
6922 fprintf(stderr,"info[5]-28 = %d\n", ( (int)info[5]) - 28 );
6924 xastir_snprintf(temp, sizeof(temp), "%03d/%03d",course,speed);
6927 sizeof(new_info) - strlen(new_info));
6928 offset = 8; // start of rest of info
6930 /* search for rig type in Mic-E data */
6932 if (info[offset] != '\0' && (info[offset] == '>' || info[offset] == ']')) {
6933 /* detected type code: > TH-D7 ] TM-D700 */
6934 if (info[offset] == '>')
6935 xastir_snprintf(rig_type,
6939 xastir_snprintf(rig_type,
6946 info_size = (int)strlen(info);
6947 /* search for compressed altitude in Mic-E data */ // {
6948 if (info_size >= offset+4 && info[offset+3] == '}') { // {
6949 /* detected altitude ___} */
6950 alt = ((((long)info[offset] - (long)33) * (long)91 +(long)info[offset+1] - (long)33) * (long)91
6951 + (long)info[offset+2] - (long)33) - 10000; // altitude in meters
6952 alt /= 0.3048; // altitude in feet, as in normal APRS
6954 //32808 is -10000 meters, or 10 km (deepest ocean), which is as low as a MIC-E
6955 //packet may go. Upper limit is mostly a guess.
6956 if ( (alt > 500000) || (alt < -32809) ) { // Altitude is whacko. Skip it.
6959 else { // Altitude is ok
6960 xastir_snprintf(temp, sizeof(temp), " /A=%06ld",alt);
6964 sizeof(new_info) - strlen(new_info));
6968 /* start of comment */
6969 if (strlen(rig_type) > 0) {
6970 xastir_snprintf(temp, sizeof(temp), "%s",rig_type);
6973 sizeof(new_info) - strlen(new_info));
6978 sizeof(new_info) - strlen(new_info));
6984 sizeof(new_info) - strlen(new_info));
6990 sizeof(new_info) - strlen(new_info));
6996 sizeof(new_info) - strlen(new_info));
7002 sizeof(new_info) - strlen(new_info));
7008 sizeof(new_info) - strlen(new_info));
7014 sizeof(new_info) - strlen(new_info));
7020 sizeof(new_info) - strlen(new_info));
7022 // Functionality removed
7029 sizeof(new_info) - strlen(new_info));
7033 xastir_snprintf(temp, sizeof(temp), "Custom%d",msg);
7036 sizeof(new_info) - strlen(new_info));
7039 if (info[offset] != '\0') {
7040 /* Append the rest of the message to the expanded MIC-E message */
7041 for (ii=offset; ii<info_size; ii++)
7042 temp[ii-offset] = info[ii];
7044 temp[info_size-offset] = '\0';
7047 sizeof(new_info) - strlen(new_info));
7050 sizeof(new_info) - strlen(new_info));
7054 // We don't transmit Mic-E protocol from Xastir, so we know it's
7055 // not our station's packets or our object/item packets,
7056 // therefore the last two parameters here are both zero.
7058 ok = data_add(APRS_MICE,call_sign,path,new_info,port,NULL,third_party, 0, 0);
7062 } // End of decode_Mic_E()
7067 void delete_object(char *name) {
7074 //fprintf(stderr,"delete_object\n");
7077 if (search_station_name(&p_station,name,1)) { // find object name
7078 p_station->flag &= (~ST_ACTIVE); // clear flag
7079 p_station->flag &= (~ST_INVIEW); // clear "In View" flag
7080 if (position_on_screen(p_station->coord_lat,p_station->coord_lon))
7081 redraw_on_new_data = 2; // redraw now
7082 // there is some problem... it is not redrawn immediately! ????
7083 // but deleted from list immediatetly
7084 redo_list = (int)TRUE; // and update lists
7091 * Decode APRS Information Field and dispatch it depending on the Data Type ID
7093 * call = Callsign or object/item name string
7094 * path = Path string
7095 * message = Info field (corrupted already if object/item packet)
7096 * origin = Originating callsign if object/item, otherwise NULL
7097 * from = DATA_VIA_LOCAL/DATA_VIA_TNC/DATA_VIA_NET/DATA_VIA_FILE
7098 * port = Port number
7099 * third_party = Set to one if third-party packet
7100 * orig_message = Unmodified info field
7103 void decode_info_field(gchar *call,
7109 gchar *orig_message)
7111 // char line[MAX_LINE_SIZE+1];
7116 int station_is_mine = 0;
7117 int object_is_mine = 0;
7119 /* remember fixed format starts with ! and can be up to 24 chars in the message */ // ???
7121 done = 0; // if 1, packet was decoded
7122 ignore = 0; // if 1, don't treat undecoded packets as status text
7123 ok_igate_net = 0; // if 1, send packet to internet
7124 ok_igate_rf = 0; // if 1, igate packet to RF if "from" is in nws-stations.txt
7126 if ( is_my_call(call, 1) ) {
7127 station_is_mine++; // Station is controlled by me
7130 if ( (message != NULL) && (strlen(message) > MAX_LINE_SIZE) ) {
7131 // Overly long message, throw it away.
7134 else if (message == NULL || strlen(message) == 0) {
7135 // we could have an empty message
7136 (void)data_add(STATION_CALL_DATA,call,path,NULL,port,origin,third_party, station_is_mine, 0);
7138 // don't report it to internet
7141 // special treatment for objects/items.
7142 if (!done && origin[0] != '\0') {
7144 // If station/object/item is owned by me (including SSID)
7145 if ( is_my_call(origin, 1) ) {
7149 if (message[0] == '*') { // set object
7150 (void)data_add(APRS_OBJECT,call,path,message+1,port,origin,third_party, station_is_mine, object_is_mine);
7151 if (strlen(origin) > 0 && strncmp(origin,"INET",4)!=0) {
7152 ok_igate_net = 1; // report it to internet
7158 else if (message[0] == '!') {
7160 (void)data_add(APRS_ITEM,call,path,message+1,port,origin,third_party, station_is_mine, object_is_mine);
7161 if (strlen(origin) > 0 && strncmp(origin,"INET",4)!=0) {
7162 ok_igate_net = 1; // report it to internet
7168 else if (message[0] == '_') { // delete object/item
7171 AprsDataRow *p_station;
7173 delete_object(call); // ?? does not vanish from map immediately !!???
7175 // If object was owned by me but another station is
7176 // transmitting it now, write entries into the
7177 // object.log file showing that we don't own this object
7181 if (search_station_name(&p_station,call,1)) {
7182 if (is_my_object_item(p_station) // If station was owned by me (including SSID)
7183 && (!object_is_mine) ) { // But isn't now
7184 disown_object_item(call,origin);
7188 if (strlen(origin) > 0 && strncmp(origin,"INET",4)!=0) {
7189 ok_igate_net = 1; // report it to internet
7202 data_id = message[0]; // look at the APRS Data Type ID (first char in information field)
7203 message += 1; // extract data ID from information field
7204 ok_igate_net = 1; // as default report packet to internet
7208 case '=': // Position without timestamp (with APRS messaging)
7211 // Need to check for weather info in this packet type as well?
7213 done = data_add(APRS_MSGCAP,call,path,message,port,origin,third_party, station_is_mine, 0);
7217 case '!': // Position without timestamp (no APRS messaging) or Ultimeter 2000 WX
7218 if (message[0] == '!' && is_xnum_or_dash(message+1,40)) // Ultimeter 2000 WX
7219 done = data_add(APRS_WX3,call,path,message+1,port,origin,third_party, station_is_mine, 0);
7221 done = data_add(APRS_FIXED,call,path,message,port,origin,third_party, station_is_mine, 0);
7225 case '/': // Position with timestamp (no APRS messaging)
7228 // Need weather decode in this section similar to the '@' section
7231 if ((toupper(message[14]) == 'N' || toupper(message[14]) == 'S') &&
7232 (toupper(message[24]) == 'W' || toupper(message[24]) == 'E')) { // uncompressed format
7233 if (message[29] == '/') {
7234 if (message[33] == 'g' && message[37] == 't')
7235 done = data_add(APRS_WX1,call,path,message,port,origin,third_party, station_is_mine, 0);
7237 done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
7240 done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
7242 else { // compressed format
7243 if (message[16] >= '!' && message[16] <= 'z') { // csT is speed/course
7244 if (message[20] == 'g' && message[24] == 't') // Wx data
7245 done = data_add(APRS_WX1,call,path,message,port,origin,third_party, station_is_mine, 0);
7247 done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
7250 done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
7252 // done = data_add(APRS_DOWN,call,path,message,from,port,origin,third_party, station_is_mine, 0);
7256 case '@': // Position with timestamp (with APRS messaging)
7257 // DK7IN: could we need to test the message length first?
7258 if ((toupper(message[14]) == 'N' || toupper(message[14]) == 'S') &&
7259 (toupper(message[24]) == 'W' || toupper(message[24]) == 'E')) { // uncompressed format
7260 if (message[29] == '/') {
7261 if (message[33] == 'g' && message[37] == 't')
7262 done = data_add(APRS_WX1,call,path,message,port,origin,third_party, station_is_mine, 0);
7264 done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
7267 done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
7269 else { // compressed format
7270 if (message[16] >= '!' && message[16] <= 'z') { // csT is speed/course
7271 if (message[20] == 'g' && message[24] == 't') // Wx data
7272 done = data_add(APRS_WX1,call,path,message,port,origin,third_party, station_is_mine, 0);
7274 done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
7277 done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
7282 case '[': // Maidenhead grid locator beacon (obsolete- but used for meteor scatter)
7283 done = data_add(APRS_GRID,call,path,message,port,origin,third_party, station_is_mine, 0);
7286 case 0x27: // Mic-E Old GPS data (or current GPS data in Kenwood TM-D700)
7287 case 0x60: // Mic-E Current GPS data (but not used in Kennwood TM-D700)
7288 //case 0x1c:// Mic-E Current GPS data (Rev. 0 beta units only)
7289 //case 0x1d:// Mic-E Old GPS data (Rev. 0 beta units only)
7290 done = decode_Mic_E(call,path,message,port,third_party);
7294 case '_': // Positionless weather data [APRS Reference, chapter 12]
7295 done = data_add(APRS_WX2,call,path,message,port,origin,third_party, station_is_mine, 0);
7299 case '#': // Peet Bros U-II Weather Station (km/h) [APRS Reference, chapter 12]
7300 if (is_xnum_or_dash(message,13))
7301 done = data_add(APRS_WX4,call,path,message,port,origin,third_party, station_is_mine, 0);
7305 case '*': // Peet Bros U-II Weather Station (mph)
7306 if (is_xnum_or_dash(message,13))
7307 done = data_add(APRS_WX6,call,path,message,port,origin,third_party, station_is_mine, 0);
7311 case '$': // Raw GPS data or Ultimeter 2000
7312 if (strncmp("ULTW",message,4) == 0 && is_xnum_or_dash(message+4,44))
7313 done = data_add(APRS_WX5,call,path,message+4,port,origin,third_party, station_is_mine, 0);
7314 else if (strncmp("GPGGA",message,5) == 0)
7315 done = data_add(GPS_GGA,call,path,message,port,origin,third_party, station_is_mine, 0);
7316 else if (strncmp("GPRMC",message,5) == 0)
7317 done = data_add(GPS_RMC,call,path,message,port,origin,third_party, station_is_mine, 0);
7318 else if (strncmp("GPGLL",message,5) == 0)
7319 done = data_add(GPS_GLL,call,path,message,port,origin,third_party, station_is_mine, 0);
7321 // handle VTG and WPT too (APRS Ref p.25)
7326 case ':': // Message
7328 // Do message logging if that feature is enabled.
7329 done = decode_message(call,path,message,port,third_party);
7331 // there could be messages I should not retransmit to internet... ??? Queries to me...
7334 case '>': // Status [APRS Reference, chapter 16]
7335 done = data_add(APRS_STATUS,call,path,message,port,origin,third_party, station_is_mine, 0);
7340 done = process_query(call,path,message,port,third_party);
7341 ignore = 1; // don't treat undecoded packets as status text
7344 case 'T': // Telemetry data [APRS Reference, chapter 13]
7345 // We treat these as status packets currently.
7347 done = data_add(APRS_STATUS,call,path,message,port,origin,third_party, station_is_mine, 0);
7350 case '{': // User-defined APRS packet format //}
7351 // We treat these as status packets currently.
7355 case '<': // Station capabilities [APRS Reference, chapter 15]
7357 // We could tweak the Incoming Data dialog to add
7358 // filter togglebuttons. One such toggle could be
7359 // "Station Capabilities". We'd then have a usable
7360 // dialog for displaying things like ?IGATE?
7361 // responses. In this case we wouldn't have to do
7362 // anything special with the packet for decoding,
7363 // just let it hit the default block below for
7364 // putting them into the status field of the record.
7365 // One downside is that we'd only be able to catch
7366 // new station capability records in that dialog.
7367 // The only way to look at past capability records
7368 // would be the Station Info dialog for each
7371 //fprintf(stderr,"%10s: %s\n", call, message);
7373 // Don't set "done" as we want these to appear in
7374 // the status text for the record.
7377 case '%': // Agrelo DFJr / MicroFinder Radio Direction Finding
7379 // Here is where we'd add a call to an RDF decode
7380 // function so that we could display vectors on the
7381 // map for each RDF position.
7384 // Agrelo format: "%XXX/Q<cr>"
7386 // "XXX" is relative bearing to the signal (000-359). Careful here:
7387 // At least one unit reports in magnetic instead of relative
7388 // degrees. "000" means no direction info available, 360 means true
7391 // "Q" is bearing quality (0-9). 0 = unsuitable. 9 = manually
7392 // entered. 1-8 = varying quality with 8 being the best.
7394 // I've also seen these formats, which may not be Agrelo compatible:
7397 // "%136.0/8/158.0" (That last number is magnetic bearing)
7399 // These sentences may be sent MULTIPLE times per second, like 20 or
7400 // more! If we decide to average readings, we'll need to dump our
7401 // averages and start over if our course changes.
7404 // Check for Agrelo format:
7405 if ( strlen(message) >= 5
7406 && is_num_chr(message[0]) // "%136/9"
7407 && is_num_chr(message[1])
7408 && is_num_chr(message[2])
7409 && message[3] == '/'
7410 && is_num_chr(message[4]) ) {
7415 "Type 1 RDF packet from call: %s\tBearing: %c%c%c\tQuality: %c\n",
7424 // Check for extended formats (not
7425 // Agrelo-compatible):
7426 else if (strlen(message) >= 13
7427 && is_num_chr(message[0]) // "%136.0/8/158.0"
7428 && is_num_chr(message[1])
7429 && is_num_chr(message[2])
7430 && message[3] == '.'
7431 && is_num_chr(message[4])
7432 && message[5] == '/'
7433 && is_num_chr(message[6])
7434 && message[7] == '/'
7435 && is_num_chr(message[8])
7436 && is_num_chr(message[9])
7437 && is_num_chr(message[10])
7438 && message[11] == '.'
7439 && is_num_chr(message[12]) ) {
7444 "Type 3 RDF packet from call: %s\tBearing: %c%c%c%c%c\tQuality: %c\tMag Bearing: %c%c%c%c%c\n",
7459 // Check for extended formats (not
7460 // Agrelo-compatible):
7461 else if (strlen(message) >= 7
7462 && is_num_chr(message[0]) // "%136.0/9"
7463 && is_num_chr(message[1])
7464 && is_num_chr(message[2])
7465 && message[3] == '.'
7466 && is_num_chr(message[4])
7467 && message[5] == '/'
7468 && is_num_chr(message[6]) ) {
7473 "Type 2 RDF packet from call: %s\tBearing: %c%c%c%c%c\tQuality: %c\n",
7483 // Don't set "done" as we want these to appear in
7484 // the status text for the record until we get the
7485 // full decoding for this type of packet coded up.
7488 case '~': // UI-format messages, not relevant for APRS ("Do not use" in Reference)
7489 case ',': // Invalid data or test packets [APRS Reference, chapter 19]
7490 case '&': // Reserved -- Map Feature
7491 ignore = 1; // Don't treat undecoded packets as status text
7496 // Add most remaining data to the station record as status
7499 if (!done && !ignore) { // Other Packets [APRS Reference, chapter 19]
7500 done = data_add(OTHER_DATA,call,path,message-1,port,origin,third_party, station_is_mine, 0);
7501 ok_igate_net = 0; // don't put data on internet ????
7504 if (!done) { // data that we do ignore...
7505 //fprintf(stderr,"decode_info_field: not decoding info: Call:%s ID:%c Msg:|%s|\n",call,data_id,message);
7506 ok_igate_net = 0; // don't put data on internet
7511 ok_igate_net = 0; // don't put third party traffic on internet
7514 if (station_is_mine)
7515 ok_igate_net = 0; // don't put my data on internet ???
7518 // TODO - Add TX to IGate support
7522 if ( (from == DATA_VIA_TNC) // Came in via a TNC
7523 && (strlen(orig_message) > 0) ) { // Not empty
7525 // Here's where we inject our own callsign like this:
7526 // "WE7U-15,I" in order to provide injection ID for our
7531 (strlen(origin)) ? origin : call,
7536 output_igate_net(line, port, third_party);
7545 * Check for a valid object name
7547 int valid_object(char *name) {
7550 // max 9 printable ASCII characters, case sensitive [APRS
7552 len = (int)strlen(name);
7553 if (len > 9 || len == 0)
7554 return(0); // wrong size
7557 if (!isprint((int)name[i]))
7558 return(0); // not printable
7569 * Extract object or item data from information field before processing
7571 * Returns 1 if valid object found, else returns 0.
7574 int extract_object(char *call, char **info, char *origin)
7578 // Object and Item Reports [APRS Reference, chapter 11]
7580 // todo: add station originator to database
7581 if ((*info)[0] == ';') { // object
7582 // fixed 9 character object name with any printable ASCII character
7583 if (strlen((*info)) > 1+9) {
7584 substr(call,(*info)+1,9); // extract object name
7585 (*info) = (*info) + 10;
7586 // Remove leading spaces ? They look bad, but are allowed by the APRS Reference ???
7587 (void)remove_trailing_spaces(call);
7588 if (valid_object(call)) {
7589 // info length is at least 1
7594 else if ((*info)[0] == ')') { // item
7595 // 3 - 9 character item name with any printable ASCII character
7596 if (strlen((*info)) > 1+3) {
7597 for (i = 1; i <= 9; i++) {
7598 if ((*info)[i] == '!' || (*info)[i] == '_') {
7602 call[i-1] = (*info)[i];
7604 call[9] = '\0'; // In case we never saw '!' || '_'
7605 (*info) = &(*info)[i];
7606 // Remove leading spaces ? They look bad, but are allowed by the APRS Reference ???
7607 //(void)remove_trailing_spaces(call); // This statement messed up our searching!!! Don't use it!
7608 if (valid_object(call)) {
7609 // info length is at least 1
7616 fprintf(stderr,"Not an object, nor an item!!! call=%s, info=%s, origin=%s.\n",
7617 call, *info, origin);
7625 * \r and \n should already be stripped from end of line
7626 * line should not be NULL
7628 * If dbadd is set, add to database. Otherwise, just return true/false
7629 * to indicate whether input is valid AX25 line.
7632 // Note that the length of "line" can be up to MAX_DEVICE_BUFFER,
7633 // which is currently set to 4096.
7635 gint decode_ax25_line(gchar *line, TAprsPort port) {
7638 gchar path[100+1]; // new one, we may add an '*'
7640 gchar info_copy[MAX_LINE_SIZE+1];
7641 gchar call[MAX_CALLSIGN+1];
7642 gchar origin[MAX_CALLSIGN+1];
7645 // gchar backup[MAX_LINE_SIZE+1];
7646 // gchar tmp_line[MAX_LINE_SIZE+1];
7647 // gchar tmp_path[100+1];
7648 // gchar *ViaCalls[10];
7655 if ( strlen(line) > MAX_LINE_SIZE )
7657 // Overly long message, throw it away. We're done.
7661 if (line[strlen(line)-1] == '\n') // better: look at other places,
7662 // so that we don't get it here...
7663 line[strlen(line)-1] = '\0'; // Wipe out '\n', to be sure
7664 if (line[strlen(line)-1] == '\r')
7665 line[strlen(line)-1] = '\0'; // Wipe out '\r'
7675 // CALL>PATH:APRS-INFO-FIELD // split line into components
7678 call_sign = (gchar*)strtok(line,">"); // extract call from AX.25 line
7679 if (call_sign != NULL) {
7680 path0 = (gchar*)strtok(NULL,":"); // extract path from AX.25 line
7681 if (path0 != NULL) {
7682 info = (gchar*)strtok(NULL,""); // rest is info_field
7684 if ((info - path0) < 100) { // check if path could be copied
7685 ok = 1; // we have found all three components
7693 snprintf(path, sizeof(path), "%s", path0);
7695 snprintf(info_copy, sizeof(info_copy), "%s", info);
7697 ok = valid_path(path); // check the path and convert it to TAPR format
7698 // Note that valid_path() also removes igate injection identifiers
7703 extract_TNC_text(info); // extract leading text from TNC X-1J4
7704 if (strlen(info) > 256) // first check if information field conforms to APRS specs
7705 ok = 0; // drop packets too long
7710 (void)remove_trailing_asterisk(call_sign); // is an asterisk valid here ???
7712 if (valid_inet_name(call_sign,info,origin,sizeof(origin)))
7713 { // accept some of the names used in internet
7714 snprintf(call, sizeof(call), "%s", call_sign);
7716 else if (valid_call(call_sign))
7717 { // accept real AX.25 calls
7718 snprintf(call, sizeof(call), "%s", call_sign);
7725 if (ok && info[0] == '}')
7727 // look for third-party traffic
7728 ok = extract_third_party(call,path,sizeof(path),&info,origin,sizeof(origin)); // extract third-party data
7732 if (ok && (info[0] == ';' || info[0] == ')'))
7734 // look for objects or items
7735 snprintf(origin, sizeof(origin), "%s", call);
7737 ok = extract_object(call,&info,origin); // extract object data
7742 // decode APRS information field, always called with valid call and path
7743 // info is a string with 0 - 256 bytes
7744 // fprintf(stderr,"dec: %s (%s) %s\n",call,origin,info);
7746 decode_info_field(call,
7764 double convert_lon_l2d(long lon)
7766 double dResult = 0.00;
7769 float deg, min; //, sec;
7773 deg = (float)(lon - 64800000l) / 360000.0;
7775 // Switch to integer arithmetic to avoid floating-point rounding
7777 temp = (long)(deg * 100000);
7787 ideg = (int)temp / 100000;
7788 min = (temp % 100000) * 60.0 / 100000.0;
7791 dResult = ewpn*(ideg+min/60.0);
7799 // convert latitude from long to double
7800 // Input is in Xastir coordinate system
7802 double convert_lat_l2d(long lat)
7804 double dResult = 0.00;
7811 deg = (float)(lat - 32400000l) / 360000.0;
7813 // Switch to integer arithmetic to avoid floating-point
7815 temp = (long)(deg * 100000);
7823 ideg = (int)temp / 100000;
7824 min = (temp % 100000) * 60.0 / 100000.0;
7826 dResult = nspn*(ideg+min/60.0);
7833 /***********************************************************/
7834 /* returns the hour (00..23), localtime */
7835 /***********************************************************/
7836 int get_hours(void) {
7837 struct tm *time_now;
7842 time_now = localtime(&secs_now);
7843 (void)strftime(shour,4,"%H",time_now);
7844 return(atoi(shour));
7850 /***********************************************************/
7851 /* returns the minute (00..59), localtime */
7852 /***********************************************************/
7853 int get_minutes(void) {
7854 struct tm *time_now;
7859 time_now = localtime(&secs_now);
7860 (void)strftime(sminute,4,"%M",time_now);
7861 return(atoi(sminute));
7867 /**************************************************************/
7868 /* compute_rain_hour - rolling average for the last 59.x */
7869 /* minutes of rain. I/O numbers are in hundredths of an inch.*/
7870 /* Output is in "rain_minute_total", a global variable. */
7871 /**************************************************************/
7872 void compute_rain_hour(float rain_total) {
7877 // Deposit the _starting_ rain_total for each minute into a separate bucket.
7878 // Subtract lowest rain_total in queue from current rain_total to get total
7879 // for the last hour.
7882 current = get_minutes(); // Fetch the current minute value. Use this as an index
7883 // into our minute "buckets" where we store the data.
7886 rain_minute[ (current + 1) % 60 ] = 0.0; // Zero out the next bucket (probably have data in
7887 // there from the previous hour).
7890 if (rain_minute[current] == 0.0) // If no rain_total stored yet in this minute's bucket
7891 rain_minute[current] = rain_total; // Write into current bucket
7894 // Find the lowest non-zero value for rain_total. The max value is "rain_total".
7895 lowest = rain_total; // Set to maximum to get things going
7896 for (j = 0; j < 60; j++) {
7897 if ( (rain_minute[j] > 0.0) && (rain_minute[j] < lowest) ) { // Found a lower non-zero value?
7898 lowest = rain_minute[j]; // Snag it
7901 // Found it, subtract the two to get total for the last hour
7902 rain_minute_total = rain_total - lowest;
7908 /***********************************************************/
7909 /* compute_rain - compute rain totals from the total rain */
7910 /* so far. rain_total (in hundredths of an inch) keeps on */
7912 /***********************************************************/
7913 void compute_rain(float rain_total) {
7918 // Skip the routine if input is outlandish (Negative value, zero, or 512 inches!).
7919 // We seem to get occasional 0.0 packets from wx200d. This makes them go away.
7920 if ( (rain_total <= 0.0) || (rain_total > 51200.0) )
7923 compute_rain_hour(rain_total);
7925 current = get_hours();
7927 // Set rain_base: The first rain_total for each hour.
7928 if (rain_base[current] == 0.0) { // If we don't have a start value yet for this hour,
7929 rain_base[current] = rain_total; // save it away.
7930 rain_check = 0; // Set up rain_check so we'll do the following
7931 // "else" clause a few times at start of each hour.
7933 else { // rain_base has been set, is it wrong? We recheck three times at start of hour.
7934 if (rain_check < 3) {
7936 // Is rain less than base? It shouldn't be.
7937 if (rain_total < rain_base[current])
7938 rain_base[current] = rain_total;
7940 // Difference greater than 10 inches since last reading? It shouldn't be.
7941 if (fabs(rain_total - rain_base[current]) > 1000.0) // Remember: Hundredths of an inch
7942 rain_base[current] = rain_total;
7945 rain_base[ (current + 1) % 24 ] = 0.0; // Clear next hour's index.
7948 // Compute total rain in last 24 hours: Really we'll compute the total rain
7949 // in the last 23 hours plus the portion of an hour we've completed (Sum up
7950 // all 24 of the hour totals). This isn't the perfect way to do it, but to
7951 // really do it right we'd need finer increments of time (to get closer to
7952 // the goal of 24 hours of rain).
7954 for ( i = 0; i < 24; i++ ) { // Find the lowest non-zero rain_base value in last 24 hours
7955 if ( (rain_base[i] > 0.0) && (rain_base[i] < lower) ) {
7956 lower = rain_base[i];
7959 rain_24 = rain_total - lower; // Hundredths of an inch
7962 // Compute rain since midnight. Note that this uses whatever local time was set
7963 // on the machine. It might not be local midnight if your box is set to GMT.
7965 for ( i = 0; i <= current; i++ ) { // Find the lowest non-zero rain_base value since midnight
7966 if ( (rain_base[i] > 0.0) && (rain_base[i] < lower) ) {
7967 lower = rain_base[i];
7970 rain_00 = rain_total - lower; // Hundredths of an inch
7972 // It is the responsibility of the calling program to save
7973 // the new totals in the data structure for our station.
7974 // We don't return anything except in global variables.
7981 /**************************************************************/
7982 /* compute_gust - compute max wind gust during last 5 minutes */
7984 /**************************************************************/
7985 float compute_gust(float wx_speed, float last_speed, time_t *last_speed_time) {
7986 float computed_gust;
7990 // Deposit max gust for each minute into a different bucket.
7991 // Check all buckets for max gust within the last five minutes
7992 // (Really 4 minutes plus whatever portion of a minute we've completed).
7994 current = get_minutes(); // Fetch the current minute value. We use this as an index
7995 // into our minute "buckets" where we store the data.
7997 // If we haven't started collecting yet, set up to do so
7998 if (gust_read_ptr == gust_write_ptr) { // We haven't started yet
7999 gust_write_ptr = current; // Set to write into current bucket
8000 gust_last_write = current;
8002 gust_read_ptr = current - 1; // Set read pointer back one, modulus 60
8003 if (gust_read_ptr < 0)
8006 gust[gust_write_ptr] = 0.0; // Zero the max gust
8007 gust[gust_read_ptr] = 0.0; // for both buckets.
8010 //gust[gust_write_ptr] = 45.9;
8013 // Check whether we've advanced at least one minute yet
8014 if (current != gust_write_ptr) { // We've advanced to a different minute
8015 gust_write_ptr = current; // Start writing into a new bucket.
8016 gust[gust_write_ptr] = 0.0; // Zero the new bucket
8018 // Check how many bins of real data we have currently. Note that this '5' is
8019 // correct, as we just advanced "current" to the next minute. We're just pulling
8020 // along the read_ptr behind us if we have 5 bins worth of data by now.
8021 if ( ((gust_read_ptr + 5) % 60) == gust_write_ptr) // We have 5 bins of real data
8022 gust_read_ptr = (gust_read_ptr + 1) % 60; // So advance the read pointer,
8024 // Check for really bad pointers, perhaps the weather station got
8025 // unplugged for a while or it's just REALLY slow at sending us data?
8026 // We're checking to see if gust_last_write happened in the previous
8027 // minute. If not, we skipped a minute or more somewhere.
8028 if ( ((gust_last_write + 1) % 60) != current ) {
8029 // We lost some time somewhere: Reset the pointers, older gust data is
8030 // lost. Start over collecting new gust data.
8032 gust_read_ptr = current - 1; // Set read pointer back one, modulus 60
8033 if (gust_read_ptr < 0)
8036 gust[gust_read_ptr] = 0.0;
8038 gust_last_write = current;
8041 // Is current wind speed higher than the current minute bucket?
8042 if (wx_speed > gust[gust_write_ptr])
8043 gust[gust_write_ptr] = wx_speed; // Save it in the bucket
8045 // Read the last (up to) five buckets and find the max gust
8046 computed_gust=gust[gust_write_ptr];
8048 while (j != ((gust_write_ptr + 1) % 60) ) {
8049 if ( computed_gust < gust[j] )
8050 computed_gust = gust[j];
8054 *last_speed_time = sec_now();
8055 return(computed_gust);
8062 //*********************************************************************
8063 // Decode Peet Brothers Ultimeter 2000 weather data (Data logging mode)
8065 // This function is called from db.c:data_add() only. Used for
8066 // decoding incoming packets, not for our own weather station data.
8068 // The Ultimeter 2000 can be in any of three modes, Data Logging Mode,
8069 // Packet Mode, or Complete Record Mode. This routine handles only
8070 // the Data Logging Mode.
8071 //*********************************************************************
8073 void decode_U2000_L (int from, unsigned char *data, AprsWeatherRow *weather) {
8074 time_t last_speed_time;
8076 float computed_gust;
8077 char temp_data1[10];
8082 last_speed_time = 0;
8083 computed_gust = 0.0;
8086 weather->wx_type = WX_TYPE;
8087 xastir_snprintf(weather->wx_station,
8088 sizeof(weather->wx_station),
8091 /* get last gust speed */
8092 if (strlen(weather->wx_gust) > 0 && !from) {
8093 /* get last speed */
8094 last_speed = (float)atof(weather->wx_gust);
8095 last_speed_time = weather->wx_speed_sec_time;
8099 // 00A4 00 46 01FF 380E 2755 02C1 03E8 ---- 0052 04D7 0001 007BM
8101 // 0 6 8 12 16 24 40
8103 if (data[0] != '-') { // '-' signifies invalid data
8104 substr(temp_data1,(char *)data,4);
8105 xastir_snprintf(weather->wx_speed,
8106 sizeof(weather->wx_speed),
8108 (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
8110 weather->wx_speed_sec_time = sec_now();
8113 computed_gust = compute_gust((float)atof(weather->wx_speed),
8116 weather->wx_speed_sec_time = sec_now();
8117 xastir_snprintf(weather->wx_gust,
8118 sizeof(weather->wx_gust),
8120 (int)(0.5 + computed_gust)); // Cheater's way of rounding
8124 weather->wx_speed[0] = 0;
8127 /* wind direction */
8129 // Note that the first two digits here may be 00, or may be FF
8130 // if a direction calibration has been entered. We should zero
8133 if (data[6] != '-') { // '-' signifies invalid data
8134 substr(temp_data1,(char *)(data+6),2);
8135 temp_data1[0] = '0';
8136 temp_data1[1] = '0';
8137 xastir_snprintf(weather->wx_course,
8138 sizeof(weather->wx_course),
8140 (int)(((float)strtol(temp_data1,&temp_conv,16)/256.0)*360.0));
8142 xastir_snprintf(weather->wx_course,
8143 sizeof(weather->wx_course),
8146 weather->wx_course[0]=0;
8150 if (data[8] != '-') { // '-' signifies invalid data
8153 substr(temp_data1,(char *)(data+8),4);
8154 temp4 = (int)strtol(temp_data1,&temp_conv,16);
8156 if (temp_data1[0] > '7') { // Negative value, convert
8157 temp4 = (temp4 & (temp4-0x7FFF)) - 0x8000;
8160 xastir_snprintf(weather->wx_temp,
8161 sizeof(weather->wx_temp),
8163 (int)((float)((temp4<<16)/65536)/10.0));
8168 weather->wx_temp[0]=0;
8171 /* rain total long term */
8172 if (data[12] != '-') { // '-' signifies invalid data
8173 substr(temp_data1,(char *)(data+12),4);
8174 xastir_snprintf(weather->wx_rain_total,
8175 sizeof(weather->wx_rain_total),
8177 (float)strtol(temp_data1,&temp_conv,16)/100.0);
8180 compute_rain((float)atof(weather->wx_rain_total));
8182 xastir_snprintf(weather->wx_rain,
8183 sizeof(weather->wx_rain),
8186 /*last 24 hour rain */
8187 xastir_snprintf(weather->wx_prec_24,
8188 sizeof(weather->wx_prec_24),
8191 /* rain since midnight */
8192 xastir_snprintf(weather->wx_prec_00,
8193 sizeof(weather->wx_prec_00),
8199 weather->wx_rain_total[0]=0;
8203 if (data[16] != '-') { // '-' signifies invalid data
8204 substr(temp_data1,(char *)(data+16),4);
8205 xastir_snprintf(weather->wx_baro,
8206 sizeof(weather->wx_baro),
8208 (float)strtol(temp_data1,&temp_conv,16)/10.0);
8211 weather->wx_baro[0]=0;
8215 /* outdoor humidity */
8216 if (data[24] != '-') { // '-' signifies invalid data
8217 substr(temp_data1,(char *)(data+24),4);
8218 xastir_snprintf(weather->wx_hum,
8219 sizeof(weather->wx_hum),
8221 (int)((float)strtol(temp_data1,&temp_conv,16)/10.0));
8224 weather->wx_hum[0]=0;
8227 /* todays rain total */
8228 if (data[40] != '-') { // '-' signifies invalid data
8230 substr(temp_data1,(char *)(data+40),4);
8231 xastir_snprintf(weather->wx_prec_00,
8232 sizeof(weather->wx_prec_00),
8234 (float)strtol(temp_data1,&temp_conv,16)/100.0);
8238 weather->wx_prec_00[0] = 0;
8246 //********************************************************************
8247 // Decode Peet Brothers Ultimeter 2000 weather data (Packet mode)
8249 // This function is called from db.c:data_add() only. Used for
8250 // decoding incoming packets, not for our own weather station data.
8252 // The Ultimeter 2000 can be in any of three modes, Data Logging Mode,
8253 // Packet Mode, or Complete Record Mode. This routine handles only
8255 //********************************************************************
8256 void decode_U2000_P(int from, unsigned char *data, AprsWeatherRow *weather) {
8257 time_t last_speed_time;
8259 float computed_gust;
8260 char temp_data1[10];
8265 last_speed_time = 0;
8266 computed_gust = 0.0;
8267 len = (int)strlen((char *)data);
8269 weather->wx_type = WX_TYPE;
8270 xastir_snprintf(weather->wx_station,
8271 sizeof(weather->wx_station),
8274 /* get last gust speed */
8275 if (strlen(weather->wx_gust) > 0 && !from) {
8276 /* get last speed */
8277 last_speed = (float)atof(weather->wx_gust);
8278 last_speed_time = weather->wx_speed_sec_time;
8281 // $ULTW 0031 00 37 02CE 0069 ---- 0000 86A0 0001 ---- 011901CC 0000 0005
8283 // 0 6 8 12 16 32 44 48
8285 /* wind speed peak over last 5 min */
8286 if (data[0] != '-') { // '-' signifies invalid data
8287 substr(temp_data1,(char *)data,4);
8289 xastir_snprintf(weather->wx_gust,
8290 sizeof(weather->wx_gust),
8292 (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
8293 /* this may be the only wind data */
8294 xastir_snprintf(weather->wx_speed,
8295 sizeof(weather->wx_speed),
8297 (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
8299 /* local station and may be the only wind data */
8301 xastir_snprintf(weather->wx_speed,
8302 sizeof(weather->wx_speed),
8304 (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
8305 computed_gust = compute_gust((float)atof(weather->wx_speed),
8308 weather->wx_speed_sec_time = sec_now();
8309 xastir_snprintf(weather->wx_gust,
8310 sizeof(weather->wx_gust),
8312 (int)(0.5 + computed_gust));
8317 weather->wx_gust[0] = 0;
8320 /* wind direction */
8322 // Note that the first two digits here may be 00, or may be FF
8323 // if a direction calibration has been entered. We should zero
8326 if (data[6] != '-') { // '-' signifies invalid data
8327 substr(temp_data1,(char *)(data+6),2);
8328 temp_data1[0] = '0';
8329 temp_data1[1] = '0';
8330 xastir_snprintf(weather->wx_course,
8331 sizeof(weather->wx_course),
8333 (int)(((float)strtol(temp_data1,&temp_conv,16)/256.0)*360.0));
8335 xastir_snprintf(weather->wx_course,
8336 sizeof(weather->wx_course),
8339 weather->wx_course[0] = 0;
8343 if (data[8] != '-') { // '-' signifies invalid data
8346 substr(temp_data1,(char *)(data+8),4);
8347 temp4 = (int)strtol(temp_data1,&temp_conv,16);
8349 if (temp_data1[0] > '7') { // Negative value, convert
8350 temp4 = (temp4 & (temp4-0x7FFF)) - 0x8000;
8353 xastir_snprintf(weather->wx_temp,
8354 sizeof(weather->wx_temp),
8356 (int)((float)((temp4<<16)/65536)/10.0));
8360 weather->wx_temp[0] = 0;
8362 /* todays rain total (on some units) */
8363 if ((data[44]) != '-') { // '-' signifies invalid data
8365 substr(temp_data1,(char *)(data+44),4);
8366 xastir_snprintf(weather->wx_prec_00,
8367 sizeof(weather->wx_prec_00),
8369 (float)strtol(temp_data1,&temp_conv,16)/100.0);
8373 weather->wx_prec_00[0] = 0;
8376 /* rain total long term */
8377 if (data[12] != '-') { // '-' signifies invalid data
8378 substr(temp_data1,(char *)(data+12),4);
8379 xastir_snprintf(weather->wx_rain_total,
8380 sizeof(weather->wx_rain_total),
8382 (float)strtol(temp_data1,&temp_conv,16)/100.0);
8385 compute_rain((float)atof(weather->wx_rain_total));
8387 snprintf(weather->wx_rain,
8388 sizeof(weather->wx_rain),
8391 /*last 24 hour rain */
8392 snprintf(weather->wx_prec_24,
8393 sizeof(weather->wx_prec_24),
8396 /* rain since midnight */
8397 snprintf(weather->wx_prec_00,
8398 sizeof(weather->wx_prec_00),
8404 weather->wx_rain_total[0] = 0;
8408 if (data[16] != '-') { // '-' signifies invalid data
8409 substr(temp_data1,(char *)(data+16),4);
8410 xastir_snprintf(weather->wx_baro,
8411 sizeof(weather->wx_baro),
8413 (float)strtol(temp_data1,&temp_conv,16)/10.0);
8416 weather->wx_baro[0] = 0;
8419 /* outdoor humidity */
8420 if (data[32] != '-') { // '-' signifies invalid data
8421 substr(temp_data1,(char *)(data+32),4);
8422 xastir_snprintf(weather->wx_hum,
8423 sizeof(weather->wx_hum),
8425 (int)((float)strtol(temp_data1,&temp_conv,16)/10.0));
8428 weather->wx_hum[0] = 0;
8431 /* 1 min wind speed avg */
8432 if (len > 48 && (data[48]) != '-') { // '-' signifies invalid data
8433 substr(temp_data1,(char *)(data+48),4);
8434 xastir_snprintf(weather->wx_speed,
8435 sizeof(weather->wx_speed),
8437 (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
8439 weather->wx_speed_sec_time = sec_now();
8442 computed_gust = compute_gust((float)atof(weather->wx_speed),
8445 weather->wx_speed_sec_time = sec_now();
8446 xastir_snprintf(weather->wx_gust,
8447 sizeof(weather->wx_gust),
8449 (int)(0.5 + computed_gust));
8454 weather->wx_speed[0] = 0;
8463 //*****************************************************************
8464 // Decode Peet Brothers Ultimeter-II weather data
8466 // This function is called from db.c:data_add() only. Used for
8467 // decoding incoming packets, not for our own weather station data.
8468 //*****************************************************************
8469 void decode_Peet_Bros(int from, unsigned char *data, AprsWeatherRow *weather, int type) {
8470 time_t last_speed_time;
8472 float computed_gust;
8473 char temp_data1[10];
8477 computed_gust = 0.0;
8478 last_speed_time = 0;
8480 weather->wx_type = WX_TYPE;
8481 xastir_snprintf(weather->wx_station,
8482 sizeof(weather->wx_station),
8488 // # 5 0B 75 0082 0082
8489 // * 7 00 76 0000 0000
8491 // rain [1/100 inch ?]
8493 // wind speed [mph / km/h]
8496 /* wind direction */
8503 substr(temp_data1,(char *)data,1);
8504 snprintf(weather->wx_course,
8505 sizeof(weather->wx_course),
8507 (int)(((float)strtol(temp_data1,&temp_conv,16)/16.0)*360.0));
8509 /* get last gust speed */
8510 if (strlen(weather->wx_gust) > 0 && !from) {
8511 /* get last speed */
8512 last_speed = (float)atof(weather->wx_gust);
8513 last_speed_time = weather->wx_speed_sec_time;
8517 substr(temp_data1,(char *)(data+1),2);
8518 if (type == APRS_WX4) { // '#' speed in km/h, convert to mph
8519 snprintf(weather->wx_speed,
8520 sizeof(weather->wx_speed),
8522 (int)(0.5 + (float)(strtol(temp_data1,&temp_conv,16)*0.62137)));
8523 } else { // type == APRS_WX6, '*' speed in mph
8524 xastir_snprintf(weather->wx_speed,
8525 sizeof(weather->wx_speed),
8527 (int)(0.5 + (float)strtol(temp_data1,&temp_conv,16)));
8531 weather->wx_speed_sec_time = sec_now();
8534 computed_gust = compute_gust((float)atof(weather->wx_speed),
8537 weather->wx_speed_sec_time = sec_now();
8538 xastir_snprintf(weather->wx_gust,
8539 sizeof(weather->wx_gust),
8541 (int)(0.5 + computed_gust));
8545 if (data[3] != '-') { // '-' signifies invalid data
8548 substr(temp_data1,(char *)(data+3),2);
8549 temp4 = (int)strtol(temp_data1,&temp_conv,16);
8551 if (temp_data1[0] > '7') { // Negative value, convert
8552 temp4 = (temp4 & (temp4-0x7FFF)) - 0x8000;
8555 xastir_snprintf(weather->wx_temp,
8556 sizeof(weather->wx_temp),
8561 weather->wx_temp[0] = 0;
8564 // Rain divided by 100 for readings in hundredth of an inch
8565 if (data[5] != '-') { // '-' signifies invalid data
8566 substr(temp_data1,(char *)(data+5),4);
8567 xastir_snprintf(weather->wx_rain_total,
8568 sizeof(weather->wx_rain_total),
8570 (float)strtol(temp_data1,&temp_conv,16)/100.0);
8573 compute_rain((float)atof(weather->wx_rain_total));
8575 xastir_snprintf(weather->wx_rain,
8576 sizeof(weather->wx_rain),
8579 /*last 24 hour rain */
8580 xastir_snprintf(weather->wx_prec_24,
8581 sizeof(weather->wx_prec_24),
8584 /* rain since midnight */
8585 xastir_snprintf(weather->wx_prec_00,
8586 sizeof(weather->wx_prec_00),
8592 weather->wx_rain_total[0] = 0;
8597 #endif //INCLUDE_APRS