]> git.itanic.dy.fi Git - maemo-mapper/blob - src/aprs_decode.c
Added basic APRS support - Can be disabled by removing definition of INCLUDE_APRS
[maemo-mapper] / src / aprs_decode.c
1 /*
2  * 
3  * This file is part of Maemo Mapper.
4  *
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.
9  *
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.
14  *
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/>.
17  * 
18  * 
19  * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
20  * 
21  *  * XASTIR, Amateur Station Tracking and Information Reporting
22  * Copyright (C) 1999,2000  Frank Giannandrea
23  * Copyright (C) 2000-2007  The Xastir Group
24  * 
25  */
26
27 #ifdef HAVE_CONFIG_H
28 #    include "config.h"
29 #endif
30
31
32 #ifdef INCLUDE_APRS
33
34 #include "aprs_decode.h"
35 #include "data.h"
36 #include "aprs.h"
37 #include "types.h"
38 #include "math.h"
39 #include <stdlib.h>
40 #include <stdio.h>
41 #include <string.h>
42 #include "hashtable.h"
43 #include "poi.h"
44 #include "display.h"
45 #include "aprs_message.h"
46 //#include "aprs_weather.h"
47
48 #define WX_TYPE 'X'
49
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;
54
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
58
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;
62
63 /////////////
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
68
69
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
79
80 int stations_heard = 0;
81
82 ////////////
83
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
89
90
91 int emergency_distance_check = 1;
92 float emergency_range = 280.0;  // Default is 4hrs @ 70mph distance
93
94
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;
101
102 time_t last_object_check = 0;   // Used to determine when to re-transmit objects/items
103
104 time_t last_emergency_time = 0;
105 char last_emergency_callsign[MAX_CALLSIGN+1];
106 int st_direct_timeout = 60 * 60;        // 60 minutes.
107
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];
112
113 // calculate every half hour, display in status line every 5 minutes
114 #define ALOHA_CALC_INTERVAL 1800 
115 #define ALOHA_STATUS_INTERVAL 300
116
117 int process_emergency_packet_again = 0;
118
119
120 ////////////
121
122 int  altnet;
123 char altnet_call[MAX_CALLSIGN+1];
124 static struct hashtable *tactical_hash = NULL;
125
126
127 char echo_digis[6][MAX_CALLSIGN+1];
128 int  tracked_stations = 0;       // A count variable used in debug code only
129
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;
133
134
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);
140
141 void init_weather(AprsWeatherRow *weather) {    // clear weather data
142
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';
165 }
166
167
168 int tactical_keys_equal(void *key1, void *key2) {
169
170     if (strlen((char *)key1) == strlen((char *)key2)
171             && strncmp((char *)key1,(char *)key2,strlen((char *)key1))==0) {
172         return(1);
173     }
174     else {
175         return(0);
176     }
177 }
178
179
180 // Multiply all the characters in the callsign, truncated to
181 // TACTICAL_HASH_SIZE
182 //
183 unsigned int tactical_hash_from_key(void *key) {
184     unsigned char *jj = key;
185     unsigned int tac_hash = 1;
186
187     while (*jj != '\0') {
188        tac_hash = tac_hash * (unsigned int)*jj++;
189     }
190
191     tac_hash = tac_hash % TACTICAL_HASH_SIZE;
192
193     return (tac_hash);
194 }
195
196
197
198
199 /* valid characters for APRS weather data fields */
200 int is_aprs_chr(char ch) {
201
202     if (g_ascii_isdigit(ch) || ch==' ' || ch=='.' || ch=='-')
203     return(1);
204     else
205     return(0);
206 }
207
208
209
210
211 //
212 // Search station record by callsign
213 // Returns a station with a call equal or after the searched one
214 //
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.
221 //
222 // DK7IN:  I don't look at case, objects and internet names could
223 // have lower case.
224 //
225 int search_station_name(AprsDataRow **p_name, char *call, int exact) {
226     int kk;
227     int hash_key;
228     int result;
229     int ok = 1;
230
231     (*p_name) = n_first;                                // start of alphabet
232
233     if (call[0] == '\0') {
234         // If call we're searching for is empty, return n_first as
235         // the pointer.
236         return(0);
237     }
238
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)
241     //
242  
243     hash_key = (int)((call[0] & 0x7f) << 7);
244     hash_key = hash_key | (int)(call[1] & 0x7f);
245
246     // Look for a match using hash table lookup
247     //
248     (*p_name) = station_shortcuts[hash_key];
249
250     if ((*p_name) == NULL) {    // No hash-table entry found.
251         int mm;
252
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) 
259             {
260                 (*p_name) = station_shortcuts[mm];
261                 
262                 break;
263             }
264         }
265     }
266
267
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).
274
275     kk = (int)strlen(call);
276
277     // Search linearly through list.  Stop at end of list or break.
278     while ( (*p_name) != NULL) {
279
280         if (exact) {
281             // Check entire string for exact match
282             result = strcmp( call, (*p_name)->call_sign );
283         }
284         else {
285             // Check first part of string for match
286             result = strncmp( call, (*p_name)->call_sign, kk );
287         }
288
289         if (result < 0) {   // We went past the right location.
290                             // We're done.
291             ok = 0;
292
293             break;
294         }
295         else if (result == 0) { // Found a possible match
296             break;
297         }
298         else {  // Result > 0.  We haven't found it yet.
299                 
300                 (*p_name) = (*p_name)->n_next;  // Next element in list
301         }
302     }
303
304     // Did we find anything?
305     if ( (*p_name) == NULL) {
306         ok = 0;
307         return(ok); // Nope.  No match found.
308     }
309
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))
313         ok = 0;
314
315     return(ok);         // if not ok: p_name points to correct insert position in name list
316 }
317
318
319
320 /*
321  *  Check if current packet is a delayed echo
322  */
323 int is_trailpoint_echo(AprsDataRow *p_station) {
324     int packets = 1;
325     time_t checktime;
326     AprsTrackRow *ptr;
327
328
329     // Check whether we're to skip checking for dupes (reading in
330     // objects/items from file is one such case).
331     //
332     if (skip_dupe_checking) {
333         return(0);  // Say that it isn't an echo
334     }
335
336     // Start at newest end of linked list and compare.  Return if we're
337     // beyond the checktime.
338     ptr = p_station->newest_trackpoint;
339
340     if (ptr == NULL)
341         return(0);  // first point couldn't be an echo
342
343     checktime = p_station->sec_heard - TRAIL_ECHO_TIME*60;
344
345     while (ptr != NULL) {
346
347         if (ptr->sec < checktime)
348             return(0);  // outside time frame, no echo found
349
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)
361
362             return(1);              // we found a delayed echo
363         }
364         ptr = ptr->prev;
365         packets++;
366     }
367     return(0);                      // no echo found
368 }
369
370
371 /*
372  *  Keep track of last six digis that echo my transmission
373  */
374 void upd_echo(char *path) {
375     int i,j,len;
376
377     if (echo_digis[5][0] != '\0') {
378         for (i=0;i<5;i++) {
379             xastir_snprintf(echo_digis[i],
380                 MAX_CALLSIGN+1,
381                 "%s",
382                 echo_digis[i+1]);
383
384         }
385         echo_digis[5][0] = '\0';
386     }
387     for (i=0,j=0;i < (int)strlen(path);i++) {
388         if (path[i] == '*')
389             break;
390         if (path[i] == ',')
391             j=i;
392     }
393     if (j > 0)
394         j++;                    // first char of call
395     if (i > 0 && i-j <= 9) {
396         len = i-j;
397         for (i=0;i<5;i++) {     // look for free entry
398             if (echo_digis[i][0] == '\0')
399                 break;
400         }
401         substr(echo_digis[i],path+j,len);
402     }
403 }
404
405
406 //
407 // Store one trail point.  Allocate storage for the new data.
408 //
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.
413 //
414 int store_trail_point(AprsDataRow *p_station,
415                       long lon,
416                       long lat,
417                       time_t sec,
418                       char *alt,
419                       char *speed,
420                       char *course,
421                       short stn_flag) {
422 // TODO - trails are currently disabled
423 // This seems to fall over!!!
424         return 1;
425         
426         
427     char flag;
428     AprsTrackRow *ptr;
429
430
431     // Allocate storage for the new track point
432     ptr = malloc(sizeof(AprsTrackRow));
433     if (ptr == NULL) {
434         return(0); // Failed due to malloc
435     }
436  
437     // Check whether we have any track data saved
438     if (p_station->newest_trackpoint == NULL) {
439         // new trail, do initialization
440
441         tracked_stations++;
442
443         // Assign a new trail color 'cuz it's a new trail
444         p_station->trail_color = new_trail_color(p_station->call_sign);
445     }
446
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
450
451     // Have an older record already?
452     if (p_station->newest_trackpoint != NULL) { // Yes
453         p_station->newest_trackpoint->next = ptr;
454     }
455     else {  // No, this is our first record
456         p_station->oldest_trackpoint = ptr;
457     }
458
459     // Link it in as our newest record
460     p_station->newest_trackpoint = ptr;
461
462     ptr->trail_long_pos = lon;
463     ptr->trail_lat_pos  = lat;
464     ptr->sec            = sec;
465
466     if (alt[0] != '\0')
467             ptr->altitude = atoi(alt)*10;
468     else            
469             ptr->altitude = -99999l;
470
471     if (speed[0] != '\0')
472             ptr->speed  = (long)(atof(speed)*18.52);
473     else
474             ptr->speed  = -1;
475
476     if (course[0] != '\0')
477             ptr->course = (int)(atof(course) + 0.5);    // Poor man's rounding
478     else
479             ptr->course = -1;
480
481     flag = '\0';                    // init flags
482
483     if ((stn_flag & ST_DIRECT) != 0)
484             flag |= TR_LOCAL;           // set "local" flag
485
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) ) {
492
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.
497             flag |= TR_NEWTRK;
498         }
499         else {
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)) {
503
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.
507                 flag |= TR_NEWTRK;
508             }
509         }
510         
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;
515         gint trackCount = 0;
516         
517         while( (ptr_tmp = ptr_tmp->prev) )
518         {
519                 if(trackCount > _aprs_std_pos_hist)
520                 {
521                         fprintf(stderr, "DEBUG: Deleting old track point\n");
522                         // Remove track
523                         ptr_tmp->prev->next = ptr_tmp->next;
524                         if(ptr_tmp->next) 
525                         {
526                                 ptr_tmp->next->prev = ptr_tmp->prev;
527                         }
528                         ptr_tmp_previous = ptr_tmp->prev;
529                         free( ptr_tmp);
530                         ptr_tmp = ptr_tmp_previous;
531                         
532                         
533                 }
534                 else
535                 {
536                         trackCount ++;
537                 }
538                 
539                 ptr_tmp = ptr_tmp->prev;
540         }
541         
542     }
543     else {
544         // Set "new track" flag for first point received.
545         flag |= TR_NEWTRK;
546     }
547     ptr->flag = flag;
548
549     return(1);  // We succeeded
550 }
551
552
553
554 /*
555  *  Substring function WITH a terminating NULL char, needs a string of at least size+1
556  */
557 void substr(char *dest, char *src, int size) 
558 {
559     snprintf(dest, size+1, "%s", src);
560 }
561
562
563
564 /*
565  *  Remove element from name ordered list
566  */
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
570
571
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.
575     //
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)
578     //
579     hash_key = (int)((p_rem->call_sign[0] & 0x7f) << 7);
580     hash_key = hash_key | (int)(p_rem->call_sign[1] & 0x7f);
581
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.
586         update_shortcuts++;
587     }
588
589
590     // Proceed to the station record removal
591     //
592     if (p_rem->n_prev == NULL) { // Appears to be first element in list
593
594         if (n_first == p_rem) {  // Yes, head of list
595
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;
599         }
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
603                 // pointers.
604
605             fprintf(stderr,
606                 "remove_name(): ERROR: p->n_prev == NULL but p != n_first\n");
607
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.
613
614         }
615     }
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;
619     }
620
621
622     if (p_rem->n_next == NULL) { // Appears to be last element in list
623
624         if (n_last == p_rem) {   // Yes, tail of list
625
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;
629         }
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
633                 // pointers.
634
635             fprintf(stderr,
636                 "remove_name(): ERROR: p->n_next == NULL but p != n_last\n");
637
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.
643
644         }
645     }
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;
649     }
650
651
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
654     // pointer.
655     if (update_shortcuts) {
656 //fprintf(stderr,"\t\t\t\t\t\tRemoval of hash key: %i\n", hash_key);
657
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
661         // up.
662         station_shortcuts_update_function(-1, NULL);
663     }
664
665
666 }
667
668
669
670 /*
671  *  Station Capabilities, Queries and Responses      [APRS Reference, chapter 15]
672  */
673 //
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.
677 //
678 // NOTE:  We may end up sending these to RF when the query came in
679 // over the internet.  We should check that.
680 //
681 int process_query( /*@unused@*/ char *call_sign, /*@unused@*/ char *path,char *message,TAprsPort port, /*@unused@*/ int third_party) {
682     char temp[100];
683     int ok = 0;
684     float randomize;
685
686
687     // Generate a random number between 0.0 and 1.0
688     randomize = rand() / (float)RAND_MAX;
689
690     // Convert to between 0 and 120 seconds
691     randomize = randomize * 120.0;
692
693     // Check for proper usage of the ?APRS? query
694 //
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
698 // this:
699 //
700 // ?Query?Lat,Long,Radius
701 // 1  n  1 n 1 n  1  4 Bytes
702 //
703 // i.e. ?APRS? 34.02,-117.15,0200
704 //
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.
711 //
712     if (!ok && strncmp(message,"APRS?",5)==0) {
713         //
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.
717         //
718         if ( posit_next_time - sec_now() < randomize ) {
719             // Skip setting it, as we'll transmit soon anyway
720         }
721         else {
722             posit_next_time = (size_t)(sec_now() + randomize);
723         }
724         ok = 1;
725     }
726     // Check for illegal case for the ?APRS? query
727     if (!ok && g_strncasecmp(message,"APRS?",5)==0) {
728         ok = 1;
729 //        fprintf(stderr,
730 //            "%s just queried us with an illegal query: %s\n",
731 //            call_sign,
732 //            message),
733 //        fprintf(stderr,
734 //            "Consider sending a message, asking them to follow the spec\n");
735     }
736
737     // Check for proper usage of the ?WX? query
738     if (!ok && strncmp(message,"WX?",3)==0) {
739         // WX is not supported
740
741         ok = 1;
742     }
743     
744     // Check for illegal case for the ?WX? query
745     if (!ok && g_strncasecmp(message,"WX?",3)==0) {
746         ok = 1;
747 //        fprintf(stderr,
748 //            "%s just queried us with an illegal query: %s\n",
749 //            call_sign,
750 //            message),
751 //        fprintf(stderr,
752 //            "Consider sending a message, asking them to follow the spec\n");
753     }
754
755     return(ok);
756 }
757
758
759
760 /*
761  *  Get new trail color for a call
762  */
763 int new_trail_color(char *call) {
764     // TODO - stub
765
766     return(0);
767 }
768
769
770 /*
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.
775  */
776 void insert_time(AprsDataRow *p_new, AprsDataRow *p_time) {
777         
778         
779     // Set up pointer to next record (or NULL), sorted by time
780     p_new->t_newer = p_time;
781
782     if (p_time == NULL) {               // add to end of list (becomes newest station)
783
784         p_new->t_older = t_newest;         // connect to previous end of list
785
786         if (t_newest == NULL)             // if list empty, create list
787             t_oldest = p_new;            // it's now our only station on the list
788         else
789             t_newest->t_newer = p_new;     // list not empty, link original last record to our new one
790
791         t_newest = p_new;                 // end of list (newest record pointer) points to our new record
792     }
793
794     else {                            // Else we're inserting into the middle of the list somewhere
795
796         p_new->t_older = p_time->t_older;
797
798         if (p_time->t_older == NULL)     // add to end of list (new record becomes oldest station)
799             t_oldest = p_new;
800         else
801             p_time->t_older->t_newer = p_new; // else 
802
803         p_time->t_older = p_new;
804     }
805     
806     // TODO - this may need implementing? 
807     /*
808     if(_aprs_max_stations > 0 && stations_heard > _aprs_max_stations)
809     {
810         // Delete the oldest
811         if(t_oldest != NULL)
812         {
813   //            fprintf(stderr, "DEBUG: oldest station deleted\n");
814                 
815                 delete_station_memory(t_oldest);
816         }
817         else
818         {
819                 stations_heard++;
820         }
821     }
822     else
823     {
824 //      fprintf(stderr, "DEBUG: Stations in memory: %d\n", stations_heard);
825         
826         stations_heard++; // Should really be done in the new station method
827     }
828     */
829 }
830
831
832
833
834 /*
835  *  Remove element from time ordered list
836  */
837 void remove_time(AprsDataRow *p_rem) {      // todo: return pointer to next element
838
839     if (p_rem->t_older == NULL) { // Appears to be first element in list
840
841         if (t_oldest == p_rem) {  // Yes, head of list (oldest)
842
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;
846         }
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.
851
852             fprintf(stderr,
853                 "remove_time(): ERROR: p->t_older == NULL but p != t_oldest\n");
854
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.
860
861         }
862     }
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;
866     }
867
868
869     if (p_rem->t_newer == NULL) { // Appears to be last (newest) element in list
870
871         if (t_newest == p_rem) {   // Yes, head of list (newest)
872
873             // Make newest list head point to previous element in
874             // list (or NULL) so that we can delete the current
875             // record.
876             t_newest = p_rem->t_older;
877         }
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
881                 // pointers.
882
883             fprintf(stderr,
884                 "remove_time(): ERROR: p->t_newer == NULL but p != t_newest\n");
885
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.
891
892         }
893     }
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;
897     }
898     
899     stations_heard--;
900 }
901
902
903
904
905 /*
906  *  Move station record before p_time in time ordered list
907  */
908 void move_station_time(AprsDataRow *p_curr, AprsDataRow *p_time) {
909
910     if (p_curr != NULL) {               // need a valid record
911         remove_time(p_curr);
912         insert_time(p_curr,p_time);
913     }
914 }
915
916
917 /*
918  *  Extract powergain and/or range from APRS info field:
919  * "PHG1234/", "PHG1234", or "RNG1234" from APRS data extension.
920  */
921 int extract_powergain_range(char *info, char *phgd) {
922     int i,found,len;
923     char *info2;
924
925
926 //fprintf(stderr,"Info:%s\n",info);
927
928     // Check whether two strings of interest are present and snag a
929     // pointer to them.
930     info2 = strstr(info,"RNG");
931     if (!info2)
932         info2 = strstr(info,"PHG");
933     if (!info2) {
934         phgd[0] = '\0';
935         return(0);
936     }
937
938     found=0;
939     len = (int)strlen(info2);
940
941     if (len >= 9 && strncmp(info2,"PHG",3)==0
942             && info2[7]=='/'
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);
949         found = 1;
950         for (i=0;i<=len-8;i++)        // delete powergain from data extension field
951             info2[i] = info2[i+8];
952     }
953     else {
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);
960             found = 1;
961             for (i=0;i<=len-7;i++)        // delete powergain from data extension field
962                 info2[i] = info2[i+7];
963         }
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);
970             found = 1;
971             for (i=0;i<=len-7;i++)        // delete powergain from data extension field
972                 info2[i] = info2[i+7];
973         }
974         else {
975             phgd[0] = '\0';
976         }
977     }
978     return(found);
979 }
980
981
982 /*
983  *  Extract omnidf from APRS info field          "DFS1234/"    from APRS data extension
984  */
985 int extract_omnidf(char *info, char *phgd) {
986     int i,found,len;
987
988     found=0;
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])) {
992         substr(phgd,info,7);
993         for (i=0;i<=len-8;i++)        // delete omnidf from data extension field
994             info[i] = info[i+8];
995         return(1);
996     }
997     else {
998         phgd[0] = '\0';
999         return(0);
1000     }
1001 }
1002
1003
1004 //
1005 //  Extract speed and/or course from beginning of info field
1006 //
1007 // Returns course in degrees, speed in KNOTS.
1008 //
1009 int extract_speed_course(char *info, char *speed, char *course) {
1010     int i,found,len;
1011
1012     len = (int)strlen(info);
1013     found = 0;
1014     if (len >= 7) {
1015         found = 1;
1016         for(i=0; found && i<7; i++) {           // check data format
1017             if (i==3) {                         // check separator
1018                 if (info[i]!='/')
1019                     found = 0;
1020             }
1021             else {
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
1025                     found = 0;
1026             }
1027         }
1028     }
1029     if (found) {
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];
1034     }
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.
1038         course[0]='\0';
1039     }
1040     else {  // recheck data format looking for undefined fields
1041         for(i=0; i<2; i++) {
1042             if( !(g_ascii_isdigit(speed[i]) ) )
1043                 speed[0] = '\0';
1044             if( !(g_ascii_isdigit(course[i]) ) )
1045                 course[0] = '\0';
1046         }
1047     }
1048
1049     return(found);
1050 }
1051
1052
1053
1054
1055
1056 /*
1057  *  Extract Area Object
1058  */
1059 void extract_area(AprsDataRow *p_station, char *data) {
1060     int i, val, len;
1061     unsigned int uval;
1062     AprsAreaObject temp_area;
1063
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 */
1069
1070     //fprintf(stderr,"Area Data: %s\n", data);
1071
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;
1080             }
1081             else {
1082                 return;
1083             }
1084         }
1085         else if (data[3] == '1') {
1086             if (val >=0 && val <= 5) {
1087                 temp_area.color = 10 + val;
1088             }
1089             else {
1090                 return;
1091             }
1092         }
1093
1094         val = 0;
1095         if (isdigit((int)data[1]) && isdigit((int)data[2])) {
1096             val = (10 * (data[1] - '0')) + (data[2] - '0');
1097         }
1098         else {
1099             return;
1100         }
1101         temp_area.sqrt_lat_off = val;
1102
1103         val = 0;
1104         if (isdigit((int)data[5]) && isdigit((int)data[6])) {
1105             val = (10 * (data[5] - '0')) + (data[6] - '0');
1106         }
1107         else {
1108             return;
1109         }
1110         temp_area.sqrt_lon_off = val;
1111
1112         for (i = 0; i <= len-7; i++) // delete area object from data extension field
1113             data[i] = data[i+7];
1114         len -= 7;
1115
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++)
1121                         if (data[i] == '}')
1122                             break;
1123                     uval = i+1;
1124                     for (i = 0; i <= (int)(len-uval); i++)
1125                         data[i] = data[i+uval]; // delete corridor width
1126                 }
1127                 else {
1128                     temp_area.corridor_width = 0;
1129                     return;
1130                 }
1131             }
1132             else {
1133                 temp_area.corridor_width = 0;
1134             }
1135         }
1136         else {
1137             temp_area.corridor_width = 0;
1138         }
1139     }
1140     else {
1141         return;
1142     }
1143
1144     memcpy(&(p_station->aprs_symbol.area_object), &temp_area, sizeof(AprsAreaObject));
1145
1146 }
1147
1148
1149
1150 /*
1151  *  Extract probability_max data from APRS info field: "Pmax1.23,"
1152  *  Please note the ending comma.  We use it to delimit the field.
1153  */
1154 int extract_probability_max(char *info, char *prob_max, int prob_max_size) {
1155     int len,done;
1156     char *c;
1157     char *d;
1158
1159
1160
1161     len = (int)strlen(info);
1162     if (len < 6) {          // Too short
1163         prob_max[0] = '\0';
1164         return(0);
1165     }
1166
1167     c = strstr(info,"Pmax");
1168     if (c == NULL) {        // Pmax not found
1169         prob_max[0] = '\0';
1170         return(0);
1171     }
1172
1173     c = c+4;    // Skip the Pmax part
1174     // Find the ending comma
1175     d = c;
1176     done = 0;
1177     while (!done) {
1178         if (*d == ',') {    // We're done
1179             done++;
1180         }
1181         else {
1182             d++;
1183         }
1184
1185         // Check for string too long
1186         if ( ((d-c) > 10) && !done) {    // Something is wrong, we should be done by now
1187             prob_max[0] = '\0';
1188             return(0);
1189         }
1190     }
1191
1192     // Copy the substring across
1193     snprintf(prob_max,
1194         prob_max_size,
1195         "%s",
1196         c);
1197     prob_max[d-c] = '\0';
1198     prob_max[10] = '\0';    // Just to make sure
1199
1200     // Delete data from data extension field 
1201     d++;    // Skip the comma
1202     done = 0;
1203     while (!done) {
1204         *(c-4) = *d;
1205         if (*d == '\0')
1206             done++;
1207         c++;
1208         d++;
1209     }
1210  
1211     return(1);
1212 }
1213
1214
1215
1216
1217 /*
1218  *  Extract probability_min data from APRS info field: "Pmin1.23,"
1219  *  Please note the ending comma.  We use it to delimit the field.
1220  */
1221 int extract_probability_min(char *info, char *prob_min, int prob_min_size) {
1222     int len,done;
1223     char *c;
1224     char *d;
1225
1226  
1227     len = (int)strlen(info);
1228     if (len < 6) {          // Too short
1229         prob_min[0] = '\0';
1230         return(0);
1231     }
1232
1233     c = strstr(info,"Pmin");
1234     if (c == NULL) {        // Pmin not found
1235         prob_min[0] = '\0';
1236         return(0);
1237     }
1238
1239     c = c+4;    // Skip the Pmin part
1240     // Find the ending comma
1241     d = c;
1242     done = 0;
1243     while (!done) {
1244         if (*d == ',') {    // We're done
1245             done++;
1246         }
1247         else {
1248             d++;
1249         }
1250
1251         // Check for string too long
1252         if ( ((d-c) > 10) && !done) {    // Something is wrong, we should be done by now
1253             prob_min[0] = '\0';
1254             return(0);
1255         }
1256     }
1257
1258     // Copy the substring across
1259     xastir_snprintf(prob_min,
1260         prob_min_size,
1261         "%s",
1262         c);
1263     prob_min[d-c] = '\0';
1264     prob_min[10] = '\0';    // Just to make sure
1265
1266     // Delete data from data extension field 
1267     d++;    // Skip the comma
1268     done = 0;
1269     while (!done) {
1270         *(c-4) = *d;
1271         if (*d == '\0')
1272             done++;
1273         c++;
1274         d++;
1275     }
1276
1277     return(1);
1278 }
1279
1280
1281
1282
1283 /*
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.
1286  */
1287 int extract_signpost(char *info, char *signpost) {
1288     int i,found,len,done;
1289
1290 //0123456
1291 //{1}
1292 //{12}
1293 //{121}
1294
1295     found=0;
1296     len = (int)strlen(info);
1297     if ( (len > 2)
1298             && (info[0] == '{')
1299             && ( (info[2] == '}' ) || (info[3] == '}' ) || (info[4] == '}' ) ) ) {
1300
1301         i = 1;
1302         done = 0;
1303         while (!done) {                 // Snag up to three digits
1304             if (info[i] == '}') {       // We're done
1305                 found = i;              // found = position of '}' character
1306                 done++;
1307             }
1308             else {
1309                 signpost[i-1] = info[i];
1310             }
1311
1312             i++;
1313
1314             if ( (i > 4) && !done) {    // Something is wrong, we should be done by now
1315                 done++;
1316                 signpost[0] = '\0';
1317                 return(0);
1318             }
1319         }
1320         substr(signpost,info+1,found-1);
1321         found++;
1322         for (i=0;i<=len-found;i++) {    // delete omnidf from data extension field
1323             info[i] = info[i+found];
1324         }
1325         return(1);
1326     }
1327     else {
1328         signpost[0] = '\0';
1329         return(0);
1330     }
1331 }
1332
1333
1334
1335 // is_altnet()
1336 //
1337 // Returns true if station fits the current altnet description.
1338 //
1339 int is_altnet(AprsDataRow *p_station) {
1340     char temp_altnet_call[20+1];
1341     char temp2[20+1];
1342     char *net_ptr;
1343     int  altnet_match;
1344     int  result;
1345
1346
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);
1350     else
1351         temp_altnet_call[0] = '\0';
1352
1353     // Save for later
1354     snprintf(temp2,
1355         sizeof(temp2),
1356         "%s",
1357         temp_altnet_call);
1358
1359     if ((net_ptr = strchr(temp_altnet_call, ',')))
1360         *net_ptr = '\0';    // Chop the string at the first ',' character
1361
1362     for (altnet_match = (int)strlen(altnet_call); altnet && altnet_call[altnet_match-1] == '*'; altnet_match--);
1363
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
1369
1370     return(result);
1371 }
1372
1373
1374
1375 int is_num_or_sp(char ch) 
1376 {
1377     return((int)((ch >= '0' && ch <= '9') || ch == ' '));
1378 }
1379
1380
1381 char *get_time(char *time_here) {
1382     struct tm *time_now;
1383     time_t timenw;
1384
1385     (void)time(&timenw);
1386     time_now = localtime(&timenw);
1387     (void)strftime(time_here,MAX_TIME,"%m%d%Y%H%M%S",time_now);
1388     return(time_here);
1389 }
1390
1391
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;
1398 }
1399
1400
1401
1402
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
1405 // '0-9'.
1406 void overlay_symbol(char symbol, char data, AprsDataRow *fill) {
1407
1408     if ( data != '/' && data !='\\') {  // Symbol overlay
1409
1410         if (data >= 'a' && data <= 'j') {
1411             // Found a compressed posit numerical overlay
1412             data = data - 'a'+'0';  // Convert to a digit
1413         }
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;
1419         }
1420         else {
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';
1425         }
1426     }
1427     else {    // No overlay character
1428         fill->aprs_symbol.aprs_type = data;
1429         fill->aprs_symbol.special_overlay='\0';
1430     }
1431     fill->aprs_symbol.aprs_symbol = symbol;
1432 }
1433
1434
1435 /*
1436  *  Extract Time from begin of line      [APRS Reference, chapter 6]
1437  *
1438  * If a time string is found in "data", it is deleted from the
1439  * beginning of the string.
1440  */
1441 int extract_time(AprsDataRow *p_station, char *data, int type) {
1442     int len, i;
1443     int ok = 0;
1444
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...
1449         if (len > 8) {
1450             // MMDDHHMM   zulu time
1451             // MM 01-12         todo: better check of time data ranges
1452             // DD 01-31
1453             // HH 01-23
1454             // MM 01-59
1455             ok = 1;
1456             for (i=0;ok && i<8;i++)
1457                 if (!isdigit((int)data[i]))
1458                     ok = 0;
1459             if (ok) {
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];
1464             }
1465         }
1466     }
1467     else {
1468         if (len > 6) {
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')
1472                 ok = 1;
1473             for (i=0;ok && i<6;i++)
1474                 if (!isdigit((int)data[i]))
1475                     ok = 0;
1476             if (ok) {
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];
1481             }
1482         }
1483     }
1484     return(ok);
1485 }
1486
1487
1488
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).
1494 //
1495 void split_string( char *data, char *cptr[], int max ) {
1496   int ii;
1497   char *temp;
1498   char *current = data;
1499
1500
1501   // NULL each char pointer
1502   for (ii = 0; ii < max; ii++) {
1503     cptr[ii] = NULL;
1504   }
1505
1506   // Save the beginning substring address
1507   cptr[0] = current;
1508
1509   for (ii = 1; ii < max; ii++) {
1510     temp = strchr(current,',');  // Find next comma
1511
1512     if(!temp) { // No commas found 
1513       return; // All done with string
1514     }
1515
1516     // Store pointer to next substring in array
1517     cptr[ii] = &temp[1];
1518     current  = &temp[1];
1519
1520     // Overwrite comma with end-of-string char and bump pointer by
1521     // one.
1522     temp[0] = '\0';
1523   }
1524 }
1525
1526
1527
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.
1532 //
1533 int is_weather_data(char *data, int len) {
1534     int ok = 1;
1535     int i;
1536     int count = 0;
1537
1538     for (i=0;ok && i<len;i++)
1539         if (!is_aprs_chr(data[i]))
1540             ok = 0;
1541
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.
1545     //
1546     for (i=0;ok && i<len;i++) {
1547         if (data[i] == ' ' || data[i] == '.') {
1548             count++;
1549         }
1550     }
1551     if (count != 0 && count != len) {
1552         ok = 0;
1553     }
1554
1555     return(ok);
1556 }
1557
1558
1559
1560
1561
1562
1563 /* convert latitude from string to long with 1/100 sec resolution */
1564 //
1565 // Input is in [D]DMM.MM[MM]N format (degrees/decimal
1566 // minutes/direction)
1567 //
1568 long convert_lat_s2l(char *lat) {      /* N=0°, Ctr=90°, S=180° */
1569     long centi_sec;
1570     char copy[15];
1571     char n[15];
1572     char *p;
1573     char offset;
1574
1575
1576     // Find the decimal point if present
1577     p = strstr(lat, ".");
1578
1579     if (p == NULL)  // No decimal point found
1580         return(0l);
1581
1582     offset = p - lat;   // Arithmetic on pointers
1583     switch (offset) {
1584         case 0:     // .MM[MM]N
1585             return(0l); // Bad, no degrees or minutes
1586             break;
1587         case 1:     // M.MM[MM]N
1588             return(0l); // Bad, no degrees
1589             break;
1590         case 2:     // MM.MM[MM]N
1591             return(0l); // Bad, no degrees
1592             break;
1593         case 3:     // DMM.MM[MM]N
1594             xastir_snprintf(copy,
1595                 sizeof(copy),
1596                 "0%s",  // Add a leading '0'
1597                 lat);
1598             break;
1599         case 4:     // DDMM.MM[MM]N
1600             xastir_snprintf(copy,
1601                 sizeof(copy),
1602                 "%s",   // Copy verbatim
1603                 lat);
1604             break;
1605         default:
1606             break;
1607     }
1608
1609     copy[14] = '\0';
1610     centi_sec=0l;
1611     if (copy[4]=='.'
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')) {
1626
1627         substr(n, copy, 2);       // degrees
1628         centi_sec=atoi(n)*60*60*100;
1629
1630         substr(n, copy+2, 2);     // minutes
1631         centi_sec += atoi(n)*60*100;
1632
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.
1637  
1638         // Extend the digits to full precision by adding zeroes on
1639         // the end.
1640         strncat(n, "0000", sizeof(n) - strlen(n));
1641
1642         // Get rid of the N/S character
1643         if (!isdigit((int)n[2]))
1644             n[2] = '0';
1645         if (!isdigit((int)n[3]))
1646             n[3] = '0';
1647
1648         // Terminate substring at the correct digit
1649         n[4] = '\0';
1650 //fprintf(stderr,"Lat: %s\n", n);
1651
1652         // Add 0.5 (Poor man's rounding)
1653         centi_sec += (long)((atoi(n) * 0.6) + 0.5);
1654
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;
1663         }
1664
1665         centi_sec += 90*60*60*100;
1666     }
1667     return(centi_sec);
1668 }
1669
1670
1671
1672
1673
1674 /* convert longitude from string to long with 1/100 sec resolution */
1675 //
1676 // Input is in [DD]DMM.MM[MM]W format (degrees/decimal
1677 // minutes/direction).
1678 //
1679 long convert_lon_s2l(char *lon) {     /* W=0°, Ctr=180°, E=360° */
1680     long centi_sec;
1681     char copy[16];
1682     char n[16];
1683     char *p;
1684     char offset;
1685
1686
1687     // Find the decimal point if present
1688     p = strstr(lon, ".");
1689
1690     if (p == NULL)  // No decimal point found
1691         return(0l);
1692
1693     offset = p - lon;   // Arithmetic on pointers
1694     switch (offset) {
1695         case 0:     // .MM[MM]N
1696             return(0l); // Bad, no degrees or minutes
1697             break;
1698         case 1:     // M.MM[MM]N
1699             return(0l); // Bad, no degrees
1700             break;
1701         case 2:     // MM.MM[MM]N
1702             return(0l); // Bad, no degrees
1703             break;
1704         case 3:     // DMM.MM[MM]N
1705             xastir_snprintf(copy,
1706                 sizeof(copy),
1707                 "00%s",  // Add two leading zeroes
1708                 lon);
1709             break;
1710         case 4:     // DDMM.MM[MM]N
1711             xastir_snprintf(copy,
1712                 sizeof(copy),
1713                 "0%s",   // Add leading '0'
1714                 lon);
1715             break;
1716         case 5:     // DDDMM.MM[MM]N
1717             xastir_snprintf(copy,
1718                 sizeof(copy),
1719                 "%s",   // Copy verbatim
1720                 lon);
1721             break;
1722         default:
1723             break;
1724     }
1725
1726     copy[15] = '\0';
1727     centi_sec=0l;
1728     if (copy[5]=='.'
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')) {
1743
1744         substr(n,copy,3);    // degrees 013
1745         centi_sec=atoi(n)*60*60*100;
1746
1747         substr(n,copy+3,2);  // minutes 26
1748         centi_sec += atoi(n)*60*100;
1749         // 01326.66E  01326.660E
1750
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.
1755  
1756         // Extend the digits to full precision by adding zeroes on
1757         // the end.
1758         strncat(n, "0000", sizeof(n) - strlen(n));
1759
1760         // Get rid of the E/W character
1761         if (!isdigit((int)n[2]))
1762             n[2] = '0';
1763         if (!isdigit((int)n[3]))
1764             n[3] = '0';
1765
1766         n[4] = '\0';    // Make sure substring is terminated
1767 //fprintf(stderr,"Lon: %s\n", n);
1768
1769         // Add 0.5 (Poor man's rounding)
1770         centi_sec += (long)((atoi(n) * 0.6) + 0.5);
1771
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;
1780         }
1781
1782         centi_sec +=180*60*60*100;;
1783     }
1784     return(centi_sec);
1785 }
1786
1787
1788
1789 APRS_Symbol *id_callsign(char *call_sign, char * to_call) {
1790     char *ptr;
1791     char *id = "/aUfbYX's><OjRkv";
1792     char hold[MAX_CALLSIGN+1];
1793     int index;
1794     static APRS_Symbol symbol;
1795
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];
1803
1804     if (strncmp(to_call, "GPS", 3) == 0 || strncmp(to_call, "SPC", 3) == 0 || strncmp(to_call, "SYM", 3) == 0) 
1805     {
1806         substr(hold, to_call+3, 3);
1807         if ((ptr = strpbrk(hold, "->,")) != NULL)
1808             *ptr = '\0';
1809
1810         if (strlen(hold) >= 2) {
1811             switch (hold[0]) {
1812                 case 'A':
1813                     symbol.aprs_type = '\\';
1814
1815                 case 'P':
1816                     if (('0' <= hold[1] && hold[1] <= '9') || ('A' <= hold[1] && hold[1] <= 'Z'))
1817                         symbol.aprs_symbol = hold[1];
1818
1819                     break;
1820
1821                 case 'O':
1822                     symbol.aprs_type = '\\';
1823
1824                 case 'B':
1825                     switch (hold[1]) {
1826                         case 'B':
1827                             symbol.aprs_symbol = '!';
1828                             break;
1829                         case 'C':
1830                             symbol.aprs_symbol = '"';
1831                             break;
1832                         case 'D':
1833                             symbol.aprs_symbol = '#';
1834                             break;
1835                         case 'E':
1836                             symbol.aprs_symbol = '$';
1837                             break;
1838                         case 'F':
1839                             symbol.aprs_symbol = '%';
1840                             break;
1841                         case 'G':
1842                             symbol.aprs_symbol = '&';
1843                             break;
1844                         case 'H':
1845                             symbol.aprs_symbol = '\'';
1846                             break;
1847                         case 'I':
1848                             symbol.aprs_symbol = '(';
1849                             break;
1850                         case 'J':
1851                             symbol.aprs_symbol = ')';
1852                             break;
1853                         case 'K':
1854                             symbol.aprs_symbol = '*';
1855                             break;
1856                         case 'L':
1857                             symbol.aprs_symbol = '+';
1858                             break;
1859                         case 'M':
1860                             symbol.aprs_symbol = ',';
1861                             break;
1862                         case 'N':
1863                             symbol.aprs_symbol = '-';
1864                             break;
1865                         case 'O':
1866                             symbol.aprs_symbol = '.';
1867                             break;
1868                         case 'P':
1869                             symbol.aprs_symbol = '/';
1870                             break;
1871                     }
1872                     break;
1873
1874                 case 'D':
1875                     symbol.aprs_type = '\\';
1876
1877                 case 'H':
1878                     switch (hold[1]) {
1879                         case 'S':
1880                             symbol.aprs_symbol = '[';
1881                             break;
1882                         case 'T':
1883                             symbol.aprs_symbol = '\\';
1884                             break;
1885                         case 'U':
1886                             symbol.aprs_symbol = ']';
1887                             break;
1888                         case 'V':
1889                             symbol.aprs_symbol = '^';
1890                             break;
1891                         case 'W':
1892                             symbol.aprs_symbol = '_';
1893                             break;
1894                         case 'X':
1895                             symbol.aprs_symbol = '`';
1896                             break;
1897                     }
1898                     break;
1899
1900                 case 'N':
1901                     symbol.aprs_type = '\\';
1902
1903                 case 'M':
1904                     switch (hold[1]) {
1905                         case 'R':
1906                             symbol.aprs_symbol = ':';
1907                             break;
1908                         case 'S':
1909                             symbol.aprs_symbol = ';';
1910                             break;
1911                         case 'T':
1912                             symbol.aprs_symbol = '<';
1913                             break;
1914                         case 'U':
1915                             symbol.aprs_symbol = '=';
1916                             break;
1917                         case 'V':
1918                             symbol.aprs_symbol = '>';
1919                             break;
1920                         case 'W':
1921                             symbol.aprs_symbol = '?';
1922                             break;
1923                         case 'X':
1924                             symbol.aprs_symbol = '@';
1925                             break;
1926                     }
1927                     break;
1928
1929                 case 'Q':
1930                     symbol.aprs_type = '\\';
1931
1932                 case 'J':
1933                     switch (hold[1]) {
1934                         case '1':
1935                             symbol.aprs_symbol = '{';
1936                             break;
1937                         case '2':
1938                             symbol.aprs_symbol = '|';
1939                             break;
1940                         case '3':
1941                             symbol.aprs_symbol = '}';
1942                             break;
1943                         case '4':
1944                             symbol.aprs_symbol = '~';
1945                             break;
1946                     }
1947                     break;
1948
1949                 case 'S':
1950                     symbol.aprs_type = '\\';
1951
1952                 case 'L':
1953                     if ('A' <= hold[1] && hold[1] <= 'Z')
1954                         symbol.aprs_symbol = tolower((int)hold[1]);
1955
1956                     break;
1957             }
1958             if (hold[2]) {
1959                 if (hold[2] >= 'a' && hold[2] <= 'j') {
1960                     // Compressed mode numeric overlay
1961                     symbol.special_overlay = hold[2] - 'a';
1962                 }
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];
1967                 }
1968                 else {
1969                     // Bad overlay character found
1970                     symbol.special_overlay = '\0';
1971                 }
1972             }
1973             else {
1974                 // No overlay character found
1975                 symbol.special_overlay = '\0';
1976             }
1977         }
1978     }
1979     return(&symbol);
1980 }
1981
1982
1983
1984 /*
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)
1988  */
1989
1990 int position_defined(long lat, long lon, int strict) {
1991
1992     if (lat == 0l && lon == 0l)
1993         return(0);              // undefined location
1994 #ifndef ACCEPT_0N_0E
1995     if (strict)
1996 #endif  // ACCEPT_0N_0E
1997         if (lat == 90*60*60*100l && lon == 180*60*60*100l)      // 0N/0E
1998             return(0);          // undefined location
1999     return(1);
2000 }
2001
2002
2003 //
2004 //  Extract data for $GPRMC, it fails if there is no position!!
2005 //
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]
2008 //
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.
2014 // Fix-Quality:
2015 //  A: Autonomous
2016 //  D: Differential
2017 //  E: Estimated
2018 //  N: Not Valid
2019 //  S: Simulator
2020 //
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
2024 //
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, ...  ???
2027     char lat_s[20];
2028     char long_s[20];
2029     int ok;
2030     char *Substring[12];  // Pointers to substrings parsed by split_string()
2031     char temp_string[MAX_MESSAGE_LENGTH+1];
2032     char temp_char;
2033
2034
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.
2037
2038     if ( (data == NULL) || (strlen(data) < 34) ) {  // Not enough data to parse position from.
2039         return(ok);
2040     }
2041
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),
2047         "%s",
2048         get_time(temp_data));
2049     p_station->flag &= (~ST_MSGCAP);    // clear "message capable" flag
2050
2051     /* check aprs type on call sign */
2052     p_station->aprs_symbol = *id_callsign(call_sign, path);
2053
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),
2058         "%s",
2059         data);
2060     split_string(temp_string, Substring, 12);
2061
2062     // The Substring[] array contains pointers to each substring in
2063     // the original data string.
2064
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
2067
2068     if (Substring[0] == NULL)   // No GPRMC string
2069         return(ok);
2070
2071     if (Substring[1] == NULL)   // No time string
2072         return(ok);
2073
2074     if (Substring[2] == NULL)   // No valid fix char
2075         return(ok);
2076
2077     if (Substring[2][0] != 'A' && Substring[2][0] != 'V')
2078         return(ok);
2079 // V is a warning but we can get good data still ?
2080 // DK7IN: got no position with 'V' !
2081
2082     if (Substring[3] == NULL)   // No latitude string
2083         return(ok);
2084
2085     if (Substring[4] == NULL)   // No latitude N/S
2086         return(ok);
2087
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 ','
2090
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;
2094     }
2095     else {
2096         *num_digits = 0;
2097     }
2098
2099     temp_char = toupper((int)Substring[4][0]);
2100
2101     if (temp_char != 'N' && temp_char != 'S')   // Bad N/S
2102         return(ok);
2103
2104     xastir_snprintf(lat_s,
2105         sizeof(lat_s),
2106         "%s%c",
2107         Substring[3],
2108         temp_char);
2109
2110     if (Substring[5] == NULL)   // No longitude string
2111         return(ok);
2112
2113     if (Substring[6] == NULL)   // No longitude E/W
2114         return(ok);
2115
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 ','
2118
2119     temp_char = toupper((int)Substring[6][0]);
2120
2121     if (temp_char != 'E' && temp_char != 'W')   // Bad E/W
2122         return(ok);
2123
2124     xastir_snprintf(long_s,
2125         sizeof(long_s),
2126         "%s%c",
2127         Substring[5],
2128         temp_char);
2129
2130     p_station->coord_lat = convert_lat_s2l(lat_s);
2131     p_station->coord_lon = convert_lon_s2l(long_s);
2132
2133     // If we've made it this far, We have enough for a position now!
2134     ok = 1;
2135
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.
2140
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
2143
2144     if (Substring[7] == NULL) { // No speed string
2145         p_station->speed[0] = '\0'; // No speed available
2146         return(ok);
2147     }
2148     else {
2149         xastir_snprintf(p_station->speed,
2150             MAX_SPEED,
2151             "%s",
2152             Substring[7]);
2153         // Is it always knots, otherwise we need a conversion!
2154     }
2155
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
2160         return(ok);
2161     }
2162     else {
2163         xastir_snprintf(p_station->course,
2164             MAX_COURSE,
2165             "%s",
2166             Substring[8]);
2167     }
2168
2169     return(ok);
2170 }
2171
2172
2173 //
2174 //  Extract data for $GPGLL
2175 //
2176 // $GPGLL,4748.811,N,12219.564,W,033850,A*3C
2177 // lat, long, UTCtime in hhmmss, A=Valid, checksum
2178 //
2179 // GPGLL,4748.811,N,12219.564,W,033850,A*3C
2180 //   0       1    2      3    4    5   6
2181 //
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, ...  ???
2184     char lat_s[20];
2185     char long_s[20];
2186     int ok;
2187     char *Substring[7];  // Pointers to substrings parsed by split_string()
2188     char temp_string[MAX_MESSAGE_LENGTH+1];
2189     char temp_char;
2190
2191
2192     ok = 0; // Start out as invalid.  If we get enough info, we change this to a 1.
2193   
2194     if ( (data == NULL) || (strlen(data) < 28) )  // Not enough data to parse position from.
2195         return(ok);
2196
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),
2202         "%s",
2203         get_time(temp_data));
2204     p_station->flag &= (~ST_MSGCAP);    // clear "message capable" flag
2205
2206     /* check aprs type on call sign */
2207     p_station->aprs_symbol = *id_callsign(call_sign, path);
2208
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),
2213         "%s",
2214         data);
2215     split_string(temp_string, Substring, 7);
2216
2217     // The Substring[] array contains pointers to each substring in
2218     // the original data string.
2219
2220     if (Substring[0] == NULL)  // No GPGGA string
2221         return(ok);
2222
2223     if (Substring[1] == NULL)  // No latitude string
2224         return(ok);
2225
2226     if (Substring[2] == NULL)   // No N/S string
2227         return(ok);
2228
2229     if (Substring[3] == NULL)   // No longitude string
2230         return(ok);
2231
2232     if (Substring[4] == NULL)   // No E/W string
2233         return(ok);
2234
2235     temp_char = toupper((int)Substring[2][0]);
2236     if (temp_char != 'N' && temp_char != 'S')
2237         return(ok);
2238
2239     xastir_snprintf(lat_s,
2240         sizeof(lat_s),
2241         "%s%c",
2242         Substring[1],
2243         temp_char);
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 ','
2246
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;
2250     }
2251     else {
2252         *num_digits = 0;
2253     }
2254
2255     temp_char = toupper((int)Substring[4][0]);
2256     if (temp_char != 'E' && temp_char != 'W')
2257         return(ok);
2258
2259     xastir_snprintf(long_s,
2260         sizeof(long_s),
2261         "%s%c",
2262         Substring[3],
2263         temp_char);
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 ','
2266
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
2270
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
2275
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.
2278
2279     return(ok);
2280 }
2281
2282
2283
2284 //
2285 //  Extract data for $GPGGA
2286 //
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]
2289 //
2290 // GPS-Quality:
2291 //  0: Invalid Fix
2292 //  1: GPS Fix
2293 //  2: DGPS Fix
2294 //  3: PPS Fix
2295 //  4: RTK Fix
2296 //  5: Float RTK Fix
2297 //  6: Estimated (dead-reckoning) Fix
2298 //  7: Manual Input Mode
2299 //  8: Simulation Mode
2300 //
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
2303 //
2304 // nsat=Number of Satellites being tracked
2305 //
2306 //
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, ...  ???
2309     char lat_s[20];
2310     char long_s[20];
2311     int  ok;
2312     char *Substring[15];  // Pointers to substrings parsed by split_string()
2313     char temp_string[MAX_MESSAGE_LENGTH+1];
2314     char temp_char;
2315     int  temp_num;
2316
2317
2318     ok = 0; // Start out as invalid.  If we get enough info, we change this to a 1.
2319  
2320     if ( (data == NULL) || (strlen(data) < 32) )  // Not enough data to parse position from.
2321         return(ok);
2322
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),
2328         "%s",
2329         get_time(temp_data));
2330     p_station->flag &= (~ST_MSGCAP);    // clear "message capable" flag
2331
2332     /* check aprs type on call sign */
2333     p_station->aprs_symbol = *id_callsign(call_sign, path);
2334
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),
2339         "%s",
2340         data);
2341     split_string(temp_string, Substring, 15);
2342
2343     // The Substring[] array contains pointers to each substring in
2344     // the original data string.
2345
2346
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
2349 //                                                                                          0     1     2    3        4
2350
2351     if (Substring[0] == NULL)  // No GPGGA string
2352         return(ok);
2353
2354     if (Substring[1] == NULL)  // No time string
2355         return(ok);
2356
2357     if (Substring[2] == NULL)   // No latitude string
2358         return(ok);
2359
2360     if (Substring[3] == NULL)   // No latitude N/S
2361         return(ok);
2362
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 ','
2365
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;
2369     }
2370     else {
2371         *num_digits = 0;
2372     }
2373
2374     temp_char = toupper((int)Substring[3][0]);
2375
2376     if (temp_char != 'N' && temp_char != 'S')   // Bad N/S
2377         return(ok);
2378
2379     xastir_snprintf(lat_s,
2380         sizeof(lat_s),
2381         "%s%c",
2382         Substring[2],
2383         temp_char);
2384
2385     if (Substring[4] == NULL)   // No longitude string
2386         return(ok);
2387
2388     if (Substring[5] == NULL)   // No longitude E/W
2389         return(ok);
2390
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 ','
2393
2394     temp_char = toupper((int)Substring[5][0]);
2395
2396     if (temp_char != 'E' && temp_char != 'W')   // Bad E/W
2397         return(ok);
2398
2399     xastir_snprintf(long_s,
2400         sizeof(long_s),
2401         "%s%c",
2402         Substring[4],
2403         temp_char);
2404
2405     p_station->coord_lat = convert_lat_s2l(lat_s);
2406     p_station->coord_lon = convert_lon_s2l(long_s);
2407
2408     // If we've made it this far, We have enough for a position now!
2409     ok = 1;
2410
2411
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.
2416
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
2426     }
2427
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
2432     }
2433     else {
2434         // Store 
2435         xastir_snprintf(p_station->sats_visible,
2436             sizeof(p_station->sats_visible),
2437             "%d",
2438             temp_num);
2439     }
2440
2441
2442 // Check for valid number for HDOP instead of just throwing it away?
2443
2444
2445     xastir_snprintf(p_station->altitude,
2446         sizeof(p_station->altitude),
2447         "%s",
2448         Substring[9]); // Get altitude
2449
2450 // Need to check for valid altitude before conversion
2451
2452     // unit is in meters, if not adjust value ???
2453
2454     if (Substring[10] == NULL)  // No units for altitude
2455         return(ok);
2456
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';
2461     }
2462
2463     return(ok);
2464 }
2465
2466
2467
2468 static void extract_multipoints(AprsDataRow *p_station,
2469         char *data,
2470         int type,
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).
2476
2477     char *p, *p2;
2478     int found = 0;
2479     char *end;
2480     int data_size;
2481
2482
2483     if (data == NULL) {
2484         return;
2485     }
2486
2487 //fprintf(stderr,"Data: %s\t\t", data);
2488
2489     data_size = strlen(data);
2490
2491     end = data + (strlen(data) - 7);  // 7 == 3 lead-in chars, plus 2 points
2492  
2493     p_station->num_multipoints = 0;
2494
2495     /*
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) {
2499             found = 1;
2500         }
2501     }
2502     */
2503
2504     // Start looking at the beginning of the data.
2505
2506     p = data;
2507
2508     // Look for the opening string.
2509
2510     while (!found && p < end && (p = strstr(p, START_STR)) != NULL) {
2511         // The opening string was found. Check the following information.
2512
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!
2515
2516             found = 1;
2517         }
2518         else {
2519             // The following characters are not right. Advance and
2520             // look again.
2521
2522             ++p;
2523         }
2524     }
2525
2526     if (found) {
2527         long multiplier;
2528         double d;
2529         char *m_start = p;    // Start of multipoint string
2530         char ok = 1;
2531  
2532         // The second character (the lowercase) indicates additional style information,
2533         // such as color, line type, etc.
2534
2535         p_station->style = *(p+2);
2536
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,
2539         // etc.
2540
2541         p_station->type = *(p+3);
2542
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.
2546         //
2547         // Use the following formula to convert the char to the value:
2548         // (10 ^ ((c - 33) / 20)) / 10000 degrees
2549         //
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
2553         // Xastir units.
2554
2555         p = p + 4;
2556
2557         if (*p < '!' || *p > 'z') {
2558             fprintf(stderr,"extract_multipoints: invalid scale character %d\n", *p);
2559             ok = 0; // Failure
2560         }
2561         else {
2562
2563             d = (double)(*p);
2564             d = pow(10.0, ((d - 33) / 20)) / 10000.0 * 360000.0;
2565             multiplier = (long)d;
2566  
2567             ++p;
2568
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.
2573
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.
2578
2579                 int lat_val = *p - 78;
2580                 int lon_val = *(p+1) - 78;
2581
2582                 // Check for correct values.
2583
2584                 if (lon_val < -44 || lon_val > 44 || lat_val < -44 || lat_val > 44) {
2585                     char temp[MAX_LINE_SIZE+1];
2586                     int i;
2587
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) )
2593                             temp[i] = ' ';
2594                     }
2595                     temp[strlen(data)] = '\0';
2596                     
2597                     fprintf(stderr,"extract_multipoints: invalid value in (filtered) \"%s\": %d,%d\n",
2598                         temp,
2599                         lat_val,
2600                         lon_val);
2601
2602                     p_station->num_multipoints = 0;     // forget any points we already set
2603                     ok = 0; // Failure to decode
2604                     break;
2605                 }
2606
2607                 // Malloc the storage area for this if we don't have
2608                 // it yet.
2609                 if (p_station->multipoint_data == NULL) {
2610
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");
2615                         return;
2616                     }
2617                 }
2618  
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?
2628
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);
2633
2634                 p += 2;
2635                 ++p_station->num_multipoints;
2636             }   // End of while loop
2637         }
2638
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.
2645
2646             // Make 'p' point to just after the end of the chars
2647             while ( (p < data+strlen(data)) && (*p != ' ') ) {
2648                p++;
2649             }
2650             // The string that 'p' points to now may be empty
2651
2652             // Truncate "data" at the starting brace - 1
2653             *m_start = '\0';
2654
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);
2658
2659             // The multipoint string and sequence number should be
2660             // erased now from "data".
2661 //fprintf(stderr,"New Data: %s\n", data);
2662         }
2663
2664     }
2665
2666 }
2667
2668
2669
2670
2671 // Returns time in seconds since the Unix epoch.
2672 //
2673 time_t sec_now(void) {
2674     time_t timenw;
2675     time_t ret;
2676
2677     ret = time(&timenw);
2678     return(ret);
2679 }
2680
2681
2682 int is_my_station(AprsDataRow *p_station) {
2683     // if station is owned by me (including SSID)
2684     return(p_station->flag & ST_MYSTATION);
2685 }
2686
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);
2690 }
2691
2692 /*
2693  *  Display text in the status line, text is removed after timeout
2694  */
2695 void statusline(char *status_text,int update) {
2696
2697 // Maybe useful
2698 /*
2699     XmTextFieldSetString (text, status_text);
2700     last_statusline = sec_now();    // Used for auto-ID timeout
2701 */
2702 }
2703
2704
2705 /*
2706  *  Extract text inserted by TNC X-1J4 from start of info line
2707  */
2708 void extract_TNC_text(char *info) {
2709     int i,j,len;
2710
2711     if (strncasecmp(info,"thenet ",7) == 0) {   // 1st match
2712         len = strlen(info)-1;
2713         for (i=7;i<len;i++) {
2714             if (info[i] == ')')
2715                 break;
2716         }
2717         len++;
2718         if (i>7 && info[i] == ')' && info[i+1] == ' ') {        // found
2719             i += 2;
2720             for (j=0;i<=len;i++,j++) {
2721                 info[j] = info[i];
2722             }
2723         }
2724     }
2725 }
2726
2727
2728
2729 /*
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"
2734  *
2735  * TAPR-2 Format:
2736  * KC2ELS-1*>SX0PWT,RELAY,WIDE:`2`$l##>/>"4)}
2737  *
2738  * AEA Format:
2739  * KC2ELS-1*>RELAY>WIDE>SX0PWT:`2`$l##>/>"4)}
2740  */
2741
2742 int valid_path(char *path) {
2743     int i,len,hops,j;
2744     int type,ast,allast,ins;
2745     char ch;
2746
2747
2748     len  = (int)strlen(path);
2749     type = 0;       // 0: unknown, 1: AEA '>', 2: TAPR2 ',', 3: mixed
2750     hops = 1;
2751     ast  = 0;
2752     allast = 0;
2753
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
2757     // our database.
2758     // KAM:        /V /H
2759     // KPC-9612:   /0 /1 /2
2760     if (len > 2 && path[len-2] == '/') {
2761         ch = path[len-1];
2762         if (ch == 'V' || ch == 'H' || ch == '0' || ch == '1' || ch == '2') {
2763             path[len-2] = '\0';
2764             len  = (int)strlen(path);
2765         }
2766     }
2767
2768
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");
2775         path[len-2] = '\0';
2776         len  = (int)strlen(path);
2777         //fprintf(stderr,"%s\n\n",path);
2778     }
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");
2784         path[len-3] = '\0';
2785         len  = (int)strlen(path);
2786         //fprintf(stderr,"%s\n\n",path);
2787     }
2788
2789
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.
2795     if (len > 6) {
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
2801                 }
2802                 len = (int)strlen(path);
2803                 //fprintf(stderr,"%s\n\n",path);
2804             }
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);
2809                 path[i] = '\0';
2810                 len = (int)strlen(path);
2811                 //fprintf(stderr,"%s\n",path);
2812             }
2813             // Check for "-*", change to '*' only
2814             if (path[i] == '-' && path[i+1] == '*') {
2815                 //fprintf(stderr,"%s\tChopping off dash\n",path);
2816                 path[i] = '*';
2817                 path[i+1] = '\0';
2818                 len = (int)strlen(path);
2819                 //fprintf(stderr,"%s\n",path);
2820             }
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
2826                 }
2827                 len = (int)strlen(path);
2828                 //fprintf(stderr,"%s\n",path);
2829             }
2830         }
2831     }
2832
2833
2834     for (i=0,j=0; i<len; i++) {
2835         ch = path[i];
2836
2837         if (ch == '>' || ch == ',') {   // found digi call separator
2838             // We're at the start of a callsign entry in the path
2839
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
2842             }
2843             ast = 0;                    // reset local asterisk counter
2844             
2845             j = i+1;                    // set to start of next call
2846             if (ch == ',')
2847                 type |= 0x02;           // set TAPR2 flag
2848             else
2849                 type |= 0x01;           // set AEA flag (found '>')
2850             hops++;                     // count hops
2851         }
2852
2853         else {                          // digi call character or asterisk
2854             // We're in the middle of a callsign entry
2855
2856             if (ch == '*') {
2857                 ast++;                  // count asterisks in call
2858                 allast++;               // count asterisks in path
2859             }
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
2863                     && ch != '-') {
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.
2867
2868                 return(0);          // wrong character in path
2869             }
2870         }
2871     }
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
2874     }
2875
2876     if (type == 0x03) {
2877         return(0);                      // wrong format, both '>' and ',' in path
2878     }
2879
2880     if (hops > 9) {                     // [APRS Reference chapter 3]
2881         return(0);                      // too much hops, destination + 0-8 digipeater addresses
2882     }
2883
2884     if (type == 0x01) {
2885         int delimiters[20];
2886         int k = 0;
2887         char dest[15];
2888         char rest[100];
2889
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
2894             }
2895         }
2896
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.
2901
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);
2909
2910         // TAPR-2 Format:
2911         // KC2ELS-1*>SX0PWT,RELAY,WIDE:`2`$l##>/>"4)}
2912         //
2913         // AEA Format:
2914         // KC2ELS-1*>RELAY>WIDE>SX0PWT:`2`$l##>/>"4)}
2915         //          9     15   20
2916
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,
2920             sizeof(rest),
2921             "%s",
2922             path);
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);
2926     }
2927
2928     if (allast < 1) {                   // try to insert a missing asterisk
2929         ins  = 0;
2930         hops = 0;
2931
2932         for (i=0; i<len; i++) {
2933
2934             for (j=i; j<len; j++) {             // search for separator
2935                 if (path[j] == ',')
2936                     break;
2937             }
2938
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') {
2942                     ins = j;
2943                 }
2944             }
2945
2946 /*
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]) ) {
2954                     ins = j;
2955                 }
2956             }
2957 */
2958
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') {
2963                     if (hops == 1)
2964                         ins = j;
2965                     else
2966                         ins = i-1;
2967                 }
2968             }
2969
2970 /*
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]) ) {
2978                     if (hops == 1)
2979                         ins = j;
2980                     else
2981                         ins = i-1;
2982                 }
2983             }
2984 */
2985
2986             hops++;
2987             i = j;                      // skip to start of next call
2988         }
2989         if (ins > 0) {
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
2993             }
2994             path[ins] = '*';            // and insert it
2995         }
2996     }
2997     return(1);  // Path is good
2998 }
2999
3000
3001
3002
3003 char *remove_leading_spaces(char *data) {
3004     int i,j;
3005     int count;
3006
3007     if (data == NULL)
3008         return NULL;
3009
3010     if (strlen(data) == 0)
3011         return NULL;
3012
3013     count = 0;
3014     // Count the leading space characters
3015     for (i = 0; i < (int)strlen(data); i++) {
3016         if (data[i] == ' ') {
3017             count++;
3018         }
3019         else {  // Found a non-space
3020             break;
3021         }
3022     }
3023
3024     // Check whether entire string was spaces
3025     if (count == (int)strlen(data)) {
3026         // Empty the string
3027         data[0] = '\0';
3028     }
3029     else if (count > 0) {  // Found some spaces
3030         i = 0;
3031         for( j = count; j < (int)strlen(data); j++ ) {
3032             data[i++] = data[j];    // Move string left
3033         }
3034         data[i] = '\0'; // Terminate the new string
3035     }
3036
3037     return(data);
3038 }
3039
3040 int is_num_chr(char ch) {
3041     return((int)isdigit(ch));
3042 }
3043
3044 char *remove_trailing_spaces(char *data) {
3045     int i;
3046
3047     if (data == NULL)
3048         return NULL;
3049
3050     if (strlen(data) == 0)
3051         return NULL;
3052
3053     for(i=strlen(data)-1;i>=0;i--)
3054         if(data[i] == ' ')
3055             data[i] = '\0';
3056         else
3057             break;
3058
3059         return(data);
3060 }
3061
3062
3063
3064 char *remove_trailing_asterisk(char *data) {
3065     int i;
3066
3067     if (data == NULL)
3068         return NULL;
3069
3070     if (strlen(data) == 0)
3071         return NULL;
3072
3073 // Should the test here be i>=0 ??
3074     for(i=strlen(data)-1;i>0;i--) {
3075         if(data[i] == '*')
3076             data[i] = '\0';
3077     }
3078     return(data);
3079 }
3080
3081 //--------------------------------------------------------------------
3082 //Removes all control codes ( <0x20 or >0x7e ) from a string, including
3083 // CR's, LF's, tab's, etc.
3084 //
3085 void makePrintable(char *cp) {
3086     int i,j;
3087     int len = (int)strlen(cp);
3088     unsigned char *ucp = (unsigned char *)cp;
3089
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
3095     }
3096 }
3097
3098
3099
3100
3101 /*
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]
3105  */
3106 int valid_call(char *call) {
3107     int len, ok;
3108     int i, del, has_num, has_chr;
3109     char c;
3110
3111     has_num = 0;
3112     has_chr = 0;
3113     ok      = 1;
3114     len = (int)strlen(call);
3115
3116     if (len == 0)
3117         return(0);                              // wrong size
3118
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
3123
3124         for(i=0; call[i+4]; i++)
3125             call[i]=call[i+4];
3126
3127         call[i++]=0;
3128         call[i++]=0;
3129         call[i++]=0;
3130         call[i++]=0;
3131         len=strlen(call);
3132
3133     }
3134
3135     if (len > 9)
3136         return(0);      // Too long for valid call (6-2 max e.g. KB6MER-12)
3137
3138     del = 0;
3139     for (i=len-2;ok && i>0 && i>=len-3;i--) {   // search for optional SSID
3140         if (call[i] =='-')
3141             del = i;                            // found the delimiter
3142     }
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
3146                 del = 0;
3147         }
3148         else {                                  // 3 char SSID
3149             if (call[del+1] != '1' || call[del+2] < '0' || call[del+2] > '5')   // -10 ... -15
3150                 del = 0;
3151         }
3152     }
3153
3154     if (del)
3155         len = del;                              // length of base call
3156
3157     for (i=0;ok && i<len;i++) {                 // check for uppercase alphanumeric
3158         c = call[i];
3159
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
3164         else
3165             ok = 0;                             // wrong character in call
3166     }
3167
3168 //    if (!has_num || !has_chr)                 // with this we also discard NOCALL etc.
3169     if (!has_chr)                               
3170         ok = 0;
3171
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);
3177
3178     return(ok);
3179 }
3180
3181
3182 /*
3183  *  Check whether callsign is mine.  "exact == 1" checks the SSID
3184  *  for a match as well.  "exact == 0" checks only the base
3185  *  callsign.
3186  */
3187 int is_my_call(char *call, int exact) {
3188     char *p_del;
3189     int ok;
3190
3191
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.
3195
3196     if (exact) {
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);
3200     }
3201     else {
3202         // We're looking for a similar match.  Compare only up to
3203         // the '-' in each (if present).
3204         int len1,len2;
3205
3206         p_del = index(call,'-');
3207         if (p_del == NULL)
3208             len1 = (int)strlen(call);
3209         else
3210             len1 = p_del - call;
3211
3212         p_del = index(_aprs_mycall,'-');
3213         if (p_del == NULL)
3214             len2 = (int)strlen(_aprs_mycall);
3215         else
3216             len2 = p_del - _aprs_mycall;
3217
3218         ok = (int)(len1 == len2 && !strncmp(call,_aprs_mycall,(size_t)len1));
3219         //fprintf(stderr,"My base call found: %s\n",call);
3220     }
3221  
3222     return(ok);
3223 }
3224
3225
3226
3227
3228 /*
3229  *  Check for a valid internet name.
3230  *  Accept darned-near anything here as long as it is the proper
3231  *  length and printable.
3232  */
3233 int valid_inet_name(char *name, char *info, char *origin, int origin_size) {
3234     int len, i, ok;
3235     char *ptr;
3236     
3237     len = (int)strlen(name);
3238
3239     if (len > 9 || len == 0)            // max 9 printable ASCII characters
3240         return(0);                      // wrong size
3241
3242     for (i=0;i<len;i++)
3243         if (!isprint((int)name[i]))
3244             return(0);                  // not printable
3245
3246     // Modifies "origin" if a match found
3247     //
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
3252     }
3253
3254     // Modifies "origin" if a match found
3255     //
3256     if (len == 6) {                     // check for NWS
3257         ok = 1;
3258         for (i=0;i<6;i++)
3259             if (name[i] <'A' || name[i] > 'Z')  // 6 uppercase characters
3260                 ok = 0;
3261         ok = ok && (info != NULL);      // check if we can test info
3262         if (ok) {
3263             ptr = strstr(info,":NWS-"); // "NWS-" in info field (non-compressed alert)
3264             ok = (ptr != NULL);
3265
3266             if (!ok) {
3267                 ptr = strstr(info,":NWS_"); // "NWS_" in info field (compressed alert)
3268                 ok = (ptr != NULL);
3269             }
3270         }
3271         if (ok) {
3272             snprintf(origin, origin_size, "INET-NWS");
3273             origin[8] = '\0';
3274             return(1);                      // weather alerts
3275         }
3276     }
3277
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.
3281 }
3282
3283
3284
3285 /*
3286  *  Extract third-party traffic from information field before processing
3287  */
3288 int extract_third_party(char *call,
3289                         char *path,
3290                         int path_size,
3291                         char **info,
3292                         char *origin,
3293                         int origin_size) {
3294     int ok;
3295     char *p_call;
3296     char *p_path;
3297
3298     p_call = NULL;                              // to make the compiler happy...
3299     p_path = NULL;                              // to make the compiler happy...
3300     ok = 0;
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
3313             }
3314         }
3315     }
3316
3317     if (ok) {
3318
3319         snprintf(path,
3320             path_size,
3321             "%s",
3322             p_path);
3323
3324         ok = valid_path(path);                  // check the path and convert it to TAPR format
3325         // Note that valid_path() also removes igate injection identifiers
3326
3327     }
3328
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
3333             snprintf(call,
3334                 MAX_CALLSIGN+1,
3335                 "%s",
3336                 p_call);
3337         }
3338         else if (valid_call(p_call)) {              // accept real AX.25 calls
3339             snprintf(call,
3340                 MAX_CALLSIGN+1,
3341                 "%s",
3342                 p_call);
3343         }
3344         else {
3345             ok = 0;
3346         }
3347     }
3348     return(ok);
3349 }
3350
3351
3352
3353 // DK7IN 99
3354 /*
3355  *  Extract Compressed Position Report Data Formats from begin of line
3356  *    [APRS Reference, chapter 9]
3357  *
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.
3363  */
3364 int extract_comp_position(AprsDataRow *p_station, char **info, /*@unused@*/ int type) {
3365     int ok;
3366     int x1, x2, x3, x4, y1, y2, y3, y4;
3367     int c = 0;
3368     int s = 0;
3369     int T = 0;
3370     int len;
3371     char *my_data;
3372     float lon = 0;
3373     float lat = 0;
3374     float range;
3375     int skip = 0;
3376     char L;
3377
3378     my_data = (*info);
3379
3380     // Check leading char.  Must be one of these:
3381     // '/'
3382     // '\'
3383     // A-Z
3384     // a-j
3385     //
3386     L = my_data[0];
3387     if (   L == '/'
3388         || L == '\\'
3389         || ( L >= 'A' && L <= 'Z' )
3390         || ( L >= 'a' && L <= 'j' ) ) {
3391         // We're good so far
3392     }
3393     else {
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.
3397         return(0);
3398     }
3399
3400     //fprintf(stderr,"my_data: %s\n",my_data);
3401
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);
3406
3407     if (ok) {
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] - '!';
3416
3417         // csT bytes
3418         if (my_data[10] == ' ') // Space
3419             c = -1; // This causes us to ignore csT
3420         else {
3421             c = (int)my_data[10] - '!';
3422             s = (int)my_data[11] - '!';
3423             T = (int)my_data[12] - '!';
3424         }
3425         skip = 13;
3426
3427         // Convert ' ' to '0'.  Not specified in APRS Reference!  Do
3428         // we need it?
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';
3437
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));
3446
3447         T &= 0x3F;      // DK7IN: force Compression Byte to valid format
3448                         // mask off upper two unused bits, they should be zero!?
3449
3450         ok = (int)(ok && (c == -1 || ((c >=0 && c < 91) && (s >= 0 && s < 91) && (T >= 0 && T < 64))));
3451
3452         if (ok) {
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
3457
3458             // The below check should _not_ be done.  Compressed
3459             // format can resolve down to about 1 foot worldwide
3460             // (0.3 meters).
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
3464         }
3465     }
3466
3467     if (ok) {
3468         overlay_symbol(my_data[9], my_data[0], p_station);      // Symbol / Table
3469
3470         // Callsign check here includes checking SSID for an exact
3471         // match
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...
3474  
3475             // Record the uncompressed lat/long that we just
3476             // computed.
3477             p_station->coord_lat = (long)((lat));               // in 1/100 sec
3478             p_station->coord_lon = (long)((lon));               // in 1/100 sec
3479         }
3480
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);
3485                 }
3486                 else { // Found compressed course/speed bytes
3487
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
3491                     if (c == 0) {
3492                         c = 90;
3493                     }
3494
3495                     // Compute course in degrees
3496                     xastir_snprintf(p_station->course,
3497                         sizeof(p_station->course),
3498                         "%03d",
3499                         c*4);
3500
3501                     // Compute speed in knots
3502                     xastir_snprintf(p_station->speed,
3503                         sizeof(p_station->speed),
3504                         "%03.0f",
3505                         pow( 1.08,(double)s ) - 1.0);
3506
3507                     //fprintf(stderr,"Decoded speed:%s, course:%s\n",p_station->speed,p_station->course);
3508
3509                 }
3510             }
3511             else {    // Found pre-calculated radio range bytes
3512                 if (c == 90) {
3513                     // pre-calculated radio range
3514                     range = 2 * pow(1.08,(double)s);    // miles
3515
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");
3527                 }
3528             }
3529         }
3530         (*info) += skip;    // delete position from comment
3531     }
3532
3533
3534     //fprintf(stderr,"  extract_comp_position end: %s\n",*info);
3535
3536     return(ok);
3537 }
3538
3539
3540 /*
3541  *  Extract bearing and number/range/quality from beginning of info field
3542  */
3543 int extract_bearing_NRQ(char *info, char *bearing, char *nrq) {
3544     int i,found,len;
3545
3546     len = (int)strlen(info);
3547     found = 0;
3548     if (len >= 8) {
3549         found = 1;
3550         for(i=1; found && i<8; i++)         // check data format
3551             if(!(isdigit((int)info[i]) || (i==4 && info[i]=='/')))
3552                 found=0;
3553     }
3554     if (found) {
3555         substr(bearing,info+1,3);
3556         substr(nrq,info+5,3);
3557
3558         for (i=0;i<=len-8;i++)        // delete bearing/nrq from info field
3559             info[i] = info[i+8];
3560     }
3561
3562     if (!found) {
3563         bearing[0] ='\0';
3564         nrq[0]='\0';
3565     }
3566     return(found);
3567 }
3568
3569
3570
3571
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
3579
3580 /* Extract one of several possible APRS Data Extensions */
3581 void process_data_extension(AprsDataRow *p_station, char *data, /*@unused@*/ int type) {
3582     char temp1[7+1];
3583     char temp2[3+1];
3584     char temp3[10+1];
3585     char bearing[3+1];
3586     char nrq[3+1];
3587
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);
3592     }
3593     else {
3594         clear_area(p_station); // we got a packet with a non area symbol, so clear the data
3595
3596         if (extract_speed_course(data,temp1,temp2)) {  // ... from Mic-E, etc.
3597         //fprintf(stderr,"extracted speed/course\n");
3598
3599             if (atof(temp2) > 0) {
3600             //fprintf(stderr,"course is non-zero\n");
3601             xastir_snprintf(p_station->speed,
3602                 sizeof(p_station->speed),
3603                 "%06.2f",
3604                 atof(temp1));
3605             xastir_snprintf(p_station->course,  // in degrees
3606                 sizeof(p_station->course),
3607                 "%s",
3608                 temp2);
3609             }
3610
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),
3615                     "%s",
3616                     bearing);
3617                 xastir_snprintf(p_station->NRQ,
3618                     sizeof(p_station->NRQ),
3619                     "%s",
3620                     nrq);
3621                 p_station->signal_gain[0] = '\0';   // And blank out the shgd values
3622             }
3623         }
3624         // Don't try to extract speed & course if a compressed
3625         // object.  Test for beam headings for compressed packets
3626         // here
3627         else if (extract_bearing_NRQ(data, bearing, nrq)) {  // Beam headings from DF'ing
3628
3629             //fprintf(stderr,"extracted bearing and NRQ\n");
3630             xastir_snprintf(p_station->bearing,
3631                     sizeof(p_station->bearing),
3632                     "%s",
3633                     bearing);
3634             xastir_snprintf(p_station->NRQ,
3635                     sizeof(p_station->NRQ),
3636                     "%s",
3637                     nrq);
3638             p_station->signal_gain[0] = '\0';   // And blank out the shgd values
3639         }
3640         else {
3641             if (extract_powergain_range(data,temp1)) {
3642
3643 //fprintf(stderr,"Found power_gain: %s\n", temp1);
3644
3645                 xastir_snprintf(p_station->power_gain,
3646                     sizeof(p_station->power_gain),
3647                     "%s",
3648                     temp1);
3649
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),
3654                         "%s",
3655                         bearing);
3656                     xastir_snprintf(p_station->NRQ,
3657                         sizeof(p_station->NRQ),
3658                         "%s",
3659                         nrq);
3660                     p_station->signal_gain[0] = '\0';   // And blank out the shgd values
3661                 }
3662             }
3663             else {
3664                 if (extract_omnidf(data,temp1)) {
3665                     xastir_snprintf(p_station->signal_gain,
3666                         sizeof(p_station->signal_gain),
3667                         "%s",
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';
3671
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),
3681                                 "%06.2f",
3682                                 atof(temp1));
3683                             xastir_snprintf(p_station->course,
3684                                 sizeof(p_station->course),
3685                                 "%s",
3686                                 temp2);                    // in degrees
3687                         }
3688                     }
3689
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
3692                     // way anyway.
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),
3697                             "%s",
3698                             bearing);
3699                         xastir_snprintf(p_station->NRQ,
3700                             sizeof(p_station->NRQ),
3701                             "%s",
3702                             nrq);
3703                         //p_station->signal_gain[0] = '\0';   // And blank out the shgd values
3704                     }
3705                 }
3706             }
3707         }
3708
3709         if (extract_signpost(data, temp2)) {
3710             //fprintf(stderr,"extracted signpost data\n");
3711             xastir_snprintf(p_station->signpost,
3712                 sizeof(p_station->signpost),
3713                 "%s",
3714                 temp2);
3715         }
3716
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),
3721                 "%s",
3722                 temp3);
3723         }
3724  
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),
3729                 "%s",
3730                 temp3);
3731         }
3732     }
3733 }
3734
3735
3736 /*
3737  *  Extract altitude from APRS info field          "/A=012345"    in feet
3738  */
3739 int extract_altitude(char *info, char *altitude) {
3740     int i,ofs,found,len;
3741
3742     found=0;
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) {
3746             found=1;
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 '-'
3749                 found=0;
3750             for(i=4; found && i<9; i++)         // check data format for next 5 chars
3751                 if(!isdigit((int)info[ofs+i]))
3752                     found=0;
3753     }
3754     if (found) {
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];
3759     }
3760     else
3761         altitude[0] = '\0';
3762     return(found);
3763 }
3764
3765
3766
3767
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];
3772
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);
3776     }
3777     // do other things...
3778 }
3779
3780
3781
3782
3783
3784
3785 /*
3786  *  Extract Uncompressed Position Report from begin of line
3787  *
3788  * If a position is found, it is deleted from the data.
3789  */
3790 int extract_position(AprsDataRow *p_station, char **info, int type) {
3791     int ok;
3792     char temp_lat[8+1];
3793     char temp_lon[9+1];
3794     char temp_grid[8+1];
3795     char *my_data;
3796     float gridlat;
3797     float gridlon;
3798     my_data = (*info);
3799
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
3806         if (ok) {
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]));
3820         }
3821                                             
3822         if (ok) {
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';
3832             }
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';
3838             }
3839             else if (my_data[5] == ' ') { // nearest minute
3840                 p_station->pos_amb = 2;
3841                 my_data[5]  = my_data[15] = '5';
3842                 my_data[6]  = '0';
3843                 my_data[16] = '0';
3844             }
3845             else if (my_data[6] == ' ') { // nearest 1/10th minute
3846                 p_station->pos_amb = 1;
3847                 my_data[6]  = my_data[16] = '5';
3848             }
3849
3850             xastir_snprintf(temp_lat,
3851                 sizeof(temp_lat),
3852                 "%s",
3853                 my_data);
3854             temp_lat[7] = toupper(my_data[7]);
3855             temp_lat[8] = '\0';
3856
3857             xastir_snprintf(temp_lon,
3858                 sizeof(temp_lon),
3859                 "%s",
3860                 my_data+9);
3861             temp_lon[8] = toupper(my_data[17]);
3862             temp_lon[9] = '\0';
3863
3864             // Callsign check here also checks SSID for an exact
3865             // match
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...
3868
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);
3871             }
3872
3873             (*info) += 19;                  // delete position from comment
3874         }
3875     }
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')));
3882         if (ok) {
3883             xastir_snprintf(temp_grid,
3884                 sizeof(temp_grid),
3885                 "%s",
3886                 my_data);
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 
3891                 temp_grid[4] = 'L';
3892                 temp_grid[5] = 'L';
3893             }
3894             else {
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]);
3898             }
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
3911     return(ok);
3912 }
3913
3914
3915
3916
3917
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.
3922 //
3923 void add_status(AprsDataRow *p_station, char *status_string) {
3924     AprsCommentRow *ptr;
3925     int add_it = 0;
3926     int len;
3927
3928
3929     len = strlen(status_string);
3930
3931     // Eliminate line-end chars
3932     if (len > 1) {
3933         if ( (status_string[len-1] == '\n')
3934                 || (status_string[len-1] == '\r') ) {
3935             status_string[len-1] = '\0';
3936         }
3937     }
3938
3939     // Shorten it
3940     (void)remove_trailing_spaces(status_string);
3941     (void)remove_leading_spaces(status_string);
3942  
3943     len = strlen(status_string);
3944
3945     // Check for valid pointer
3946     if (p_station != NULL) {
3947
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.
3955
3956         //fprintf(stderr,"Station:%s\tStatus:%s\n",p_station->call_sign,status_string);
3957
3958         // Check whether we have any data stored for this station
3959         if (p_station->status_data == NULL) {
3960             if (len > 0) {
3961                 // No status stored yet and new status is non-NULL,
3962                 // so add it to the list.
3963                 add_it++;
3964             }
3965         }
3966         else {  // We have status data stored already
3967                 // Check for an identical string
3968             AprsCommentRow *ptr2;
3969             int ii = 0;
3970  
3971             ptr = p_station->status_data;
3972             ptr2 = ptr;
3973             while (ptr != NULL) {
3974
3975                 // Note that both text_ptr and comment_string can be
3976                 // empty strings.
3977
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);
3982
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) {
3988
3989                         // Only update the timestamp: We're at the
3990                         // beginning of the list already.
3991                         ptr->sec_heard = sec_now();
3992
3993                         return; // No need to add a new record
3994                     }
3995                     else {  // Delete the record
3996                         AprsCommentRow *ptr3;
3997
3998                         // Keep a pointer to the record
3999                         ptr3 = ptr;
4000
4001                         // Close the chain, skipping this record
4002                         ptr2->next = ptr3->next;
4003
4004                         // Skip "ptr" over the record we wish to
4005                         // delete
4006                         ptr = ptr3->next;
4007
4008                         // Free the record
4009                         free(ptr3->text_ptr);
4010                         free(ptr3);
4011
4012                         // Muck with the counter 'cuz we just
4013                         // deleted one record
4014                         ii--;
4015                     }
4016                 }
4017                 ptr2 = ptr; // Back one record
4018                 if (ptr != NULL) {
4019                     ptr = ptr->next;
4020                 }
4021                 ii++;
4022             }
4023
4024
4025             // No matching string found, or new timestamp found for
4026             // old record.  Add it to the top of the list.
4027             add_it++;
4028             //fprintf(stderr,"No match:
4029             //%s:%s\n",p_station->call_sign,status_string);
4030
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.
4036             //
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) {
4045                     ptr2 = ptr2->next;
4046                 }
4047                 // At this point, we have a pointer to the last
4048                 // record in ptr2->next.  Free it and the text
4049                 // string in it.
4050                 free(ptr2->next->text_ptr);
4051                 free(ptr2->next);
4052                 ptr2->next = NULL;
4053             } 
4054         }
4055
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.
4060
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);
4064
4065             p_station->status_data->next = ptr;    // Link in old records or NULL
4066
4067             // Malloc the string space we'll need, attach it to our
4068             // new record
4069             p_station->status_data->text_ptr = (char *)malloc(sizeof(char) * (len+1));
4070             CHECKMALLOC(p_station->status_data->text_ptr);
4071
4072             // Fill in the string
4073             xastir_snprintf(p_station->status_data->text_ptr,
4074                 len+1,
4075                 "%s",
4076                 status_string);
4077
4078             // Fill in the timestamp
4079             p_station->status_data->sec_heard = sec_now();
4080
4081             //fprintf(stderr,"Station:%s\tStatus:%s\n\n",p_station->call_sign,p_station->status_data->text_ptr);
4082         }
4083     }
4084 }
4085
4086
4087  
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.
4093 //
4094 void add_comment(AprsDataRow *p_station, char *comment_string) {
4095     AprsCommentRow *ptr;
4096     int add_it = 0;
4097     int len;
4098
4099
4100     len = strlen(comment_string);
4101
4102     // Eliminate line-end chars
4103     if (len > 1) {
4104         if ( (comment_string[len-1] == '\n')
4105                 || (comment_string[len-1] == '\r') ) {
4106             comment_string[len-1] = '\0';
4107         }
4108     }
4109
4110     // Shorten it
4111     (void)remove_trailing_spaces(comment_string);
4112     (void)remove_leading_spaces(comment_string);
4113
4114     len = strlen(comment_string);
4115
4116     // Check for valid pointer
4117     if (p_station != NULL) {
4118
4119         // Check whether we have any data stored for this station
4120         if (p_station->comment_data == NULL) {
4121             if (len > 0) {
4122                 // No comments stored yet and new comment is
4123                 // non-NULL, so add it to the list.
4124                 add_it++;
4125             }
4126         }
4127         else {  // We have comment data stored already
4128                 // Check for an identical string
4129             AprsCommentRow *ptr2;
4130             int ii = 0;
4131  
4132             ptr = p_station->comment_data;
4133             ptr2 = ptr;
4134             while (ptr != NULL) {
4135
4136                 // Note that both text_ptr and comment_string can be
4137                 // empty strings.
4138
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);
4142
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();
4151
4152                         return; // No need to add a new record
4153                     }
4154                     else {  // Delete the record
4155                         AprsCommentRow *ptr3;
4156
4157                         // Keep a pointer to the record
4158                         ptr3 = ptr;
4159
4160                         // Close the chain, skipping this record
4161                         ptr2->next = ptr3->next;
4162
4163                         // Skip "ptr" over the record we with to
4164                         // delete
4165                         ptr = ptr3->next;
4166
4167                         // Free the record
4168                         free(ptr3->text_ptr);
4169                         free(ptr3);
4170
4171                         // Muck with the counter 'cuz we just
4172                         // deleted one record
4173                         ii--;
4174                     }
4175                 }
4176                 ptr2 = ptr; // Keep this pointer one record back as
4177                             // we progress.
4178
4179                 if (ptr != NULL) {
4180                     ptr = ptr->next;
4181                 }
4182
4183                 ii++;
4184             }
4185             // No matching string found, or new timestamp found for
4186             // old record.  Add it to the top of the list.
4187             add_it++;
4188             //fprintf(stderr,"No match: %s:%s\n",p_station->call_sign,comment_string);
4189
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.
4195             //
4196             if (ii >= MAX_COMMENT_LINES) {
4197
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) {
4205                     ptr2 = ptr2->next;
4206                 }
4207                 // At this point, we have a pointer to the last
4208                 // record in ptr2->next.  Free it and the text
4209                 // string in it.
4210                 free(ptr2->next->text_ptr);
4211                 free(ptr2->next);
4212                 ptr2->next = NULL;
4213             } 
4214         }
4215
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.
4220
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);
4224
4225             p_station->comment_data->next = ptr;    // Link in old records or NULL
4226
4227             // Malloc the string space we'll need, attach it to our
4228             // new record
4229             p_station->comment_data->text_ptr = (char *)malloc(sizeof(char) * (len+1));
4230             CHECKMALLOC(p_station->comment_data->text_ptr);
4231
4232             // Fill in the string
4233             xastir_snprintf(p_station->comment_data->text_ptr,
4234                 len+1,
4235                 "%s",
4236                 comment_string);
4237
4238             // Fill in the timestamp
4239             p_station->comment_data->sec_heard = sec_now();
4240         }
4241     }
4242 }
4243
4244
4245
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.
4249 //
4250 // PE1DNN
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.
4253 //
4254 int extract_weather_item(char *data, char type, int datalen, char *temp) {
4255     int i,ofs,found,len;
4256
4257
4258 //fprintf(stderr,"%s\n",data);
4259
4260     found=0;
4261     len = (int)strlen(data);
4262     for(ofs=0; !found && ofs<len-datalen; ofs++)      // search for start sequence
4263         if (data[ofs]==type) {
4264             found=1;
4265             if (!is_weather_data(data+ofs+1, datalen))
4266                 found=0;
4267         }
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
4275             temp[0] = '\0';
4276             found = 0;
4277         }
4278         else
4279         {
4280 //                fprintf(stderr,"extract_weather_item: %s\n",temp);
4281         }
4282     }
4283     else
4284         temp[0] = '\0';
4285     return(found);
4286 }
4287
4288
4289
4290
4291
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
4295 // found.
4296 //
4297 // PE1DNN
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.
4300 //
4301 int test_extract_weather_item(char *data, char type, int datalen) {
4302     int ofs,found,len;
4303
4304     found=0;
4305     len = (int)strlen(data);
4306     for(ofs=0; !found && ofs<len-datalen; ofs++)      // search for start sequence
4307         if (data[ofs]==type) {
4308             found=1;
4309             if (!is_weather_data(data+ofs+1, datalen))
4310                 found=0;
4311         }
4312
4313     // We really should test for numbers here (with an optional
4314     // leading '-'), and test across the length of the substring.
4315     //
4316     if(found && ((data[ofs+1] == ' ') || (data[ofs+1] == '.'))) {
4317         // found it, but it doesn't contain a value!
4318         // report "not found" - PE1DNN
4319         found = 0;
4320     }
4321
4322     //fprintf(stderr,"test_extract: %c %d\n",type,found);
4323     return(found);
4324 }
4325
4326
4327 int get_weather_record(AprsDataRow *fill) {    // get or create weather storage
4328     int ok=1;
4329
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");
4334             ok = 0;
4335         }
4336         else {
4337             init_weather(fill->weather_data);
4338         }
4339     }
4340     return(ok);
4341 }
4342
4343
4344
4345
4346 // DK7IN 77
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
4351 //
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
4354 // objects.
4355 //
4356 // By the time we call this function we've already extracted any
4357 // time/position info at the beginning of the string.
4358 //
4359 int extract_weather(AprsDataRow *p_station, char *data, int compr) {
4360     char time_data[MAX_TIME];
4361     char temp[5];
4362     int  ok = 1;
4363     AprsWeatherRow *weather;
4364     char course[4];
4365     char speed[4];
4366     int in_knots = 0;
4367
4368 //WE7U
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.
4377
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)) {
4383
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,
4390                 sizeof(speed),
4391                 "%s",
4392                 p_station->speed);
4393             xastir_snprintf(course,
4394                 sizeof(course),
4395                 "%s",
4396                 p_station->course);
4397             in_knots = 1;
4398
4399             //fprintf(stderr,"Found compressed wx\n");
4400         }
4401         // Look for weather data in non-fixed locations (RAWS WX
4402         // Stations?)
4403         else if ( strlen(data) >= 8
4404                 && test_extract_weather_item(data,'g',3)
4405                 && test_extract_weather_item(data,'t',3) ) {
4406
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,
4413                 sizeof(speed),
4414                 "%s",
4415                 p_station->speed);
4416             xastir_snprintf(course,
4417                 sizeof(course),
4418                 "%s",
4419                 p_station->course);
4420             in_knots = 1;
4421
4422             //fprintf(stderr,"Found compressed WX in non-fixed locations! %s:%s\n",
4423             //    p_station->call_sign,data);
4424
4425         }
4426         else {  // No weather data found
4427             ok = 0;
4428
4429             //fprintf(stderr,"No compressed wx\n");
4430         }
4431     }
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
4438
4439             // Get speed/course.  Speed is in knots.
4440             (void)extract_speed_course(data,speed,course);
4441             in_knots = 1;
4442
4443             // Either one not found?  Try again.
4444             if ( (speed[0] == '\0') || (course[0] == '\0') ) {
4445
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)
4450                 in_knots = 0;
4451             }
4452
4453             //fprintf(stderr,"Found Complete Weather Report\n");
4454         }
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
4463             // is in knots.
4464             (void)extract_speed_course(data,speed,course);
4465             in_knots = 1;
4466
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)
4474                 in_knots = 0;
4475             }
4476
4477             //fprintf(stderr,"Found weather\n");
4478         }
4479         // Look for weather data in non-fixed locations (RAWS WX
4480         // Stations?)
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) ) {
4485
4486             // Try to snag speed/course out of first 7 bytes.  Speed
4487             // is in knots.
4488             (void)extract_speed_course(data,speed,course);
4489             in_knots = 1;
4490
4491             // Either one not found?  Try again.
4492             if ( (speed[0] == '\0') || (course[0] == '\0') ) {
4493
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)
4498                 in_knots = 0;
4499             }
4500  
4501             //fprintf(stderr,"Found WX in non-fixed locations!  %s:%s\n",
4502             //    p_station->call_sign,data);
4503         }
4504         else {  // No weather data found
4505             ok = 0;
4506
4507             //fprintf(stderr,"No wx found\n");
4508         }
4509     }
4510
4511     if (ok) {
4512         ok = get_weather_record(p_station);     // get existing or create new weather record
4513     }
4514
4515     if (ok) {
4516         weather = p_station->weather_data;
4517
4518         // Copy into weather speed variable.  Convert knots to mph
4519         // if necessary.
4520         if (in_knots) {
4521             xastir_snprintf(weather->wx_speed,
4522                 sizeof(weather->wx_speed),
4523                 "%03.0f",
4524                 atoi(speed) * 1.1508);  // Convert knots to mph
4525         }
4526         else {
4527             // Already in mph.  Copy w/no conversion.
4528             xastir_snprintf(weather->wx_speed,
4529                 sizeof(weather->wx_speed),
4530                 "%s",
4531                 speed);
4532         }
4533
4534         xastir_snprintf(weather->wx_course,
4535             sizeof(weather->wx_course),
4536             "%s",
4537             course);
4538
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';
4545         }
4546
4547         (void)extract_weather_item(data,'g',3,weather->wx_gust);      // gust (peak wind speed in mph in the last 5 minutes)
4548
4549         (void)extract_weather_item(data,'t',3,weather->wx_temp);      // temperature (in deg Fahrenheit), could be negative
4550
4551         (void)extract_weather_item(data,'r',3,weather->wx_rain);      // rainfall (1/100 inch) in the last hour
4552
4553         (void)extract_weather_item(data,'p',3,weather->wx_prec_24);   // rainfall (1/100 inch) in the last 24 hours
4554
4555         (void)extract_weather_item(data,'P',3,weather->wx_prec_00);   // rainfall (1/100 inch) since midnight
4556
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);
4559
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),
4563                 "%0.1f",
4564                 (float)(atoi(weather->wx_baro)/10.0));
4565
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
4571         }
4572
4573         (void)extract_weather_item(data,'L',3,temp);                  // luminosity (in watts per square meter) 999 and below
4574
4575         (void)extract_weather_item(data,'l',3,temp);                  // luminosity (in watts per square meter) 1000 and above
4576
4577         (void)extract_weather_item(data,'#',3,temp);                  // raw rain counter
4578
4579         (void)extract_weather_item(data,'F',3,weather->wx_fuel_temp); // Fuel Temperature in Â°F (RAWS)
4580
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),
4584                 "%03d",
4585                 (atoi(weather->wx_fuel_moisture)+99)%100+1);
4586
4587 //    extract_weather_item(data,'w',3,temp);                          // ?? text wUII
4588
4589     // now there should be the name of the weather station...
4590
4591         // Create a timestamp from the current time
4592         xastir_snprintf(weather->wx_time,
4593             sizeof(weather->wx_time),
4594             "%s",
4595             get_time(time_data));
4596
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
4601
4602 //        case ('.'):/* skip */
4603 //            wx_strpos+=4;
4604 //            break;
4605
4606 //        default:
4607 //            wx_done=1;
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),
4612 //                    "%s",
4613 //                    data+wx_strpos+1);
4614 //            break;
4615     }
4616     return(ok);
4617 }
4618
4619
4620
4621 // Initial attempt at decoding tropical storm, tropical depression,
4622 // and hurricane data.
4623 //
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
4629 // "/TY" = Typhoon
4630 // "/ST" = Super Typhoon
4631 // "/SC" = Severe Cyclone
4632
4633 // The symbol will be either "\@" for current position, or "/@" for
4634 // predicted position.
4635 //
4636 int extract_storm(AprsDataRow *p_station, char *data, int compr) {
4637     char time_data[MAX_TIME];
4638     int  ok = 1;
4639     AprsWeatherRow *weather;
4640     char course[4];
4641     char speed[4];  // Speed in knots
4642     char *p, *p2;
4643
4644
4645 // Should probably encode the storm type in the weather object and
4646 // print it out in plain text in the Station Info dialog.
4647
4648     if ((p = strstr(data, "/TS")) != NULL) {
4649         // We have a Tropical Storm
4650 //fprintf(stderr,"Tropical Storm! %s\n",data);
4651     }
4652     else if ((p = strstr(data, "/TD")) != NULL) {
4653         // We have a Tropical Depression
4654 //fprintf(stderr,"Tropical Depression! %s\n",data);
4655     }
4656     else if ((p = strstr(data, "/HC")) != NULL) {
4657         // We have a Hurricane
4658 //fprintf(stderr,"Hurricane! %s\n",data);
4659     }
4660     else if ((p = strstr(data, "/TY")) != NULL) {
4661         // We have a Typhoon
4662 //fprintf(stderr,"Hurricane! %s\n",data);
4663     }
4664     else if ((p = strstr(data, "/ST")) != NULL) {
4665         // We have a Super Typhoon
4666 //fprintf(stderr,"Hurricane! %s\n",data);
4667     }
4668     else if ((p = strstr(data, "/SC")) != NULL) {
4669         // We have a Severe Cyclone
4670 //fprintf(stderr,"Hurricane! %s\n",data);
4671     }
4672     else {  // Not one of the three we're trying to decode
4673         ok = 0;
4674         return(ok);
4675     }
4676
4677 //fprintf(stderr,"\n%s\n",data);
4678
4679     // Back up 7 spots to try to extract the next items
4680     p2 = p - 7;
4681     if (p2 >= data) {
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");
4686             ok = 0;
4687             return(ok);
4688         }
4689     }
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");
4693         ok = 0;
4694         return(ok);
4695     }
4696
4697
4698 //fprintf(stderr,"%s\n",data);
4699
4700     if (ok) {
4701
4702         // If we got this far, we have speed/course and know what type
4703         // of storm it is.
4704 //fprintf(stderr,"Speed: %s, Course: %s\n",speed,course);
4705
4706         ok = get_weather_record(p_station);     // get existing or create new weather record
4707     }
4708
4709     if (ok) {
4710 //        p_station->speed_time[0]     = '\0';
4711
4712         p_station->weather_data->wx_storm = 1;  // We found a storm
4713
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),
4719                 "%s",
4720                 speed);
4721         }
4722         else {
4723             p_station->speed[0] = '\0';
4724         }
4725
4726         if (strcmp(course,"   ") != 0 && strcmp(course,"...") != 0)
4727             xastir_snprintf(p_station->course,
4728                 sizeof(p_station->course),
4729                 "%s",
4730                 course);
4731         else
4732             p_station->course[0] = '\0';
4733  
4734         weather = p_station->weather_data;
4735  
4736         p2++;   // Skip the description text, "/TS", "/HC", "/TD", "/TY", "/ST", or "/SC"
4737
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),
4743                 "%0.1f",
4744                 (float)(atoi(weather->wx_speed)) * 1.1508);
4745
4746 //fprintf(stderr,"%s\n",data);
4747
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),
4753                 "%0.1f",
4754                 (float)(atoi(weather->wx_gust)) * 1.1508);
4755
4756 //fprintf(stderr,"%s\n",data);
4757
4758         // Pressure is already in millibars/hPa.  No conversion
4759         // needed.
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),
4763                 "%0.1f",
4764                 (float)(atoi(weather->wx_baro)));
4765
4766 //fprintf(stderr,"%s\n",data);
4767
4768         (void)extract_weather_item(p2,'>',3,weather->wx_hurricane_radius); // Nautical miles
4769
4770 //fprintf(stderr,"%s\n",data);
4771
4772         (void)extract_weather_item(p2,'&',3,weather->wx_trop_storm_radius); // Nautical miles
4773
4774 //fprintf(stderr,"%s\n",data);
4775
4776         (void)extract_weather_item(p2,'%',3,weather->wx_whole_gale_radius); // Nautical miles
4777
4778 //fprintf(stderr,"%s\n",data);
4779
4780         // Create a timestamp from the current time
4781         xastir_snprintf(weather->wx_time,
4782             sizeof(weather->wx_time),
4783             "%s",
4784             get_time(time_data));
4785
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();
4789     }
4790     return(ok);
4791 }
4792
4793
4794
4795 /*
4796  *  Free station memory for one entry
4797  */
4798 void delete_station_memory(AprsDataRow *p_del) {
4799     if (p_del == NULL)
4800         return;
4801     
4802     remove_name(p_del);
4803     remove_time(p_del);
4804     free(p_del);
4805     station_count--;
4806 }
4807
4808 /*
4809  *  Create new uninitialized element in station list
4810  *  and insert it before p_name after p_time entries.
4811  *
4812  *  Returns NULL if malloc error.
4813  */
4814 /*@null@*/ AprsDataRow *insert_new_station(AprsDataRow *p_name, AprsDataRow *p_time) {
4815     AprsDataRow *p_new;
4816
4817
4818     p_new = (AprsDataRow *)malloc(sizeof(AprsDataRow));
4819
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
4828     }
4829     else {  // p_new == NULL
4830         fprintf(stderr,"ERROR: we got no memory for station storage\n");
4831     }
4832
4833
4834     return(p_new);                      // return pointer to new element
4835 }
4836
4837
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
4841
4842 /*
4843  *  Setup station storage structure
4844  */
4845 void init_station_data(void) {
4846
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
4856     
4857 }
4858
4859
4860 /*
4861  *  Initialize station data
4862  */        
4863 void init_station(AprsDataRow *p_station) {
4864     // the list pointers should already be set
4865         
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;
4920     
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.
4925     // KG4NBB
4926     
4927     p_station->num_multipoints = 0;
4928     p_station->multipoint_data = NULL;
4929 }
4930
4931
4932
4933 void init_tactical_hash(int clobber) {
4934
4935     // make sure we don't leak
4936     if (tactical_hash) {
4937         if (clobber) {
4938             hashtable_destroy(tactical_hash, 1);
4939             tactical_hash=create_hashtable(TACTICAL_HASH_SIZE,
4940                 tactical_hash_from_key,
4941                 tactical_keys_equal);
4942         }
4943     }
4944     else {
4945         tactical_hash=create_hashtable(TACTICAL_HASH_SIZE,
4946             tactical_hash_from_key,
4947             tactical_keys_equal);
4948     }
4949 }
4950
4951
4952
4953
4954 char *get_tactical_from_hash(char *callsign) {
4955     char *result;
4956
4957     if (callsign == NULL || *callsign == '\0') {
4958         fprintf(stderr,"Empty callsign passed to get_tactical_from_hash()\n");
4959         return(NULL);
4960     }
4961
4962     if (!tactical_hash) {  // no table to search
4963 //fprintf(stderr,"Creating hash table\n");
4964         init_tactical_hash(1); // so create one
4965         return NULL;
4966     }
4967
4968 //    fprintf(stderr,"   searching for %s...",callsign);
4969
4970     result=hashtable_search(tactical_hash,callsign);
4971
4972         if (result) {
4973 //            fprintf(stderr,"\t\tFound it, %s, len=%d, %s\n",
4974 //                callsign,
4975 //                strlen(callsign),
4976 //                result);
4977         } else {
4978 //            fprintf(stderr,"\t\tNot found, %s, len=%d\n",
4979 //                callsign,
4980 //                strlen(callsign));
4981         }
4982
4983     return (result);
4984 }
4985
4986
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.
4992 //
4993 //
4994 // Inputs:  lat1/long1/lat2/long2 in radians (double)
4995 //
4996 // Outputs: Distance in meters between them (double)
4997 //
4998 double calc_distance_haversine_radian(double lat1, double lon1, double lat2, double lon2) {
4999     double dlon, dlat;
5000     double a, c, d;
5001     double R = EARTH_RADIUS_METERS;
5002     #define square(x) (x)*(x)
5003
5004
5005     dlon = lon2 - lon1;
5006     dlat = lat2 - lat1;
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));
5009     d = R * c;
5010
5011     return(d);
5012 }
5013
5014
5015
5016
5017
5018
5019 /*
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.
5022  */
5023 void insert_name(AprsDataRow *p_new, AprsDataRow *p_name) {
5024
5025     // Set up pointer to next record (or NULL), sorted by name
5026     p_new->n_next = p_name;
5027
5028     if (p_name == NULL) {       // Add to end of list
5029
5030         p_new->n_prev = n_last;
5031
5032         if (n_last == NULL)     // If we have an empty list
5033             n_first = p_new;    // Add it to the head of the list
5034
5035         else    // List wasn't empty, add to the end of the list.
5036             n_last->n_next = p_new;
5037
5038         n_last = p_new;
5039     }
5040
5041     else {  // Insert new record ahead of p_name record
5042
5043         p_new->n_prev = p_name->n_prev;
5044
5045         if (p_name->n_prev == NULL)     // add to begin of list
5046             n_first = p_new;
5047         else
5048             p_name->n_prev->n_next = p_new;
5049
5050         p_name->n_prev = p_new;
5051     }
5052 }
5053
5054
5055
5056 // Update all of the pointers so that they accurately reflect the
5057 // current state of the station database.
5058 //
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
5064 // letters.
5065 //
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.
5071 //
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.
5076 //
5077 void station_shortcuts_update_function(int hash_key_in, AprsDataRow *p_rem) {
5078     int ii;
5079     AprsDataRow *ptr;
5080     int prev_hash_key = 0x0000;
5081     int hash_key;
5082
5083
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.
5087
5088     if ( (hash_key_in != -1)
5089             && (hash_key_in >= 0)
5090             && (hash_key_in < 16384) ) {
5091
5092         // We're adding/changing a hash key entry
5093         station_shortcuts[hash_key_in] = p_rem;
5094 //fprintf(stderr,"%i ",hash_key_in);
5095     }
5096     else {  // We're removing a hash key entry.
5097
5098         // Clear and rebuild the entire hash table.
5099
5100 //??????????????????????????????????????????????????
5101     // Clear all of the pointers before we begin????
5102 //??????????????????????????????????????????????????
5103         for (ii = 0; ii < 16384; ii++) {
5104             station_shortcuts[ii] = NULL;
5105         }
5106
5107         ptr = n_first;  // Start of list
5108
5109
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
5113         // of the list.
5114         //
5115         while ( (ptr != NULL) && (prev_hash_key < 16384) ) {
5116
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)
5119             //
5120             hash_key = (int)((ptr->call_sign[0] & 0x7f) << 7);
5121             hash_key = hash_key | (int)(ptr->call_sign[1] & 0x7f);
5122
5123             if (hash_key > prev_hash_key) {
5124
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;
5129
5130                 }
5131                 prev_hash_key = hash_key;
5132             }
5133             ptr = ptr->n_next;
5134         }
5135
5136     }
5137
5138 }
5139
5140
5141
5142
5143 /*
5144  *  Create new initialized element for call in station list
5145  *  and insert it before p_name after p_time entries.
5146  *
5147  *  Returns NULL if mallc error.
5148  */
5149 /*@null@*/ AprsDataRow *add_new_station(AprsDataRow *p_name, AprsDataRow *p_time, char *call) {
5150     AprsDataRow *p_new;
5151     int hash_key;   // We use a 14-bit hash key
5152     char *tactical_call;
5153
5154
5155
5156     if (call[0] == '\0') {
5157         // Do nothing.  No update needed.  Callsign is empty.
5158         return(NULL);
5159     }
5160
5161          
5162     if(_aprs_show_new_station_alert)
5163     {
5164             const gchar *msg = g_strdup_printf("New station: %s", call); 
5165             hildon_banner_show_information(_window, NULL, msg);
5166     }
5167
5168     p_new = insert_new_station(p_name,p_time);  // allocate memory
5169
5170     if (p_new == NULL) {
5171
5172         // Couldn't allocate space for the station
5173         return(NULL);
5174     }
5175
5176     init_station(p_new);                    // initialize new station record
5177     
5178     
5179     xastir_snprintf(p_new->call_sign,
5180         sizeof(p_new->call_sign),
5181         "%s",
5182         call);
5183     station_count++;
5184
5185
5186
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.
5190
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)
5193     //
5194     hash_key = (int)((call[0] & 0x7f) << 7);
5195     hash_key = hash_key | (int)(call[1] & 0x7f);
5196
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);
5200     }
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
5204         // segment.
5205         station_shortcuts_update_function(hash_key, p_new);
5206     }
5207     else {
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]) {
5214
5215             station_shortcuts_update_function(hash_key, p_new);
5216         }
5217     }
5218
5219     // Check whether we have a tactical call to assign to this
5220     // station in our tactical hash table.
5221
5222     tactical_call = get_tactical_from_hash(call);
5223
5224
5225     // If tactical call found and not blank
5226     if (tactical_call && tactical_call[0] != '\0') {
5227
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);
5231
5232         snprintf(p_new->tactical_call_sign,
5233             MAX_TACTICAL_CALL+1,
5234             "%s",
5235             tactical_call);
5236
5237         //if (tactical_call[0] == '\0')
5238         //    fprintf(stderr,"Blank tactical call\n");
5239     }
5240
5241     return(p_new);                      // return pointer to new element
5242 }
5243
5244
5245
5246 /*
5247  *  Add data from APRS information field to station database
5248  *  Returns a 1 if successful
5249  */
5250 int data_add(gint type,
5251              gchar *call_sign,
5252              gchar *path,
5253              gchar *data,
5254              TAprsPort port,
5255              gchar *origin,
5256              gint third_party,
5257              gint station_is_mine,
5258              gint object_is_mine) {
5259
5260
5261     AprsDataRow *p_station;
5262     AprsDataRow *p_time;
5263     char call[MAX_CALLSIGN+1];
5264     char new_station;
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;
5270     short last_flag;
5271     char temp_data[40]; // short term string storage, MAX_CALLSIGN, ...
5272 //    long l_lat, l_lon;
5273     double distance;
5274 //    char station_id[600];
5275     int found_pos;
5276     float value;
5277     AprsWeatherRow *weather;
5278     int moving;
5279     int changed_pos;
5280     int screen_update;
5281     int ok, store;
5282     int ok_to_display;
5283     int compr_pos;
5284     char *p = NULL; // KC2ELS - used for WIDEn-N
5285     int direct = 0;
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
5289     
5290 // TODO update based on time
5291 //    static time_t lastScreenUpdate;
5292
5293
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.
5298
5299         return(0);  // Not an ok packet
5300     }
5301
5302     // Check for some reasonable string in call_sign parameter
5303     if (call_sign == NULL || strlen(call_sign) == 0) {
5304
5305         return(0);
5306     }
5307  
5308
5309     if (origin && is_my_call(origin, 1)) {
5310         new_origin_is_mine++;   // The new object/item is owned by me
5311     }
5312
5313     weather = NULL; // only to make the compiler happy...
5314     found_pos = 1;
5315     snprintf(call,
5316         sizeof(call),
5317         "%s",
5318         call_sign);
5319     p_station = NULL;
5320     new_station = (char)FALSE;                          // to make the compiler happy...
5321     last_lat = 0L;
5322     last_lon = 0L;
5323     last_stn_sec = sec_now();
5324     last_alt[0]    = '\0';
5325     last_speed[0]  = '\0';
5326     last_course[0] = '\0';
5327     last_flag      = 0;
5328     ok = 0;
5329     store = 0;
5330     p_time = NULL;                                      // add to end of time sorted list (newest)
5331     compr_pos = 0;
5332
5333
5334     if (search_station_name(&p_station,call,1)) 
5335     {       // If we found the station in our list
5336  
5337
5338 //      fprintf(stderr, "DEBUG: Station:,Found,:%s:\n", call);
5339         
5340         // Check whether it's already a locally-owned object/item
5341         if (is_my_object_item(p_station)) {
5342
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.
5346
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?
5354
5355             new_station = (char)FALSE;
5356             object_is_mine_previous++;
5357         }
5358         else {
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
5361         }
5362
5363         if (is_my_station(p_station)) {
5364             station_is_mine++; // Station/object/item is owned/controlled by me
5365         }
5366     }
5367     else 
5368     {
5369 //      fprintf(stderr, "DEBUG: Station:,New,:%s:\n", call);
5370
5371         p_station = add_new_station(p_station,p_time,call);     // create storage
5372         new_station = (char)TRUE;                       // for new station
5373
5374     }
5375     
5376
5377     if (p_station != NULL) 
5378     {
5379
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;
5383         snprintf(last_alt,
5384             sizeof(last_alt),
5385             "%s",
5386             p_station->altitude);
5387         snprintf(last_speed,
5388             sizeof(last_speed),
5389             "%s",
5390             p_station->speed);
5391         snprintf(last_course,
5392             sizeof(last_course),    
5393             "%s",
5394             p_station->course);
5395         last_flag = p_station->flag;
5396
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';
5401
5402         ok = 1;                         // succeed as default
5403
5404
5405         switch (type) {
5406
5407             case (APRS_MICE):           // Mic-E format
5408             case (APRS_FIXED):          // '!'
5409             case (APRS_MSGCAP):         // '='
5410
5411                 if (!extract_position(p_station,&data,type)) {          // uncompressed lat/lon
5412                     compr_pos = 1;
5413                     if (!extract_comp_position(p_station,&data,type))   // compressed lat/lon
5414                         ok = 0;
5415                     else
5416                         p_station->pos_amb = 0; // No ambiguity in compressed posits
5417                 }
5418
5419                 if (ok) {
5420
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
5429
5430                     if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
5431                         extract_multipoints(p_station, data, type, 1);
5432  
5433                     add_comment(p_station,data);
5434
5435                     p_station->record_type = NORMAL_APRS;
5436                     if (type == APRS_MSGCAP)
5437                         p_station->flag |= ST_MSGCAP;           // set "message capable" flag
5438                     else
5439                         p_station->flag &= (~ST_MSGCAP);        // clear "message capable" flag
5440
5441                     // Assign a non-default value for the error
5442                     // ellipse?
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;
5447                     }
5448                     else {
5449                         p_station->error_ellipse_radius = 600; // Default of 6m
5450                         p_station->lat_precision = 6;
5451                         p_station->lon_precision = 6;
5452                     }
5453
5454                 }
5455                 break;
5456
5457 /*
5458             case (APRS_DOWN):           // '/'
5459                 ok = extract_time(p_station, data, type);               // we need a time
5460                 if (ok) {
5461                     if (!extract_position(p_station,&data,type)) {      // uncompressed lat/lon
5462                         compr_pos = 1;
5463                         if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
5464                             ok = 0;
5465                         else
5466                             p_station->pos_amb = 0; // No ambiguity in compressed posits
5467                     }
5468                 }
5469
5470                 if (ok) {
5471
5472                     // Create a timestamp from the current time
5473                     xastir_snprintf(p_station->pos_time,
5474                         sizeof(p_station->pos_time),
5475                         "%s",
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
5479
5480                     if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
5481                         extract_multipoints(p_station, data, type, 1);
5482  
5483                     add_comment(p_station,data);
5484
5485                     p_station->record_type = DOWN_APRS;
5486                     p_station->flag &= (~ST_MSGCAP);            // clear "message capable" flag
5487
5488                     // Assign a non-default value for the error
5489                     // ellipse?
5490                     if (!compr_pos) {
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;
5494                     }
5495                     else {
5496                         p_station->error_ellipse_radius = 600; // Default of 6m
5497                         p_station->lat_precision = 6;
5498                         p_station->lon_precision = 6;
5499                     }
5500                 }
5501                 break;
5502 */
5503
5504             case (APRS_DF):             // '@'
5505             case (APRS_MOBILE):         // '@'
5506
5507                 ok = extract_time(p_station, data, type);               // we need a time
5508                 if (ok) {
5509                     if (!extract_position(p_station,&data,type)) {      // uncompressed lat/lon
5510                         compr_pos = 1;
5511                         if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
5512                             ok = 0;
5513                         else
5514                             p_station->pos_amb = 0; // No ambiguity in compressed posits
5515                     }
5516                 }
5517                 if (ok) {
5518
5519                     process_data_extension(p_station,data,type);        // PHG, speed, etc.
5520                     process_info_field(p_station,data,type);            // altitude
5521
5522                     if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
5523                         extract_multipoints(p_station, data, type, 1);
5524  
5525                     add_comment(p_station,data);
5526
5527                     if(type == APRS_MOBILE)
5528                         p_station->record_type = MOBILE_APRS;
5529                     else
5530                         p_station->record_type = DF_APRS;
5531                     //@ stations have messaging per spec
5532                     p_station->flag |= (ST_MSGCAP);            // set "message capable" flag
5533
5534                     // Assign a non-default value for the error
5535                     // ellipse?
5536                     if (!compr_pos) {
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;
5540                     }
5541                     else {
5542                         p_station->error_ellipse_radius = 600; // Default of 6m
5543                         p_station->lat_precision = 6;
5544                         p_station->lon_precision = 6;
5545                     }
5546                 }
5547                 break;
5548
5549             case (APRS_GRID):
5550
5551                 ok = extract_position(p_station, &data, type);
5552                 if (ok) { 
5553
5554
5555                     process_info_field(p_station,data,type);            // altitude
5556
5557                     if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
5558                         extract_multipoints(p_station, data, type, 1);
5559  
5560                     add_comment(p_station,data);
5561
5562                     // Assign a non-default value for the error
5563                     // ellipse?
5564 //                    p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
5565
5566 // WE7U
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;
5570                 }
5571                 else {
5572                 }
5573                 break;
5574
5575             case (STATION_CALL_DATA):
5576
5577                 p_station->record_type = NORMAL_APRS;
5578                 found_pos = 0;
5579                 break;
5580
5581             case (APRS_STATUS):         // '>' Status Reports     [APRS Reference, chapter 16]
5582
5583                 (void)extract_time(p_station, data, type);              // we need a time
5584                 // todo: could contain Maidenhead or beam heading+power
5585
5586                 if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
5587                     extract_multipoints(p_station, data, type, 1);
5588  
5589                 add_status(p_station,data);
5590
5591                 p_station->flag |= (ST_STATUS);                         // set "Status" flag
5592                 p_station->record_type = NORMAL_APRS;                   // ???
5593                 found_pos = 0;
5594                 break;
5595
5596             case (OTHER_DATA):          // Other Packets          [APRS Reference, chapter 19]
5597
5598                 // non-APRS beacons, treated as status reports until we get a real one
5599
5600                 if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
5601                     extract_multipoints(p_station, data, type, 1);
5602  
5603                 if ((p_station->flag & (~ST_STATUS)) == 0) {            // only store if no status yet
5604
5605                     add_status(p_station,data);
5606
5607                     p_station->record_type = NORMAL_APRS;               // ???
5608                 }
5609                 found_pos = 0;
5610                 break;
5611
5612             case (APRS_OBJECT):
5613
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.
5619                 //
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.
5624                 //
5625                 // The new record we're processing is an active
5626                 // object, as data_add() won't be called on a killed
5627                 // object.
5628                 //
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) );
5636                 }
5637  
5638                 ok = extract_time(p_station, data, type);               // we need a time
5639                 if (ok) {
5640                     if (!extract_position(p_station,&data,type)) {      // uncompressed lat/lon
5641                         compr_pos = 1;
5642                         if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
5643                             ok = 0;
5644                         else
5645                             p_station->pos_amb = 0; // No ambiguity in compressed posits
5646                     }
5647                 }
5648                 p_station->flag |= ST_OBJECT;                           // Set "Object" flag
5649                 if (ok) {
5650
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.
5655
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
5658   // TODO                  
5659 /*
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
5662
5663                         disown_object_item(call_sign, origin);
5664
5665                     }
5666 */
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);
5678                     }
5679  
5680                     // Create a timestamp from the current time
5681                     xastir_snprintf(p_station->pos_time,
5682                         sizeof(p_station->pos_time),
5683                         "%s",
5684                         get_time(temp_data));
5685
5686                     xastir_snprintf(p_station->origin,
5687                         sizeof(p_station->origin),
5688                         "%s",
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
5694
5695                     if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
5696                         extract_multipoints(p_station, data, type, 0);
5697  
5698                     add_comment(p_station,data);
5699
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
5706
5707                     // Assign a non-default value for the error
5708                     // ellipse?
5709                     if (!compr_pos) {
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;
5713                     }
5714                     else {
5715                         p_station->error_ellipse_radius = 600; // Default of 6m
5716                         p_station->lat_precision = 6;
5717                         p_station->lon_precision = 6;
5718                     }
5719                 }
5720                 break;
5721
5722             case (APRS_ITEM):
5723
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.
5729                 //
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.
5734                 //
5735                 // The new record we're processing is an active
5736                 // Item, as data_add() won't be called on a killed
5737                 // Item.
5738                 //
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
5743  
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) );
5747                 }
5748  
5749                 if (!extract_position(p_station,&data,type)) {          // uncompressed lat/lon
5750                     compr_pos = 1;
5751                     if (!extract_comp_position(p_station,&data,type))   // compressed lat/lon
5752                         ok = 0;
5753                     else
5754                         p_station->pos_amb = 0; // No ambiguity in compressed posits
5755                 }
5756                 p_station->flag |= ST_ITEM;                             // Set "Item" flag
5757                 if (ok) {
5758
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.
5763 // TODO
5764 /*
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
5769
5770                         disown_object_item(call_sign,origin);
5771                     }
5772  */
5773
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);
5785                     }
5786  
5787                     // Create a timestamp from the current time
5788                     xastir_snprintf(p_station->pos_time,
5789                         sizeof(p_station->pos_time),
5790                         "%s",
5791                         get_time(temp_data));
5792                     xastir_snprintf(p_station->origin,
5793                         sizeof(p_station->origin),
5794                         "%s",
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
5800
5801                     if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
5802                         extract_multipoints(p_station, data, type, 0);
5803  
5804                     add_comment(p_station,data);
5805
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
5812
5813                     // Assign a non-default value for the error
5814                     // ellipse?
5815                     if (!compr_pos) {
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;
5819                     }
5820                     else {
5821                         p_station->error_ellipse_radius = 600; // Default of 6m
5822                         p_station->lat_precision = 6;
5823                         p_station->lon_precision = 6;
5824                     }
5825                 }
5826                 break;
5827
5828             case (APRS_WX1):    // weather in '@' or '/' packet
5829
5830                 ok = extract_time(p_station, data, type);               // we need a time
5831                 if (ok) {
5832                     if (!extract_position(p_station,&data,type)) {      // uncompressed lat/lon
5833                         compr_pos = 1;
5834                         if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
5835                             ok = 0;
5836                         else
5837                             p_station->pos_amb = 0; // No ambiguity in compressed posits
5838                     }
5839                 }
5840                 if (ok) {
5841
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;
5845
5846                     process_info_field(p_station,data,type);            // altitude
5847
5848                     if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
5849                         extract_multipoints(p_station, data, type, 1);
5850  
5851                     add_comment(p_station,data);
5852
5853                     // Assign a non-default value for the error
5854                     // ellipse?
5855                     if (!compr_pos) {
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;
5859                     }
5860                     else {
5861                         p_station->error_ellipse_radius = 600; // Default of 6m
5862                         p_station->lat_precision = 6;
5863                         p_station->lon_precision = 6;
5864                     }
5865                 }
5866                 break;
5867
5868             case (APRS_WX2):            // '_'
5869
5870                 ok = extract_time(p_station, data, type);               // we need a time
5871                 if (ok) {
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;
5875                     found_pos = 0;
5876
5877                     process_info_field(p_station,data,type);            // altitude
5878
5879                     if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
5880                         extract_multipoints(p_station, data, type, 1);
5881                 }
5882                 break;
5883
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)
5888
5889                 
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
5893                     {
5894                         decode_U2000_L (1,(unsigned char *)data,weather);
5895                     }
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),
5904                         "%s",
5905                         get_time(temp_data));
5906                     weather->wx_sec_time = sec_now();
5907                     found_pos = 0;
5908                 }
5909
5910                 break;
5911
5912
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
5918
5919
5920             case (GPS_RMC):             // $GPRMC
5921
5922 // WE7U
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);
5926
5927                 if (ok) {
5928                     // Assign a non-default value for the error
5929                     // ellipse?
5930 //
5931 // WE7U
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.
5936 //
5937                     switch (num_digits) {
5938
5939                         case 0:
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;
5943                             break;
5944
5945                         case 1:
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;
5949                             break;
5950
5951                         case 2:
5952                             p_station->error_ellipse_radius = 600; // Default of 6m
5953                             p_station->lat_precision = 60;
5954                             p_station->lon_precision = 60;
5955                             break;
5956
5957                         case 3:
5958                             p_station->error_ellipse_radius = 600; // Default of 6m
5959                             p_station->lat_precision = 6;
5960                             p_station->lon_precision = 6;
5961                             break;
5962
5963                         case 4:
5964                         case 5:
5965                         case 6:
5966                         case 7:
5967                             p_station->error_ellipse_radius = 600; // Default of 6m
5968                             p_station->lat_precision = 0;
5969                             p_station->lon_precision = 0;
5970                             break;
5971
5972                         default:
5973                             p_station->error_ellipse_radius = 600; // Default of 6m
5974                             p_station->lat_precision = 60;
5975                             p_station->lon_precision = 60;
5976                             break;
5977                     }
5978                 }
5979                 break;
5980
5981
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
5987 // 
5988 // 
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
5994
5995
5996             case (GPS_GGA):             // $GPGGA
5997
5998 // WE7U
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);
6002
6003                 if (ok) {
6004                     // Assign a non-default value for the error
6005                     // ellipse?
6006 //
6007 // WE7U
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.
6012 //
6013                      switch (num_digits) {
6014
6015                         case 0:
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;
6019                             break;
6020
6021                         case 1:
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;
6025                             break;
6026
6027                         case 2:
6028                             p_station->error_ellipse_radius = 600; // Default of 6m
6029                             p_station->lat_precision = 60;
6030                             p_station->lon_precision = 60;
6031                             break;
6032
6033                         case 3:
6034                             p_station->error_ellipse_radius = 600; // Default of 6m
6035                             p_station->lat_precision = 6;
6036                             p_station->lon_precision = 6;
6037                             break;
6038
6039                         case 4:
6040                         case 5:
6041                         case 6:
6042                         case 7:
6043                             p_station->error_ellipse_radius = 600; // Default of 6m
6044                             p_station->lat_precision = 0;
6045                             p_station->lon_precision = 0;
6046                             break;
6047
6048                         default:
6049                             p_station->error_ellipse_radius = 600; // Default of 6m
6050                             p_station->lat_precision = 60;
6051                             p_station->lon_precision = 60;
6052                             break;
6053                     }
6054                 }
6055                 break;
6056
6057
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
6063
6064
6065             case (GPS_GLL):             // $GPGLL
6066                 ok = extract_GLL(p_station,data,call_sign,path,&num_digits);
6067
6068                 if (ok) {
6069                     // Assign a non-default value for the error
6070                     // ellipse?
6071 //
6072 // WE7U
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
6075 // give it 600.
6076 //
6077                      switch (num_digits) {
6078
6079                         case 0:
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;
6083                             break;
6084  
6085                         case 1:
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;
6089                             break;
6090
6091                         case 2:
6092                             p_station->error_ellipse_radius = 600; // Default of 6m
6093                             p_station->lat_precision = 60;
6094                             p_station->lon_precision = 60;
6095                             break;
6096
6097                         case 3:
6098                             p_station->error_ellipse_radius = 600; // Default of 6m
6099                             p_station->lat_precision = 6;
6100                             p_station->lon_precision = 6;
6101                             break;
6102
6103                         case 4:
6104                         case 5:
6105                         case 6:
6106                         case 7:
6107                             p_station->error_ellipse_radius = 600; // Default of 6m
6108                             p_station->lat_precision = 0;
6109                             p_station->lon_precision = 0;
6110                             break;
6111
6112                         default:
6113                             p_station->error_ellipse_radius = 600; // Default of 6m
6114                             p_station->lat_precision = 60;
6115                             p_station->lon_precision = 60;
6116                             break;
6117                     }
6118  
6119                 }
6120                 break;
6121
6122             default:
6123
6124                 fprintf(stderr,"ERROR: UNKNOWN TYPE in data_add\n");
6125                 ok = 0;
6126                 break;
6127         }
6128
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
6138
6139             extract_multipoints(p_station, data, type, 0);
6140         }
6141     }
6142
6143     if (!ok) {  // non-APRS beacon, treat it as Other Packet   [APRS Reference, chapter 19]
6144
6145         // GPRMC etc. without a position is here too, but it should not be stored as status!
6146
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
6149  
6150             add_status(p_station,data-1);
6151
6152             p_station->record_type = NORMAL_APRS;               // ???
6153  
6154         }
6155  
6156         ok = 1;            
6157         found_pos = 0;
6158     }
6159     
6160     curr_sec = sec_now();
6161     if (ok) {
6162
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
6166         if (!new_station
6167                 && is_my_station(p_station) // Check SSID as well
6168                 && strchr(path,'*') != NULL) {
6169  
6170             upd_echo(path);   // store digi that echoes my signal...
6171             statusline("Echo from digipeater",0);   // Echo from digipeater
6172
6173         }
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)
6179  
6180             store = 1;                     // don't store secondary echos
6181     }
6182     
6183     if (!ok && new_station)
6184         delete_station_memory(p_station);       // remove unused record
6185  
6186     if (store) {
6187  
6188         // we now have valid data to store into database
6189         // make time index unique by adding a serial number
6190
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
6196             // up.
6197             //
6198             p_station->flag |= ST_MYSTATION;
6199         }
6200         
6201         // Check whether it's a locally-owned object/item
6202         if ( object_is_mine
6203                 || (   new_origin_is_mine
6204                     && (p_station->flag & ST_ACTIVE)
6205                     && (p_station->flag & ST_OBJECT) ) ) {
6206
6207             p_station->flag |= ST_MYOBJITEM;
6208
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.
6212             //
6213             if (new_station) {
6214                 p_station->sec_heard = curr_sec;
6215             }
6216
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?.
6224         }
6225         else {
6226             // Reset the "my object" flag
6227             p_station->flag &= ~ST_MYOBJITEM;
6228
6229             p_station->sec_heard = curr_sec;    // Give it a new timestamp
6230         }
6231         
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
6235         }
6236
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;
6243             }
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.
6248             }
6249         }
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;
6255             }
6256         }
6257         p_station->last_port_heard = port;
6258         
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
6262          
6263         p_station->flag |= ST_ACTIVE;
6264         if (third_party)
6265             p_station->flag |= ST_3RD_PT;               // set "third party" flag
6266         else
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),
6271                 "%s",
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),
6276                 "%s",
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
6280
6281
6282         //--------------------------------------------------------------------
6283
6284         // KC2ELS
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.
6293
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.
6299
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
6304
6305             // Look for WIDE or TRACE
6306             if ((((p = strstr(path,"WIDE")) != NULL) 
6307                     && (p+=4)) || 
6308                     (((p = strstr(path,"TRACE")) != NULL) 
6309                     && (p+=5))) {
6310
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)) {
6316                                 direct = 1;
6317                             }
6318                             else {
6319                                 direct = 0;
6320                             }
6321                         }
6322                         else {
6323                             direct = 0;
6324                         }
6325                     }
6326                     else {
6327                         direct = 0;
6328                     }
6329                 }
6330                 else {
6331                     direct = 1;
6332                 }
6333             }
6334             else {
6335                 direct = 1;
6336             }
6337         }
6338         else {
6339             direct = 0;
6340         }
6341         
6342         
6343         if (direct == 1) {
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);
6348         }
6349         else {
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.
6355
6356             if ((p_station->flag & ST_DIRECT) != 0 &&
6357                     curr_sec > (p_station->direct_heard + st_direct_timeout)) {
6358                 p_station->flag &= (~ST_DIRECT);
6359             }
6360         }
6361
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
6366         //     direct recently)
6367         //     *) ST_DIRECT is set, st_direct_timeout has expired
6368         //     (packet hasn't been heard direct recently)
6369         //
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.
6373         //
6374         if ((port == APRS_PORT_TTY)  // Heard via TNC
6375                 && !third_party     // Not a 3RD-Party packet
6376                 && path != NULL) {  // Path is not NULL
6377
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
6381             // variable.
6382             //
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) ) ) ) {
6387
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);
6394
6395                 substr(p_station->node_path_ptr,path,strlen(path));
6396             }
6397         }
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.
6404         //
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
6408
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.
6414             //
6415             if (curr_sec > (p_station->heard_via_tnc_last_time + 60*60)) {
6416
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
6420                 // now.
6421
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);
6428
6429                 substr(p_station->node_path_ptr,path,strlen(path));
6430
6431                 // Clear the ST_VIATNC bit
6432                 p_station->flag &= ~ST_VIATNC;
6433             }
6434
6435             // If direct_heard is over an hour old, clear the
6436             // ST_DIRECT flag.  We're only hearing this station on
6437             // INET now.
6438             // 
6439             if (curr_sec > (p_station->direct_heard + st_direct_timeout)) {
6440
6441                 // Yep, more than one hour old or is a zero, clear
6442                 // the ST_DIRECT flag.
6443                 p_station->flag &= ~ST_DIRECT;
6444             }
6445         }
6446  
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.
6452         //
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
6456                 
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.
6462             //
6463             if (curr_sec > (p_station->heard_via_tnc_last_time + 60*60)) {
6464
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
6468                 // now.
6469
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);
6476
6477                 substr(p_station->node_path_ptr,path,strlen(path));
6478
6479                 // Clear the ST_VIATNC bit
6480                 p_station->flag &= ~ST_VIATNC;
6481
6482             }
6483             
6484             // If direct_heard is over an hour old, clear the
6485             // ST_DIRECT flag.  We're only hearing this station on
6486             // INET now.
6487             // 
6488             if (curr_sec > (p_station->direct_heard + st_direct_timeout)) {
6489
6490                 // Yep, more than one hour old or is a zero, clear
6491                 // the ST_DIRECT flag.
6492                 p_station->flag &= ~ST_DIRECT;
6493             }
6494         }
6495  
6496  
6497         //---------------------------------------------------------------------
6498
6499         p_station->num_packets += 1;
6500
6501
6502         screen_update = 0;
6503         if (new_station) {
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
6506                 moving = 1;
6507             }
6508
6509             if (p_station->coord_lat != 0 && p_station->coord_lon != 0) {   // discard undef positions from screen
6510                 if (!altnet || is_altnet(p_station) ) {
6511
6512                     screen_update = 1;
6513                     // TODO - check needed
6514                 }
6515             
6516             }
6517         }
6518         else {        // we had seen this station before...
6519                 
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
6523                 }
6524                 else {
6525                     if (strlen(p_station->speed) > 0 && atof(p_station->speed) > 0) {
6526                         moving = 1;                     // declare it moving, if it has a speed
6527                     }
6528                     else {
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
6533                         }
6534                         else {
6535                             moving = 0;
6536                         }
6537                     }
6538                 }
6539                 changed_pos = 0;
6540                 if (moving == 1) {                      
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,
6548                                                         last_lon,
6549                                                         last_lat,
6550                                                         last_stn_sec,
6551                                                         last_alt,
6552                                                         last_speed,
6553                                                         last_course,
6554                                                         last_flag);
6555                             }
6556                         }
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
6560                                                 // later ?)
6561                                                 //
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
6567                         // discarded.
6568                         //
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,
6575                                                     p_station->speed,
6576                                                     p_station->course,
6577                                                     p_station->flag);
6578                             changed_pos = 1;
6579
6580                             // Check whether it's a locally-owned object/item
6581                             if (object_is_mine) {
6582
6583                                 // Update time, change position in
6584                                 // time-sorted list to change
6585                                 // expiration time.
6586                                 move_station_time(p_station,p_time);
6587
6588                                 // Give it a new timestamp
6589                                 p_station->sec_heard = curr_sec;
6590                                 //fprintf(stderr,"Updating last heard time\n");
6591                             }
6592                         }
6593                     }
6594
6595 //                    if (track_station_on == 1)          // maybe we are tracking a station
6596 //                        track_station(da,tracking_station_call,p_station);
6597                 } // moving...
6598
6599                 // now do the drawing to the screen
6600                 ok_to_display = !altnet || is_altnet(p_station); // Optimization step, needed twice below.
6601 // TODO
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;
6607 //                    }
6608
6609                 }
6610
6611                 if (changed_pos == 1 || !position_defined(last_lat,last_lon,0)) {
6612                     if (ok_to_display) {
6613
6614                         screen_update = 1; // TODO -check needed
6615                     }
6616
6617                 }
6618             } // defined position
6619         }
6620     
6621         if (screen_update) {
6622             if (p_station->last_port_heard == APRS_PORT_TTY) {   // Data from local TNC
6623
6624                 redraw_on_new_data = 2; // Update all symbols NOW!
6625             }
6626           else {
6627                 redraw_on_new_data = 0; // Update each 60 secs
6628           }
6629      }
6630
6631
6632     if(ok)
6633     {
6634
6635         plot_aprs_station( p_station, TRUE );
6636
6637     }
6638
6639     return(ok);
6640 }   // End of data_add() function
6641
6642
6643 int is_xnum_or_dash(char *data, int max){
6644     int i;
6645     int ok;
6646
6647     ok=1;
6648     for(i=0; i<max;i++)
6649         if(!(isxdigit((int)data[i]) || data[i]=='-')) {
6650             ok=0;
6651             break;
6652         }
6653
6654     return(ok);
6655 }
6656
6657
6658
6659 /*
6660  *  Decode Mic-E encoded data
6661  */
6662 int decode_Mic_E(char *call_sign,char *path,char *info,int port,int third_party) {
6663     int  ii;
6664     int  offset;
6665     unsigned char s_b1;
6666     unsigned char s_b2;
6667     unsigned char s_b3;
6668     unsigned char s_b4;
6669     unsigned char s_b5;
6670     unsigned char s_b6;
6671     unsigned char s_b7;
6672     int  north,west,long_offset;
6673     int  d,m,h;
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
6676     int  course;
6677     int  speed;
6678     int  msg1,msg2,msg3,msg;
6679     int  info_size;
6680     long alt;
6681     int  msgtyp;
6682     char rig_type[10];
6683     int ok;
6684         
6685     // MIC-E Data Format   [APRS Reference, chapter 10]
6686
6687     // todo:  error check
6688     //        drop wrong positions from receive errors...
6689     //        drop 0N/0E position (p.25)
6690     
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.        */
6695     /*
6696
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
6707     in the Info field).
6708     C = Command/Response flag (see AX.25 specification).
6709     r = reserved for future use (currently 0).
6710     
6711     */
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).    *
6717     *                                                                           *
6718     * DK7IN : lat/long with custom msg works, altitude/course/speed works       *
6719     *****************************************************************************/
6720
6721
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 '\'
6725     // or 0-9, A-Z.
6726     //
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
6731
6732         // We're good, keep going
6733
6734     }
6735     else { // Symbol table or overlay char incorrect
6736
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.
6742         }
6743
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.
6746     }
6747
6748     // Check for valid symbol.  Should be between '!' and '~' only.
6749     if (info[6] < '!' || info[6] > '~') {
6750
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.
6753     }
6754
6755     // Check for minimum MIC-E size.
6756     if (strlen(info) < 8) {
6757
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.
6760     }
6761
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);
6769             return(1);
6770         }
6771     }
6772
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) {
6776
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
6780         }
6781         else {  // 8-bit telemetry flag was not found.  Check that
6782                 // we only have 7-bit characters through the rest of
6783                 // the packet.
6784
6785             for (ii = 8; ii < (int)strlen(info); ii++) {
6786
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);
6790                     return(1);
6791                 }
6792             }
6793         }
6794     }
6795
6796     //fprintf(stderr,"Path1:%s\n",path);
6797
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
6804
6805     //fprintf(stderr,"Msg: %d\n",msg);
6806
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;
6813
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);
6817  
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;
6822
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);
6826  
6827     s_b3 = (unsigned char)( (path[2] & (char)0x0f) + (char)0x2f );
6828     if (path[2] & 0x10)     // A-J
6829         s_b3 += (unsigned char)1;
6830
6831     if (s_b3 > (unsigned char)0x39)        // K,L,Z
6832         s_b3 = (unsigned char)0x20;
6833  
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;
6837
6838  
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;
6842
6843  
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);
6849  
6850     s_b7 =  (unsigned char)path[6];        // SSID, not used here
6851     //fprintf(stderr,"path6:%c\ts_b7:%c\n",path[6],s_b7);
6852  
6853     //fprintf(stderr,"\n");
6854
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.
6862     if (path[3] == 'L')
6863         north = 0;
6864     else 
6865         north = (int)((path[3] & 0x40) == (char)0x40);  // N/S Lat Indicator
6866
6867     if (path[4] == 'L')
6868         long_offset = 0;
6869     else
6870         long_offset = (int)((path[4] & 0x40) == (char)0x40);  // Longitude Offset
6871
6872     if (path[5] == 'L')
6873         west = 0;
6874     else
6875         west = (int)((path[5] & 0x40) == (char)0x40);  // W/E Long Indicator
6876
6877     //fprintf(stderr,"north:%c->%d\tlat:%c->%d\twest:%c->%d\n",path[3],north,path[4],long_offset,path[5],west);
6878
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
6882
6883     /* Compute degrees longitude */
6884     snprintf(new_info, sizeof(new_info), "%s", temp);
6885     d = (int) info[0]-28;
6886
6887     if (long_offset)
6888         d += 100;
6889
6890     if ((180<=d)&&(d<=189))  // ??
6891         d -= 80;
6892
6893     if ((190<=d)&&(d<=199))  // ??
6894         d -= 190;
6895
6896     /* Compute minutes longitude */
6897     m = (int) info[1]-28;
6898     if (m>=60)
6899         m -= 60;
6900
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]);
6905     strncat(new_info,
6906         temp,
6907         sizeof(new_info) - strlen(new_info));
6908
6909     /* Compute speed in knots */
6910     speed = (int)( ( info[3] - (char)28 ) * (char)10 );
6911     speed += ( (int)( (info[4] - (char)28) / (char)10) );
6912     if (speed >= 800)
6913         speed -= 800;       // in knots
6914
6915     /* Compute course */
6916     course = (int)( ( ( (info[4] - (char)28) % 10) * (char)100) + (info[5] - (char)28) );
6917     if (course >= 400)
6918         course -= 400;
6919
6920     /*  ???
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 );
6923     */
6924     xastir_snprintf(temp, sizeof(temp), "%03d/%03d",course,speed);
6925     strncat(new_info,
6926         temp,
6927         sizeof(new_info) - strlen(new_info));
6928     offset = 8;   // start of rest of info
6929
6930     /* search for rig type in Mic-E data */
6931     rig_type[0] = '\0';
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,
6936                 sizeof(rig_type),
6937                 " TH-D7");
6938         else
6939             xastir_snprintf(rig_type,
6940                 sizeof(rig_type),
6941                 " TM-D700");
6942
6943         offset++;
6944     }
6945
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
6953
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.
6957             offset += 4;
6958         }
6959         else {  // Altitude is ok
6960             xastir_snprintf(temp, sizeof(temp), " /A=%06ld",alt);
6961             offset += 4;
6962             strncat(new_info,
6963                 temp,
6964                 sizeof(new_info) - strlen(new_info));
6965         }
6966     }
6967
6968     /* start of comment */
6969     if (strlen(rig_type) > 0) {
6970         xastir_snprintf(temp, sizeof(temp), "%s",rig_type);
6971         strncat(new_info,
6972             temp,
6973             sizeof(new_info) - strlen(new_info));
6974     }
6975
6976     strncat(new_info,
6977         " Mic-E ",
6978         sizeof(new_info) - strlen(new_info));
6979     if (msgtyp == 0) {
6980         switch (msg) {
6981             case 1:
6982                 strncat(new_info,
6983                     "Enroute",
6984                     sizeof(new_info) - strlen(new_info));
6985                 break;
6986
6987             case 2:
6988                 strncat(new_info,
6989                     "In Service",
6990                     sizeof(new_info) - strlen(new_info));
6991                 break;
6992
6993             case 3:
6994                 strncat(new_info,
6995                     "Returning",
6996                     sizeof(new_info) - strlen(new_info));
6997                 break;
6998
6999             case 4:
7000                 strncat(new_info,
7001                     "Committed",
7002                     sizeof(new_info) - strlen(new_info));
7003                 break;
7004
7005             case 5:
7006                 strncat(new_info,
7007                     "Special",
7008                     sizeof(new_info) - strlen(new_info));
7009                 break;
7010
7011             case 6:
7012                 strncat(new_info,
7013                     "Priority",
7014                     sizeof(new_info) - strlen(new_info));
7015                 break;
7016
7017             case 7:
7018                 strncat(new_info,
7019                     "Emergency",
7020                     sizeof(new_info) - strlen(new_info));
7021
7022                 // Functionality removed
7023
7024                 break;
7025
7026             default:
7027                 strncat(new_info,
7028                     "Off Duty",
7029                     sizeof(new_info) - strlen(new_info));
7030         }
7031     }
7032     else {
7033         xastir_snprintf(temp, sizeof(temp), "Custom%d",msg);
7034         strncat(new_info,
7035             temp,
7036             sizeof(new_info) - strlen(new_info));
7037     }
7038
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];
7043
7044         temp[info_size-offset] = '\0';
7045         strncat(new_info,
7046             " ",
7047             sizeof(new_info) - strlen(new_info));
7048         strncat(new_info,
7049             temp,
7050             sizeof(new_info) - strlen(new_info));
7051     }
7052
7053
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.
7057     //
7058     ok = data_add(APRS_MICE,call_sign,path,new_info,port,NULL,third_party, 0, 0);
7059
7060
7061     return(ok);
7062 }   // End of decode_Mic_E()
7063
7064
7065
7066
7067 void delete_object(char *name) {
7068         
7069         // TODO 
7070 /*
7071         
7072         DataRow *p_station;
7073
7074 //fprintf(stderr,"delete_object\n");
7075
7076     p_station = NULL;
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
7085     }
7086     */
7087 }
7088
7089
7090 /*
7091  *  Decode APRS Information Field and dispatch it depending on the Data Type ID
7092  *
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
7101  *
7102  */
7103 void decode_info_field(gchar *call,
7104                        gchar *path,
7105                        gchar *message,
7106                        gchar *origin, 
7107                        TAprsPort port,
7108                        gint third_party,
7109                        gchar *orig_message) 
7110 {
7111 //    char line[MAX_LINE_SIZE+1];
7112     int  ok_igate_net;
7113     int  ok_igate_rf;
7114     int  done, ignore;
7115     char data_id;
7116     int station_is_mine = 0;
7117     int object_is_mine = 0;
7118
7119     /* remember fixed format starts with ! and can be up to 24 chars in the message */ // ???
7120
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
7125
7126     if ( is_my_call(call, 1) ) {
7127         station_is_mine++; // Station is controlled by me
7128     }
7129  
7130     if ( (message != NULL) && (strlen(message) > MAX_LINE_SIZE) ) { 
7131         // Overly long message, throw it away.
7132         done = 1;
7133     }
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);
7137         done = 1;                                       
7138         // don't report it to internet
7139     }
7140
7141     // special treatment for objects/items.
7142     if (!done && origin[0] != '\0') {
7143
7144         // If station/object/item is owned by me (including SSID)
7145         if ( is_my_call(origin, 1) ) {
7146             object_is_mine++;
7147         }
7148          
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
7153             }
7154             ok_igate_rf = 1;
7155             done = 1;
7156         }
7157
7158         else if (message[0] == '!') {   
7159                 // set item
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
7163             }
7164             ok_igate_rf = 1;
7165             done = 1;
7166         }
7167
7168         else if (message[0] == '_') {   // delete object/item
7169 // TODO
7170 /*
7171             AprsDataRow *p_station;
7172
7173             delete_object(call);    // ?? does not vanish from map immediately !!???
7174
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
7178             // anymore.
7179             p_station = NULL;
7180
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);
7185                 }
7186             }
7187
7188             if (strlen(origin) > 0 && strncmp(origin,"INET",4)!=0) {
7189                 ok_igate_net = 1;   // report it to internet
7190             }
7191
7192             ok_igate_rf = 1;
7193 */
7194             done = 1;
7195             
7196         }
7197     }
7198     
7199     if (!done) {
7200         int rdf_type;
7201
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
7205
7206
7207         switch (data_id) {
7208             case '=':   // Position without timestamp (with APRS messaging)
7209
7210                 //WE7U
7211                 // Need to check for weather info in this packet type as well?
7212
7213                 done = data_add(APRS_MSGCAP,call,path,message,port,origin,third_party, station_is_mine, 0);
7214                 ok_igate_rf = done;
7215                 break;
7216
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);
7220                 else
7221                     done = data_add(APRS_FIXED,call,path,message,port,origin,third_party, station_is_mine, 0);
7222                 ok_igate_rf = done;
7223                 break;
7224
7225             case '/':   // Position with timestamp (no APRS messaging)
7226
7227 //WE7U
7228 // Need weather decode in this section similar to the '@' section
7229 // below.
7230
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);
7236                         else
7237                             done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
7238                     }
7239                     else
7240                         done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
7241                 }
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);
7246                         else
7247                             done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
7248                     }
7249                     else
7250                         done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
7251                 }
7252 //                done = data_add(APRS_DOWN,call,path,message,from,port,origin,third_party, station_is_mine, 0);
7253                 ok_igate_rf = done;
7254                 break;
7255
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);
7263                         else
7264                             done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
7265                     }
7266                     else
7267                         done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
7268                 }
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);
7273                         else
7274                             done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
7275                     }
7276                     else
7277                         done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
7278                 }
7279                 ok_igate_rf = done;
7280                 break;
7281
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);
7284                 ok_igate_rf = done;
7285                 break;
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);
7291                 ok_igate_rf = done;
7292                 break;
7293
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);
7296                 ok_igate_rf = done;
7297                 break;
7298
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);
7302                 ok_igate_rf = done;
7303                 break;
7304
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);
7308                 ok_igate_rf = done;
7309                 break;
7310
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);
7320                 else {
7321                         // handle VTG and WPT too  (APRS Ref p.25)
7322                 }
7323                 ok_igate_rf = done;
7324                 break;
7325
7326             case ':':   // Message
7327
7328                 // Do message logging if that feature is enabled.
7329                 done = decode_message(call,path,message,port,third_party);
7330
7331                 // there could be messages I should not retransmit to internet... ??? Queries to me...
7332                 break;
7333
7334             case '>':   // Status                                   [APRS Reference, chapter 16]
7335                 done = data_add(APRS_STATUS,call,path,message,port,origin,third_party, station_is_mine, 0);
7336                 ok_igate_rf = done;
7337                 break;
7338
7339             case '?':   // Query
7340                 done = process_query(call,path,message,port,third_party);
7341                 ignore = 1;     // don't treat undecoded packets as status text
7342                 break;
7343
7344             case 'T':   // Telemetry data                           [APRS Reference, chapter 13]
7345                 // We treat these as status packets currently.
7346                 ok_igate_rf = 1;
7347                 done = data_add(APRS_STATUS,call,path,message,port,origin,third_party, station_is_mine, 0);
7348                 break;
7349  
7350             case '{':   // User-defined APRS packet format     //}
7351                 // We treat these as status packets currently.
7352                 ok_igate_rf = 1;
7353                 break;
7354  
7355             case '<':   // Station capabilities                     [APRS Reference, chapter 15]
7356                 //
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
7369                 // station.
7370                 //
7371                 //fprintf(stderr,"%10s:  %s\n", call, message);
7372
7373                 // Don't set "done" as we want these to appear in
7374                 // the status text for the record.
7375                 break;
7376
7377             case '%':   // Agrelo DFJr / MicroFinder Radio Direction Finding
7378
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.
7382
7383 //
7384 // Agrelo format:  "%XXX/Q<cr>"
7385 //
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
7389 // north.
7390 //
7391 // "Q" is bearing quality (0-9).  0 = unsuitable.  9 = manually
7392 // entered.  1-8 = varying quality with 8 being the best.
7393 //
7394 // I've also seen these formats, which may not be Agrelo compatible:
7395 //
7396 //      "%136.0/9"
7397 //      "%136.0/8/158.0" (That last number is magnetic bearing)
7398 //
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.
7402 //
7403
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]) ) {
7411
7412                     rdf_type = 1;
7413
7414                     fprintf(stderr,
7415                         "Type 1 RDF packet from call: %s\tBearing: %c%c%c\tQuality: %c\n",
7416                         call,
7417                         message[0],
7418                         message[1],
7419                         message[2],
7420                         message[4]);
7421  
7422                 }
7423
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]) ) {
7440
7441                     rdf_type = 3;
7442
7443                     fprintf(stderr,
7444                         "Type 3 RDF packet from call: %s\tBearing: %c%c%c%c%c\tQuality: %c\tMag Bearing: %c%c%c%c%c\n",
7445                         call,
7446                         message[0],
7447                         message[1],
7448                         message[2],
7449                         message[3],
7450                         message[4],
7451                         message[6],
7452                         message[8],
7453                         message[9],
7454                         message[10],
7455                         message[11],
7456                         message[12]);
7457                 }
7458
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]) ) {
7469
7470                     rdf_type = 2;
7471
7472                     fprintf(stderr,
7473                         "Type 2 RDF packet from call: %s\tBearing: %c%c%c%c%c\tQuality: %c\n",
7474                         call,
7475                         message[0],
7476                         message[1],
7477                         message[2],
7478                         message[3],
7479                         message[4],
7480                         message[6]);
7481                 }
7482
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.
7486                 break;
7487  
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
7492                 break;
7493         }
7494
7495         
7496         // Add most remaining data to the station record as status
7497         // info
7498         //
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       ????
7502         }
7503
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
7507         }
7508     }
7509     
7510     if (third_party)
7511         ok_igate_net = 0;   // don't put third party traffic on internet
7512
7513
7514     if (station_is_mine)
7515         ok_igate_net = 0;   // don't put my data on internet     ???
7516     
7517     
7518     // TODO - Add TX to IGate support
7519 /*
7520     if (ok_igate_net) {
7521
7522         if ( (from == DATA_VIA_TNC) // Came in via a TNC
7523                 && (strlen(orig_message) > 0) ) { // Not empty
7524
7525             // Here's where we inject our own callsign like this:
7526             // "WE7U-15,I" in order to provide injection ID for our
7527             // igate.
7528             snprintf(line,
7529                 sizeof(line),
7530                 "%s>%s,%s,I:%s",
7531                 (strlen(origin)) ? origin : call,
7532                 path,
7533                 _aprs_mycall,
7534                 orig_message);
7535
7536             output_igate_net(line, port, third_party);
7537         }
7538     }
7539 */
7540 }
7541
7542
7543
7544 /*
7545  *  Check for a valid object name
7546  */
7547 int valid_object(char *name) {
7548     int len, i;
7549
7550     // max 9 printable ASCII characters, case sensitive   [APRS
7551     // Reference]
7552     len = (int)strlen(name);
7553     if (len > 9 || len == 0)
7554         return(0);                      // wrong size
7555
7556     for (i=0;i<len;i++)
7557         if (!isprint((int)name[i]))
7558             return(0);                  // not printable
7559
7560     return(1);
7561 }
7562
7563
7564
7565
7566
7567
7568 /*
7569  *  Extract object or item data from information field before processing
7570  *
7571  *  Returns 1 if valid object found, else returns 0.
7572  *
7573  */
7574 int extract_object(char *call, char **info, char *origin) 
7575 {
7576     int ok, i;
7577
7578     // Object and Item Reports     [APRS Reference, chapter 11]
7579     ok = 0;
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
7590                 ok = 1;
7591             }
7592         }
7593     }
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] == '_') {
7599                     call[i-1] = '\0';
7600                     break;
7601                 }
7602                 call[i-1] = (*info)[i];
7603             }
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
7610                 ok = 1;
7611             }
7612         }
7613     }
7614     else 
7615     {
7616         fprintf(stderr,"Not an object, nor an item!!! call=%s, info=%s, origin=%s.\n",
7617                call, *info, origin);
7618     }
7619     return(ok);
7620 }
7621
7622
7623 /*
7624  *  Decode AX.25 line
7625  *  \r and \n should already be stripped from end of line
7626  *  line should not be NULL
7627  *
7628  * If dbadd is set, add to database.  Otherwise, just return true/false
7629  * to indicate whether input is valid AX25 line.
7630  */
7631 //
7632 // Note that the length of "line" can be up to MAX_DEVICE_BUFFER,
7633 // which is currently set to 4096.
7634 //
7635 gint decode_ax25_line(gchar *line, TAprsPort port) {
7636     gchar *call_sign;
7637     gchar *path0;
7638     gchar path[100+1];           // new one, we may add an '*'
7639     gchar *info;
7640     gchar info_copy[MAX_LINE_SIZE+1];
7641     gchar call[MAX_CALLSIGN+1];
7642     gchar origin[MAX_CALLSIGN+1];
7643     gint ok;
7644     gint third_party;
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];
7649
7650     if (line == NULL) 
7651     {
7652         return(FALSE);
7653     }
7654
7655     if ( strlen(line) > MAX_LINE_SIZE ) 
7656     { 
7657         // Overly long message, throw it away.  We're done.
7658         return(FALSE);
7659     }
7660
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'
7666
7667     call_sign   = NULL;
7668     path0       = NULL;
7669     info        = NULL;
7670     origin[0]   = '\0';
7671     call[0]     = '\0';
7672     path[0]     = '\0';
7673     third_party = 0;
7674
7675     // CALL>PATH:APRS-INFO-FIELD                // split line into components
7676     //     ^    ^
7677     ok = 0;
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
7683             if (info != NULL) {
7684                 if ((info - path0) < 100) {     // check if path could be copied
7685                     ok = 1;                     // we have found all three components
7686                 }
7687             }
7688         }
7689     }
7690
7691     if (ok) 
7692     {
7693         snprintf(path, sizeof(path), "%s", path0);
7694
7695         snprintf(info_copy, sizeof(info_copy), "%s", info);
7696
7697         ok = valid_path(path);                  // check the path and convert it to TAPR format
7698         // Note that valid_path() also removes igate injection identifiers
7699     }
7700
7701     if (ok) 
7702     {
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
7706     }
7707
7708     if (ok) 
7709     {                                                   // check callsign
7710         (void)remove_trailing_asterisk(call_sign);              // is an asterisk valid here ???
7711
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);
7715         }
7716         else if (valid_call(call_sign)) 
7717         {                     // accept real AX.25 calls
7718             snprintf(call, sizeof(call), "%s", call_sign);
7719         }
7720         else {
7721             ok = 0;
7722         }
7723     }
7724
7725     if (ok && info[0] == '}') 
7726     {
7727         // look for third-party traffic
7728         ok = extract_third_party(call,path,sizeof(path),&info,origin,sizeof(origin)); // extract third-party data
7729         third_party = 1;
7730     }
7731
7732     if (ok && (info[0] == ';' || info[0] == ')')) 
7733     {             
7734         // look for objects or items
7735         snprintf(origin, sizeof(origin), "%s", call);
7736
7737         ok = extract_object(call,&info,origin);                 // extract object data
7738     }
7739
7740     if (ok) 
7741     {
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);
7745   
7746        decode_info_field(call,
7747             path,
7748             info,
7749             origin,
7750             port,
7751             third_party,
7752             info_copy);
7753     }
7754
7755
7756     return(ok);
7757 }
7758
7759
7760
7761
7762
7763 ///////////////////
7764 double convert_lon_l2d(long lon) 
7765 {
7766     double dResult = 0.00;
7767     
7768     int ewpn;
7769     float deg, min; //, sec;
7770     int ideg; //, imin;
7771     long temp;
7772
7773     deg = (float)(lon - 64800000l) / 360000.0;
7774
7775     // Switch to integer arithmetic to avoid floating-point rounding
7776     // errors.
7777     temp = (long)(deg * 100000);
7778
7779     
7780     ewpn = 1;
7781     if (temp <= 0) 
7782     {
7783         ewpn = -1;
7784         temp = labs(temp);
7785     }
7786
7787     ideg = (int)temp / 100000;
7788     min = (temp % 100000) * 60.0 / 100000.0;
7789
7790
7791     dResult = ewpn*(ideg+min/60.0);
7792
7793     return dResult;
7794 }
7795
7796
7797
7798
7799 // convert latitude from long to double 
7800 // Input is in Xastir coordinate system
7801
7802 double convert_lat_l2d(long lat) 
7803 {
7804     double dResult = 0.00;
7805
7806     int nspn;
7807     float deg, min;
7808     int ideg;
7809     long temp;
7810
7811     deg = (float)(lat - 32400000l) / 360000.0;
7812  
7813     // Switch to integer arithmetic to avoid floating-point
7814     // rounding errors.
7815     temp = (long)(deg * 100000);
7816
7817     nspn = -1;
7818     if (temp <= 0) {
7819         nspn = 1;
7820         temp = labs(temp);
7821     }   
7822
7823     ideg = (int)temp / 100000;
7824     min = (temp % 100000) * 60.0 / 100000.0;
7825
7826     dResult = nspn*(ideg+min/60.0);
7827
7828     return dResult;
7829 }
7830
7831
7832
7833 /***********************************************************/
7834 /* returns the hour (00..23), localtime                    */
7835 /***********************************************************/
7836 int get_hours(void) {
7837     struct tm *time_now;
7838     time_t secs_now;
7839     char shour[5];
7840
7841     secs_now=sec_now();
7842     time_now = localtime(&secs_now);
7843     (void)strftime(shour,4,"%H",time_now);
7844     return(atoi(shour));
7845 }
7846
7847
7848
7849
7850 /***********************************************************/
7851 /* returns the minute (00..59), localtime                  */
7852 /***********************************************************/
7853 int get_minutes(void) {
7854     struct tm *time_now;
7855     time_t secs_now;
7856     char sminute[5];
7857
7858     secs_now=sec_now();
7859     time_now = localtime(&secs_now);
7860     (void)strftime(sminute,4,"%M",time_now);
7861     return(atoi(sminute));
7862 }
7863
7864
7865
7866
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) {
7873     int current, j;
7874     float lowest;
7875
7876
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.
7880
7881
7882     current = get_minutes(); // Fetch the current minute value.  Use this as an index
7883                         // into our minute "buckets" where we store the data.
7884
7885
7886     rain_minute[ (current + 1) % 60 ] = 0.0;   // Zero out the next bucket (probably have data in
7887                                                 // there from the previous hour).
7888
7889
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
7892
7893
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
7899         }
7900     }
7901     // Found it, subtract the two to get total for the last hour
7902     rain_minute_total = rain_total - lowest;
7903 }
7904
7905
7906
7907
7908 /***********************************************************/
7909 /* compute_rain - compute rain totals from the total rain  */
7910 /* so far.  rain_total (in hundredths of an inch) keeps on */
7911 /* incrementing.                                           */
7912 /***********************************************************/
7913 void compute_rain(float rain_total) {
7914     int current, i;
7915     float lower;
7916
7917
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) )
7921         return;
7922
7923     compute_rain_hour(rain_total);
7924
7925     current = get_hours();
7926
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.
7932     }
7933     else {  // rain_base has been set, is it wrong?  We recheck three times at start of hour.
7934         if (rain_check < 3) {
7935             rain_check++;
7936             // Is rain less than base?  It shouldn't be.
7937             if (rain_total < rain_base[current])
7938                 rain_base[current] = rain_total;
7939
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;
7943         }
7944     }
7945     rain_base[ (current + 1) % 24 ] = 0.0;    // Clear next hour's index.
7946
7947
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).
7953     lower = rain_total;
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];
7957         }
7958     }
7959     rain_24 = rain_total - lower;    // Hundredths of an inch
7960
7961
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.
7964     lower = rain_total;
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];
7968         }
7969     }
7970     rain_00 = rain_total - lower;    // Hundredths of an inch
7971
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.
7975
7976 }
7977
7978
7979
7980
7981 /**************************************************************/
7982 /* compute_gust - compute max wind gust during last 5 minutes */
7983 /*                                                            */
7984 /**************************************************************/
7985 float compute_gust(float wx_speed, float last_speed, time_t *last_speed_time) {
7986     float computed_gust;
7987     int current, j;
7988
7989
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).
7993
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.
7996
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;
8001
8002         gust_read_ptr = current - 1;        // Set read pointer back one, modulus 60
8003         if (gust_read_ptr < 0)
8004             gust_read_ptr = 59;
8005
8006         gust[gust_write_ptr] = 0.0;         // Zero the max gust
8007         gust[gust_read_ptr] = 0.0;          // for both buckets.
8008
8009 //WE7U: Debug
8010 //gust[gust_write_ptr] = 45.9;
8011     }
8012
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
8017
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,
8023
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.
8031
8032             gust_read_ptr = current - 1;    // Set read pointer back one, modulus 60
8033             if (gust_read_ptr < 0)
8034                 gust_read_ptr = 59;
8035
8036             gust[gust_read_ptr] = 0.0;
8037         }
8038         gust_last_write = current;
8039     }
8040
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
8044
8045     // Read the last (up to) five buckets and find the max gust
8046     computed_gust=gust[gust_write_ptr];
8047     j = gust_read_ptr;
8048     while (j != ((gust_write_ptr + 1) % 60) ) {
8049         if ( computed_gust < gust[j] )
8050             computed_gust = gust[j];
8051         j = (j + 1) % 60;
8052     }
8053
8054     *last_speed_time = sec_now();
8055     return(computed_gust);
8056 }
8057
8058
8059
8060
8061
8062 //*********************************************************************
8063 // Decode Peet Brothers Ultimeter 2000 weather data (Data logging mode)
8064 //
8065 // This function is called from db.c:data_add() only.  Used for
8066 // decoding incoming packets, not for our own weather station data.
8067 //
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 //*********************************************************************
8072
8073 void decode_U2000_L (int from, unsigned char *data, AprsWeatherRow *weather) {
8074     time_t last_speed_time;
8075     float last_speed;
8076     float computed_gust;
8077     char temp_data1[10];
8078     char *temp_conv;
8079     char format;
8080
8081     last_speed = 0.0;
8082     last_speed_time = 0;
8083     computed_gust = 0.0;
8084     format = 0;
8085
8086     weather->wx_type = WX_TYPE;
8087     xastir_snprintf(weather->wx_station,
8088         sizeof(weather->wx_station),
8089         "U2k");
8090
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;
8096     }
8097
8098     // 006B 00 58
8099     // 00A4 00 46 01FF 380E 2755 02C1 03E8 ---- 0052 04D7    0001 007BM
8100     // ^       ^  ^    ^    ^         ^                      ^
8101     // 0       6  8    12   16        24                     40
8102     /* wind speed */
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),
8107             "%03d",
8108             (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
8109         if (from) {
8110             weather->wx_speed_sec_time = sec_now();
8111         } else {
8112             /* local station */
8113             computed_gust = compute_gust((float)atof(weather->wx_speed),
8114                                         last_speed,
8115                                         &last_speed_time);
8116             weather->wx_speed_sec_time = sec_now();
8117             xastir_snprintf(weather->wx_gust,
8118                 sizeof(weather->wx_gust),
8119                 "%03d",
8120                 (int)(0.5 + computed_gust)); // Cheater's way of rounding
8121         }
8122     } else {
8123         if (!from)
8124             weather->wx_speed[0] = 0;
8125     }
8126
8127     /* wind direction */
8128     //
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
8131     // them.
8132     //
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),
8139             "%03d",
8140             (int)(((float)strtol(temp_data1,&temp_conv,16)/256.0)*360.0));
8141     } else {
8142         xastir_snprintf(weather->wx_course,
8143             sizeof(weather->wx_course),
8144             "000");
8145         if (!from)
8146             weather->wx_course[0]=0;
8147     }
8148
8149     /* outdoor temp */
8150     if (data[8] != '-') { // '-' signifies invalid data
8151         int temp4;
8152
8153         substr(temp_data1,(char *)(data+8),4);
8154         temp4 = (int)strtol(temp_data1,&temp_conv,16);
8155  
8156         if (temp_data1[0] > '7') {  // Negative value, convert
8157             temp4 = (temp4 & (temp4-0x7FFF)) - 0x8000;
8158         }
8159
8160         xastir_snprintf(weather->wx_temp,
8161             sizeof(weather->wx_temp),
8162             "%03d",
8163             (int)((float)((temp4<<16)/65536)/10.0));
8164
8165     }
8166     else {
8167         if (!from)
8168             weather->wx_temp[0]=0;
8169     }
8170
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),
8176             "%0.2f",
8177             (float)strtol(temp_data1,&temp_conv,16)/100.0);
8178         if (!from) {
8179             /* local station */
8180             compute_rain((float)atof(weather->wx_rain_total));
8181             /*last hour rain */
8182             xastir_snprintf(weather->wx_rain,
8183                 sizeof(weather->wx_rain),
8184                 "%0.2f",
8185                 rain_minute_total);
8186             /*last 24 hour rain */
8187             xastir_snprintf(weather->wx_prec_24,
8188                 sizeof(weather->wx_prec_24),
8189                 "%0.2f",
8190                 rain_24);
8191             /* rain since midnight */
8192             xastir_snprintf(weather->wx_prec_00,
8193                 sizeof(weather->wx_prec_00),
8194                 "%0.2f",
8195                 rain_00);
8196         }
8197     } else {
8198         if (!from)
8199             weather->wx_rain_total[0]=0;
8200     }
8201
8202     /* baro */
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),
8207             "%0.1f",
8208             (float)strtol(temp_data1,&temp_conv,16)/10.0);
8209     } else {
8210         if (!from)
8211             weather->wx_baro[0]=0;
8212     }
8213     
8214
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),
8220             "%03d",
8221             (int)((float)strtol(temp_data1,&temp_conv,16)/10.0));
8222     } else {
8223         if (!from)
8224             weather->wx_hum[0]=0;
8225     }
8226
8227     /* todays rain total */
8228     if (data[40] != '-') { // '-' signifies invalid data
8229         if (from) {
8230             substr(temp_data1,(char *)(data+40),4);
8231             xastir_snprintf(weather->wx_prec_00,
8232                 sizeof(weather->wx_prec_00),
8233                 "%0.2f",
8234                 (float)strtol(temp_data1,&temp_conv,16)/100.0);
8235         }
8236     } else {
8237         if (!from)
8238             weather->wx_prec_00[0] = 0;
8239     }
8240 }
8241
8242
8243
8244
8245
8246 //********************************************************************
8247 // Decode Peet Brothers Ultimeter 2000 weather data (Packet mode)
8248 //
8249 // This function is called from db.c:data_add() only.  Used for
8250 // decoding incoming packets, not for our own weather station data.
8251 //
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
8254 // the Packet Mode.
8255 //********************************************************************
8256 void decode_U2000_P(int from, unsigned char *data, AprsWeatherRow *weather) {
8257     time_t last_speed_time;
8258     float last_speed;
8259     float computed_gust;
8260     char temp_data1[10];
8261     char *temp_conv;
8262     int len;
8263
8264     last_speed      = 0.0;
8265     last_speed_time = 0;
8266     computed_gust   = 0.0;
8267     len = (int)strlen((char *)data);
8268
8269     weather->wx_type = WX_TYPE;
8270     xastir_snprintf(weather->wx_station,
8271         sizeof(weather->wx_station),
8272         "U2k");
8273
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;
8279     }
8280
8281     // $ULTW   0031 00 37 02CE  0069 ---- 0000 86A0 0001 ---- 011901CC   0000 0005
8282     //         ^       ^  ^     ^    ^                   ^               ^    ^
8283     //         0       6  8     12   16                  32              44   48
8284
8285     /* wind speed peak over last 5 min */
8286     if (data[0] != '-') { // '-' signifies invalid data
8287         substr(temp_data1,(char *)data,4);
8288         if (from) {
8289             xastir_snprintf(weather->wx_gust,
8290                 sizeof(weather->wx_gust),
8291                 "%03d",
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),
8296                 "%03d",
8297                 (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
8298         } else {
8299             /* local station and may be the only wind data */
8300             if (len < 51) {
8301                 xastir_snprintf(weather->wx_speed,
8302                     sizeof(weather->wx_speed),
8303                     "%03d",
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),
8306                                         last_speed,
8307                                         &last_speed_time);
8308                 weather->wx_speed_sec_time = sec_now();
8309                 xastir_snprintf(weather->wx_gust,
8310                     sizeof(weather->wx_gust),
8311                     "%03d",
8312                     (int)(0.5 + computed_gust));
8313             }
8314         }
8315     } else {
8316         if (!from)
8317             weather->wx_gust[0] = 0;
8318     }
8319
8320     /* wind direction */
8321     //
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
8324     // them.
8325     //
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),
8332             "%03d",
8333             (int)(((float)strtol(temp_data1,&temp_conv,16)/256.0)*360.0));
8334     } else {
8335         xastir_snprintf(weather->wx_course,
8336             sizeof(weather->wx_course),
8337             "000");
8338         if (!from)
8339             weather->wx_course[0] = 0;
8340     }
8341
8342     /* outdoor temp */
8343     if (data[8] != '-') { // '-' signifies invalid data
8344         int temp4;
8345
8346         substr(temp_data1,(char *)(data+8),4);
8347         temp4 = (int)strtol(temp_data1,&temp_conv,16);
8348
8349         if (temp_data1[0] > '7') {  // Negative value, convert
8350             temp4 = (temp4 & (temp4-0x7FFF)) - 0x8000;
8351         }
8352
8353         xastir_snprintf(weather->wx_temp,
8354             sizeof(weather->wx_temp),
8355             "%03d",
8356             (int)((float)((temp4<<16)/65536)/10.0));
8357     }
8358     else {
8359         if (!from)
8360             weather->wx_temp[0] = 0;
8361     }
8362     /* todays rain total (on some units) */
8363     if ((data[44]) != '-') { // '-' signifies invalid data
8364         if (from) {
8365             substr(temp_data1,(char *)(data+44),4);
8366             xastir_snprintf(weather->wx_prec_00,
8367                 sizeof(weather->wx_prec_00),
8368                 "%0.2f",
8369                 (float)strtol(temp_data1,&temp_conv,16)/100.0);
8370         }
8371     } else {
8372         if (!from)
8373             weather->wx_prec_00[0] = 0;
8374     }
8375
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),
8381             "%0.2f",
8382             (float)strtol(temp_data1,&temp_conv,16)/100.0);
8383         if (!from) {
8384             /* local station */
8385             compute_rain((float)atof(weather->wx_rain_total));
8386             /*last hour rain */
8387             snprintf(weather->wx_rain,
8388                 sizeof(weather->wx_rain),
8389                 "%0.2f",
8390                 rain_minute_total);
8391             /*last 24 hour rain */
8392             snprintf(weather->wx_prec_24,
8393                 sizeof(weather->wx_prec_24),
8394                 "%0.2f",
8395                 rain_24);
8396             /* rain since midnight */
8397             snprintf(weather->wx_prec_00,
8398                 sizeof(weather->wx_prec_00),
8399                 "%0.2f",
8400                 rain_00);
8401         }
8402     } else {
8403         if (!from)
8404             weather->wx_rain_total[0] = 0;
8405     }
8406
8407     /* baro */
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),
8412             "%0.1f",
8413             (float)strtol(temp_data1,&temp_conv,16)/10.0);
8414     } else {
8415         if (!from)
8416             weather->wx_baro[0] = 0;
8417     }
8418
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),
8424             "%03d",
8425             (int)((float)strtol(temp_data1,&temp_conv,16)/10.0));
8426     } else {
8427         if (!from)
8428             weather->wx_hum[0] = 0;
8429     }
8430
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),
8436             "%03d",
8437             (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
8438         if (from) {
8439             weather->wx_speed_sec_time = sec_now();
8440         } else {
8441             /* local station */
8442             computed_gust = compute_gust((float)atof(weather->wx_speed),
8443                                         last_speed,
8444                                         &last_speed_time);
8445             weather->wx_speed_sec_time = sec_now();
8446             xastir_snprintf(weather->wx_gust,
8447                 sizeof(weather->wx_gust),
8448                 "%03d",
8449                 (int)(0.5 + computed_gust));
8450         }
8451     } else {
8452         if (!from) {
8453             if (len > 48)
8454                 weather->wx_speed[0] = 0;
8455         }
8456     }
8457 }
8458
8459
8460
8461
8462
8463 //*****************************************************************
8464 // Decode Peet Brothers Ultimeter-II weather data
8465 //
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;
8471     float last_speed;
8472     float computed_gust;
8473     char temp_data1[10];
8474     char *temp_conv;
8475
8476     last_speed    = 0.0;
8477     computed_gust = 0.0;
8478     last_speed_time = 0;
8479
8480     weather->wx_type = WX_TYPE;
8481     xastir_snprintf(weather->wx_station,
8482         sizeof(weather->wx_station),
8483         "UII");
8484
8485     // '*' = MPH
8486     // '#' = km/h
8487     //
8488     // #  5 0B 75 0082 0082
8489     // *  7 00 76 0000 0000
8490     //    ^ ^  ^  ^
8491     //            rain [1/100 inch ?]
8492     //         outdoor temp
8493     //      wind speed [mph / km/h]
8494     //    wind dir
8495
8496     /* wind direction */
8497     //
8498     // 0x00 is N
8499     // 0x04 is E
8500     // 0x08 is S
8501     // 0x0C is W
8502     //
8503     substr(temp_data1,(char *)data,1);
8504     snprintf(weather->wx_course,
8505         sizeof(weather->wx_course),
8506         "%03d",
8507         (int)(((float)strtol(temp_data1,&temp_conv,16)/16.0)*360.0));
8508
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;
8514     }
8515
8516     /* wind speed */
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),
8521             "%03d",
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),
8526             "%03d",
8527             (int)(0.5 + (float)strtol(temp_data1,&temp_conv,16)));
8528     }
8529
8530     if (from) {
8531         weather->wx_speed_sec_time = sec_now();
8532     } else {
8533         /* local station */
8534         computed_gust = compute_gust((float)atof(weather->wx_speed),
8535                                 last_speed,
8536                                 &last_speed_time);
8537         weather->wx_speed_sec_time = sec_now();
8538         xastir_snprintf(weather->wx_gust,
8539             sizeof(weather->wx_gust),
8540             "%03d",
8541             (int)(0.5 + computed_gust));
8542     }
8543
8544     /* outdoor temp */
8545     if (data[3] != '-') { // '-' signifies invalid data
8546         int temp4;
8547
8548         substr(temp_data1,(char *)(data+3),2);
8549         temp4 = (int)strtol(temp_data1,&temp_conv,16);
8550
8551         if (temp_data1[0] > '7') {  // Negative value, convert
8552             temp4 = (temp4 & (temp4-0x7FFF)) - 0x8000;
8553         }
8554
8555         xastir_snprintf(weather->wx_temp,
8556             sizeof(weather->wx_temp),
8557             "%03d",
8558             temp4-56);
8559     } else {
8560         if (!from)
8561             weather->wx_temp[0] = 0;
8562     }
8563
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),
8569             "%0.2f",
8570             (float)strtol(temp_data1,&temp_conv,16)/100.0);
8571         if (!from) {
8572             /* local station */
8573             compute_rain((float)atof(weather->wx_rain_total));
8574             /*last hour rain */
8575             xastir_snprintf(weather->wx_rain,
8576                 sizeof(weather->wx_rain),
8577                 "%0.2f",
8578                 rain_minute_total);
8579             /*last 24 hour rain */
8580             xastir_snprintf(weather->wx_prec_24,
8581                 sizeof(weather->wx_prec_24),
8582                 "%0.2f",
8583                 rain_24);
8584             /* rain since midnight */
8585             xastir_snprintf(weather->wx_prec_00,
8586                 sizeof(weather->wx_prec_00),
8587                 "%0.2f",
8588                 rain_00);
8589         }
8590     } else {
8591         if (!from)
8592             weather->wx_rain_total[0] = 0;
8593     }
8594 }
8595
8596
8597 #endif //INCLUDE_APRS