1 /*
2  *
3  * XASTIR, Amateur Station Tracking and Information Reporting
4  * Copyright (C) 1999,2000  Frank Giannandrea
5  * Copyright (C) 2000-2019 The Xastir Group
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20  *
21  * Look at the README for more information on the program.
22  */
23 
24 /* Note: the header file for db.c should be called db.h,
25  * but is named database.h to avoid conflicts with the
26  * Berkeley DB package.
27  */
28 
29 // NOTE:  decode_info_field() is a good place to start for decoding.
30 
31 
32 // Used only for special debugging of message/station expiration.
33 // Leave commented out for normal operation.
34 //#define EXPIRE_DEBUG
35 #define DEBUG_MESSAGE_REMOVE_CYCLE 15
36 #define DEBUG_STATION_REMOVE_CYCLE 15
37 #define DEBUG_MESSAGE_REMOVE 600
38 #define DEBUG_STATION_REMOVE 600
39 
40 #ifdef HAVE_CONFIG_H
41   #include "config.h"
42 #endif  // HAVE_CONFIG_H
43 
44 #include "snprintf.h"
45 
46 #include <stdio.h>
47 #include <fcntl.h>
48 #include <unistd.h>
49 #include <stdlib.h>
50 #include <ctype.h>
51 #include <string.h>
52 
53 // Needed for Solaris
54 #ifdef HAVE_STRINGS_H
55   #include <strings.h>
56 #endif  // HAVE_STRINGS_H
57 
58 #include <math.h>
59 
60 #include <Xm/XmAll.h>
61 
62 #include "xastir.h"
63 #include "main.h"
64 #include "draw_symbols.h"
65 #include "alert.h"
66 #include "util.h"
67 #include "bulletin_gui.h"
68 #include "fcc_data.h"
69 #include "geo.h"
70 #include "gps.h"
71 #include "rac_data.h"
72 #include "interface.h"
73 #include "maps.h"
74 #include "wx.h"
75 #include "igate.h"
76 #include "list_gui.h"
77 #include "objects.h"
78 #include "track_gui.h"
79 #include "xa_config.h"
80 #include "x_spider.h"
81 #include "db_gis.h"
82 
83 // Must be last include file
84 #include "leak_detection.h"
85 
86 
87 
88 #define CHECKMALLOC(m)  if (!m) { fprintf(stderr, "***** Malloc Failed *****\n"); exit(0); }
89 
90 
91 #define STATION_REMOVE_CYCLE 300    /* check station remove in seconds (every 5 minutes) */
92 #define MESSAGE_REMOVE_CYCLE 600    /* check message remove in seconds (every 10 minutes) */
93 #define IN_VIEW_MIN         600l    /* margin for off-screen stations, with possible trails on screen, in minutes */
94 #define TRAIL_POINT_MARGIN   30l    /* margin for off-screen trails points, for segment to be drawn, in minutes */
95 #define TRAIL_MAX_SPEED      900    /* max. acceptible speed for drawing trails, in mph */
96 #define MY_TRAIL_COLOR      0x16    /* trail color index reserved for my station */
97 #define TRAIL_ECHO_TIME       30    /* check for delayed echos during last 30 minutes */
98 /* MY_TRAIL_DIFF_COLOR changed to user configurable my_trail_diff_color  */
99 
100 
101 /////////////////////////////////////
102 #define GUARD_SIZE 10
103 char GUARD_BAND_THREE[GUARD_SIZE];
104 /////////////////////////////////////
105 
106 extern XmFontList fontlist1;    // Menu/System fontlist
107 
108 // Station Info
109 Widget  db_station_popup = (Widget)NULL;
110 char *db_station_info_callsign = NULL;
111 Pixmap  SiS_icon0, SiS_icon;
112 Widget  SiS_symb;
113 Widget  station_list;
114 Widget  button_store_track;
115 int station_data_auto_update = 0;
116 
117 
118 // Used to store all the calls we might "relay" digipeat by.
119 // Separated by commas.  Up to 50 callsigns of 9 chars each plus
120 // comma delimiters.
121 char relay_digipeater_calls[10*MAX_RELAY_DIGIPEATER_CALLS];
122 
123 Widget si_text;
124 Widget db_station_info  = (Widget)NULL;
125 
126 static xastir_mutex db_station_info_lock;
127 static xastir_mutex db_station_popup_lock;
128 
129 void redraw_symbols(Widget w);
130 int  delete_weather(DataRow *fill);
131 int  delete_multipoints(DataRow *fill);
132 void Station_data_destroy_track(Widget widget, XtPointer clientData, XtPointer callData);
133 void my_station_gps_change(char *pos_long, char *pos_lat, char *course, char *speed, char speedu, char *alt, char *sats);
134 void station_shortcuts_update_function(int hash_key, DataRow *p_rem);
135 int position_on_extd_screen(long lat, long lon);
136 
137 int  extract_speed_course(char *info, char *speed, char *course);
138 int  extract_bearing_NRQ(char *info, char *bearing, char *nrq);
139 
140 int skip_dupe_checking;
141 int  tracked_stations = 0;       // A count variable used in debug code only
142 void track_station(Widget w, char *call_tracked, DataRow *p_station);
143 
144 int  new_message_data;
145 time_t last_message_remove;     // last time we did a check for message removing
146 
147 ////////////////////////////////////
148 char GUARD_BAND_FOUR[GUARD_SIZE];
149 ////////////////////////////////////
150 
151 //// Save most recent 100 packets in an array called packet_data_string[]
152 #define MAX_PACKET_DATA_DISPLAY 100
153 int  redraw_on_new_packet_data;
154 char packet_data_string[MAX_PACKET_DATA_DISPLAY][MAX_LINE_SIZE+1];
155 int first_line=-1;
156 int next_line=0;
157 int ncharsdel=0;
158 int nlinesadd=0;
159 
160 ///////////////////////////////////
161 char GUARD_BAND_ONE[GUARD_SIZE];
162 ///////////////////////////////////
163 
164 int station_count;              // number of stored stations
165 int station_count_save = 0;     // old copy of above
166 DataRow *n_first;               // pointer to first element in name sorted station list
167 DataRow *n_last;                // pointer to last  element in name sorted station list
168 DataRow *t_oldest;              // pointer to first element in time sorted station list (oldest)
169 DataRow *t_newest;              // pointer to last  element in time sorted station list (newest)
170 time_t last_station_remove;     // last time we did a check for station removing
171 time_t last_sec,curr_sec;       // for comparing if seconds in time have changed
172 int next_time_sn;               // time serial number for unique time index
173 
174 ///////////////////////////////////
175 char GUARD_BAND_TWO[GUARD_SIZE];
176 ///////////////////////////////////
177 
178 
179 
180 int emergency_distance_check = 1;
181 float emergency_range = 280.0;  // Default is 4hrs @ 70mph distance
182 
183 CADRow *CAD_list_head = NULL;   // pointer to first element in CAD objects list
184 
185 void draw_trail(Widget w, DataRow *fill, int solid);
186 void export_trail(DataRow *p_station);          // export trail of one or all stations to xastir export file
187 //void export_trail_as_kml(DataRow *p_station);   // export trail of one or all stations to kml file
188 
189 int decoration_offset_x = 0;
190 int decoration_offset_y = 0;
191 int last_station_info_x = 0;
192 int last_station_info_y = 0;
193 int fcc_lookup_pushed = 0;
194 int rac_lookup_pushed = 0;
195 
196 time_t last_object_check = 0;   // Used to determine when to re-transmit objects/items
197 
198 time_t last_emergency_time = 0;
199 char last_emergency_callsign[MAX_CALLSIGN+1];
200 int st_direct_timeout = 60 * 60;        // 60 minutes.
201 
202 // Used in search_station_name() function.  Shortcuts into the
203 // station list based on the least-significant 7 bits of the first
204 // two letters of the callsign/object name.
205 DataRow *station_shortcuts[16384];
206 
207 // used to time aloha calculations
208 static time_t aloha_time = 0;
209 static time_t aloha_status_time = 0;
210 static double aloha_radius=-1;  // in miles
211 static aloha_stats the_aloha_stats;
212 // calculate every half hour, display in status line every 5 minutes
213 #define ALOHA_CALC_INTERVAL 1800
214 #define ALOHA_STATUS_INTERVAL 300
215 
216 int process_emergency_packet_again = 0;
217 
218 
219 
220 
221 
db_init(void)222 void db_init(void)
223 {
224   int ii;
225 
226 
227   // Set up guard bands around important global pointers
228   for (ii = 0; ii < GUARD_SIZE; ii++)
229   {
230     GUARD_BAND_ONE[ii]   = 0x00;
231     GUARD_BAND_TWO[ii]   = 0x00;
232     GUARD_BAND_THREE[ii] = 0x00;
233     GUARD_BAND_FOUR[ii]  = 0x00;
234   }
235 
236   init_critical_section( &db_station_info_lock );
237   init_critical_section( &db_station_popup_lock );
238   last_emergency_callsign[0] = '\0';
239 
240   // Seed the random number generator
241   srand(1);
242 }
243 
244 
245 
246 
247 
248 ///////////////////////////////////  Utilities  ////////////////////////////////////////////////////
249 
250 
251 
252 // Variable used for below test code
253 //int we7u_count = 50;
254 
255 
256 
257 // Check guard bands around important global pointers.
258 //
259 // These guard bands are initialized in db.c:db_init()
260 //
261 // Returns:  0 if ok
262 //           1 if guard band has been tampered with
263 //
check_guard_band(void)264 int check_guard_band(void)
265 {
266   int ii;
267 
268   for (ii = 0; ii < GUARD_SIZE; ii++)
269   {
270     if (GUARD_BAND_ONE[ii] != 0x00
271         || GUARD_BAND_TWO[ii] != 0x00
272         || GUARD_BAND_THREE[ii] != 0x00
273         || GUARD_BAND_FOUR[ii] != 0x00)
274     {
275 
276       if (GUARD_BAND_ONE[ii]   != 0x00)
277       {
278         fprintf(stderr, "WARNING: GUARD_BAND_ONE   was corrupted!\n");
279       }
280 
281       if (GUARD_BAND_TWO[ii]   != 0x00)
282       {
283         fprintf(stderr, "WARNING: GUARD_BAND_TWO   was corrupted!\n");
284       }
285 
286       if (GUARD_BAND_THREE[ii] != 0x00)
287       {
288         fprintf(stderr, "WARNING: GUARD_BAND_THREE was corrupted!\n");
289       }
290 
291       if (GUARD_BAND_FOUR[ii]  != 0x00)
292       {
293         fprintf(stderr, "WARNING: GUARD_BAND_FOUR  was corrupted!\n");
294       }
295 
296       fprintf(stderr, "Previous incoming line was: %s\n", incoming_data_copy_previous);
297       fprintf(stderr, "    Last incoming line was: %s\n", incoming_data_copy);
298 
299       abort();    // Cause immediate exit to aid in debugging
300 
301       return(1);
302     }
303   }
304 
305 // Test code
306   /*
307   if (we7u_count-- <= 0) {
308       GUARD_BAND_ONE[0] = 0x01;
309       GUARD_BAND_TWO[0] = 0x01;
310       GUARD_BAND_THREE[0] = 0x01;
311       GUARD_BAND_FOUR[0] = 0x01;
312   }
313   */
314 
315   return(0);
316 }
317 
318 
319 
320 
321 
322 /*
323  *  Check whether callsign is mine.  "exact == 1" checks the SSID
324  *  for a match as well.  "exact == 0" checks only the base
325  *  callsign.
326  */
is_my_call(char * call,int exact)327 int is_my_call(char *call, int exact)
328 {
329   char *p_del;
330   int ok;
331 
332 
333   // U.S. special-event callsigns can be as short as three
334   // characters, any less and we don't have a valid callsign.  We
335   // don't check for that restriction here though.
336 
337   if (exact)
338   {
339     // We're looking for an exact match
340     ok = (int)( !strcmp(call,my_callsign) );
341     //fprintf(stderr,"My exact call found: %s\n",call);
342   }
343   else
344   {
345     // We're looking for a similar match.  Compare only up to
346     // the '-' in each (if present).
347     int len1,len2;
348 
349     p_del = index(call,'-');
350     if (p_del == NULL)
351     {
352       len1 = (int)strlen(call);
353     }
354     else
355     {
356       len1 = p_del - call;
357     }
358 
359     p_del = index(my_callsign,'-');
360     if (p_del == NULL)
361     {
362       len2 = (int)strlen(my_callsign);
363     }
364     else
365     {
366       len2 = p_del - my_callsign;
367     }
368 
369     ok = (int)(len1 == len2 && !strncmp(call,my_callsign,(size_t)len1));
370     //fprintf(stderr,"My base call found: %s\n",call);
371   }
372 
373   return(ok);
374 }
375 
376 
377 
378 
379 
is_my_station(DataRow * p_station)380 int is_my_station(DataRow *p_station)
381 {
382   // if station is owned by me (including SSID)
383   return(p_station->flag & ST_MYSTATION);
384 }
385 
386 
387 
388 
389 
is_my_object_item(DataRow * p_station)390 int is_my_object_item(DataRow *p_station)
391 {
392   // If object/item is owned by me (including SSID)
393   return(p_station->flag & ST_MYOBJITEM);
394 }
395 
396 
397 
398 
399 
400 /*
401  *  Change map position if neccessary while tracking a station
402  *      we call it with defined station call and position
403  */
is_tracked_station(char * call_sign)404 int is_tracked_station(char *call_sign)
405 {
406   int found;
407   char call_find[MAX_CALLSIGN+1];
408   int ii;
409   int call_len;
410 
411   if (!track_station_on)
412   {
413     return(0);
414   }
415 
416   call_len = 0;
417   found = 0;
418 
419   if (!track_case)
420   {
421     for ( ii = 0; ii < (int)strlen(tracking_station_call); ii++ )
422     {
423       if (isalpha((int)tracking_station_call[ii]))
424       {
425         call_find[ii] = toupper((int)tracking_station_call[ii]);
426       }
427       else
428       {
429         call_find[ii] = tracking_station_call[ii];
430       }
431     }
432     call_find[ii] = '\0';
433   }
434   else
435   {
436     memcpy(call_find, tracking_station_call, sizeof(call_find));
437     call_find[sizeof(call_find)-1] = '\0';  // Terminate string
438   }
439 
440   if (debug_level & 256)
441   {
442     fprintf(stderr,"is_tracked_station(): CALL %s %s %s\n",
443             tracking_station_call,
444             call_find, call_sign);
445   }
446 
447   if (track_match)
448   {
449     if (strcmp(call_find,call_sign) == 0)   // we want an exact match
450     {
451       found = 1;
452     }
453   }
454   else
455   {
456     found = 0;
457     call_len = (int)(strlen(call_sign) - strlen(call_find));
458     if (strlen(call_find) <= strlen(call_sign))
459     {
460       found = 1;
461       for ( ii = 0; ii <= call_len; ii++ )
462       {
463         if (!track_case)
464         {
465           if (strncasecmp(call_find,call_sign+ii,strlen(call_find)) != 0)
466           {
467             found = 0;  // Found a mis-match
468           }
469         }
470         else
471         {
472           if (strncmp(call_find,call_sign+ii,strlen(call_find)) != 0)
473           {
474             found = 0;
475           }
476         }
477       }
478     }
479   }
480   return(found);
481 }
482 
483 
484 
485 
486 
487 /////////////////////////////////////////// Messages ///////////////////////////////////////////
488 
489 static long *msg_index;
490 static long msg_index_end;
491 static long msg_index_max;
492 
493 static Message *msg_data; // Array containing all messages,
494 // including ones we've transmitted (via
495 // loopback in the code)
496 
497 time_t last_message_update = 0;
498 ack_record *ack_list_head = NULL;  // Head of linked list storing most recent ack's
499 int satellite_ack_mode;
500 
501 
502 // How often update_messages() will run, in seconds.
503 // This is necessary because routines like UpdateTime()
504 // call update_messages() VERY OFTEN.
505 //
506 // Actually, we just changed the code around so that we only call
507 // update_messages() with the force option, and only when we receive a
508 // message.  message_update_delay is no longer used, and we don't call
509 // update_messages() from UpdateTime() anymore.
510 static int message_update_delay = 300;
511 
512 
513 
514 
515 
516 // Saves latest ack in a linked list.  We need this value in order
517 // to use Reply/Ack protocol when sending out messages.
store_most_recent_ack(char * callsign,char * ack)518 void store_most_recent_ack(char *callsign, char *ack)
519 {
520   ack_record *p;
521   int done = 0;
522   char call[MAX_CALLSIGN+1];
523   char new_ack[5+1];
524 
525   xastir_snprintf(call,
526                   sizeof(call),
527                   "%s",
528                   callsign);
529   remove_trailing_spaces(call);
530 
531   // Get a copy of "ack".  We might need to change it.
532   xastir_snprintf(new_ack,
533                   sizeof(new_ack),
534                   "%s",
535                   ack);
536 
537   // If it's more than 2 characters long, we can't use it for
538   // Reply/Ack protocol as there's only space enough for two.
539   // In this case we need to make sure that we blank out any
540   // former ack that was 1 or 2 characters, so that communications
541   // doesn't stop.
542   if ( strlen(new_ack) > 2 )
543   {
544     // It's too long, blank it out so that gets saved as "",
545     // which will overwrite any previously saved ack's that were
546     // short enough to use.
547     new_ack[0] = '\0';
548   }
549 
550   // Search for matching callsign through linked list
551   p = ack_list_head;
552   while ( !done && (p != NULL) )
553   {
554     if (strcasecmp(call,p->callsign) == 0)
555     {
556       done++;
557     }
558     else
559     {
560       p = p->next;
561     }
562   }
563 
564   if (done)   // Found it.  Update the ack field.
565   {
566     //fprintf(stderr,"Found callsign %s on recent ack list, Old:%s, New:%s\n",call,p->ack,new_ack);
567     xastir_snprintf(p->ack,sizeof(p->ack),"%s",new_ack);
568   }
569   else    // Not found.  Add a new record to the beginning of the
570   {
571     // list.
572     //fprintf(stderr,"New callsign %s, adding to list.  Ack: %s\n",call,new_ack);
573     p = (ack_record *)malloc(sizeof(ack_record));
574     CHECKMALLOC(p);
575 
576     xastir_snprintf(p->callsign,sizeof(p->callsign),"%s",call);
577     xastir_snprintf(p->ack,sizeof(p->ack),"%s",new_ack);
578     p->next = ack_list_head;
579     ack_list_head = p;
580   }
581 }
582 
583 
584 
585 
586 
587 // Gets latest ack by callsign
get_most_recent_ack(char * callsign)588 char *get_most_recent_ack(char *callsign)
589 {
590   ack_record *p;
591   int done = 0;
592   char call[MAX_CALLSIGN+1];
593 
594   xastir_snprintf(call,
595                   sizeof(call),
596                   "%s",
597                   callsign);
598   remove_trailing_spaces(call);
599 
600   // Search for matching callsign through linked list
601   p = ack_list_head;
602   while ( !done && (p != NULL) )
603   {
604     if (strcasecmp(call,p->callsign) == 0)
605     {
606       done++;
607     }
608     else
609     {
610       p = p->next;
611     }
612   }
613 
614   if (done)   // Found it.  Return pointer to ack string.
615   {
616     //fprintf(stderr,"Found callsign %s on linked list, returning ack: %s\n",call,p->ack);
617     return(&p->ack[0]);
618   }
619   else
620   {
621     //fprintf(stderr,"Callsign %s not found\n",call);
622     return(NULL);
623   }
624 }
625 
626 
627 
628 
629 
init_message_data(void)630 void init_message_data(void)    // called at start of main
631 {
632 
633   new_message_data = 0;
634   last_message_remove = sec_now();
635 }
636 
637 
638 
639 
640 
641 #ifdef MSG_DEBUG
msg_clear_data(Message * clear)642 void msg_clear_data(Message *clear)
643 {
644   int size;
645   int i;
646   unsigned char *data_ptr;
647 
648   data_ptr = (unsigned char *)clear;
649   size=sizeof(Message);
650   for(i=0; i<size; i++)
651   {
652     *data_ptr++ = 0;
653   }
654 }
655 
656 
657 
658 
659 
msg_copy_data(Message * to,Message * from)660 void msg_copy_data(Message *to, Message *from)
661 {
662   int size;
663   int i;
664   unsigned char *data_ptr;
665   unsigned char *data_ptr_from;
666 
667   data_ptr = (unsigned char *)to;
668   data_ptr_from = (unsigned char *)from;
669   size=sizeof(Message);
670   for(i=0; i<size; i++)
671   {
672     *data_ptr++ = *data_ptr_from++;
673   }
674 }
675 #endif /* MSG_DEBUG */
676 
677 
678 
679 
680 
681 // Returns 1 if it's time to update the messages again
message_update_time(void)682 int message_update_time (void)
683 {
684   if ( sec_now() > (last_message_update + message_update_delay) )
685   {
686     return(1);
687   }
688   else
689   {
690     return(0);
691   }
692 }
693 
694 
695 
696 
697 
msg_comp_active(const void * a,const void * b)698 int msg_comp_active(const void *a, const void *b)
699 {
700   char temp_a[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+2];
701   char temp_b[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+2];
702 
703   xastir_snprintf(temp_a, sizeof(temp_a), "%c%s%s%s",
704                   ((Message*)a)->active, ((Message*)a)->call_sign,
705                   ((Message*)a)->from_call_sign,
706                   ((Message*)a)->seq);
707   xastir_snprintf(temp_b, sizeof(temp_b), "%c%s%s%s",
708                   ((Message*)b)->active, ((Message*)b)->call_sign,
709                   ((Message*)b)->from_call_sign,
710                   ((Message*)b)->seq);
711 
712   return(strcmp(temp_a, temp_b));
713 }
714 
715 
716 
717 
718 
msg_comp_data(const void * a,const void * b)719 int msg_comp_data(const void *a, const void *b)
720 {
721   char temp_a[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+1];
722   char temp_b[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+1];
723 
724   xastir_snprintf(temp_a, sizeof(temp_a), "%s%s%s",
725                   msg_data[*(long*)a].call_sign, msg_data[*(long *)a].from_call_sign,
726                   msg_data[*(long *)a].seq);
727   xastir_snprintf(temp_b, sizeof(temp_b), "%s%s%s", msg_data[*(long*)b].call_sign,
728                   msg_data[*(long *)b].from_call_sign, msg_data[*(long *)b].seq);
729 
730   return(strcmp(temp_a, temp_b));
731 }
732 
733 
734 
735 
736 
msg_input_database(Message * m_fill)737 void msg_input_database(Message *m_fill)
738 {
739   void *m_ptr;
740   long i;
741 
742   if (msg_index_end == msg_index_max)
743   {
744     for (i = 0; i < msg_index_end; i++)
745     {
746 
747       // Check for a record that is marked RECORD_NOTACTIVE.
748       // If found, use that record instead of malloc'ing a new
749       // one.
750       if (msg_data[msg_index[i]].active == RECORD_NOTACTIVE)
751       {
752 
753         // Found an unused record.  Fill it in.
754         memcpy(&msg_data[msg_index[i]], m_fill, sizeof(Message));
755 
756         // Sort msg_data
757         qsort(msg_data, (size_t)msg_index_end, sizeof(Message), msg_comp_active);
758 
759         for (i = 0; i < msg_index_end; i++)
760         {
761           msg_index[i] = i;
762           if (msg_data[i].active == RECORD_NOTACTIVE)
763           {
764             msg_index_end = i;
765             break;
766           }
767         }
768 
769         // Sort msg_index
770         qsort(msg_index, (size_t)msg_index_end, sizeof(long *), msg_comp_data);
771 
772         // All done with this message.
773         return;
774       }
775     }
776 
777     // Didn't find free message record.  Fetch some more space.
778     // Get more msg_data space.
779     m_ptr = realloc(msg_data, (msg_index_max+MSG_INCREMENT)*sizeof(Message));
780     if (m_ptr)
781     {
782       msg_data = m_ptr;
783 
784       // Get more msg_index space
785       m_ptr = realloc(msg_index, (msg_index_max+MSG_INCREMENT)*sizeof(Message *));
786       if (m_ptr)
787       {
788         msg_index = m_ptr;
789         msg_index_max += MSG_INCREMENT;
790 
791         //fprintf(stderr, "Max Message Array: %ld\n", msg_index_max);
792 
793       }
794       else
795       {
796         XtWarning("Unable to allocate more space for message index.\n");
797       }
798     }
799     else
800     {
801       XtWarning("Unable to allocate more space for message database.\n");
802     }
803   }
804   if (msg_index_end < msg_index_max)
805   {
806     msg_index[msg_index_end] = msg_index_end;
807 
808     // Copy message data into new message record.
809     memcpy(&msg_data[msg_index_end++], m_fill, sizeof(Message));
810 
811     // Sort msg_index
812     qsort(msg_index, (size_t)msg_index_end, sizeof(long *), msg_comp_data);
813   }
814 }
815 
816 
817 
818 
819 
820 // Does a binary search through a sorted message database looking
821 // for a string match.
822 //
823 // If two or more messages match, this routine _should_ return the
824 // message with the latest timestamp.  This will ensure that earlier
825 // messages don't get mistaken for current messages, for the case
826 // where the remote station did a restart and is using the same
827 // sequence numbers over again.
828 //
msg_find_data(Message * m_fill)829 long msg_find_data(Message *m_fill)
830 {
831   long record_start, record_mid, record_end, return_record, done;
832   char tempfile[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+1];
833   char tempfill[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+1];
834 
835 
836   xastir_snprintf(tempfill, sizeof(tempfill), "%s%s%s",
837                   m_fill->call_sign,
838                   m_fill->from_call_sign,
839                   m_fill->seq);
840 
841   return_record = -1L;
842   if (msg_index && msg_index_end >= 1)
843   {
844     /* more than one record */
845     record_start=0L;
846     record_end = (msg_index_end - 1);
847     record_mid=(record_end-record_start)/2;
848 
849     done=0;
850     while (!done)
851     {
852 
853       /* get data for record start */
854       xastir_snprintf(tempfile, sizeof(tempfile), "%s%s%s",
855                       msg_data[msg_index[record_start]].call_sign,
856                       msg_data[msg_index[record_start]].from_call_sign,
857                       msg_data[msg_index[record_start]].seq);
858 
859       if (strcmp(tempfill, tempfile) < 0)
860       {
861         /* filename comes before */
862         /*fprintf(stderr,"Before No data found!!\n");*/
863         done=1;
864         break;
865       }
866       else   /* get data for record end */
867       {
868 
869         xastir_snprintf(tempfile, sizeof(tempfile), "%s%s%s",
870                         msg_data[msg_index[record_end]].call_sign,
871                         msg_data[msg_index[record_end]].from_call_sign,
872                         msg_data[msg_index[record_end]].seq);
873 
874         if (strcmp(tempfill,tempfile)>=0)   /* at end or beyond */
875         {
876           if (strcmp(tempfill, tempfile) == 0)
877           {
878             return_record = record_end;
879             //fprintf(stderr,"record %ld",return_record);
880           }
881 
882           done=1;
883           break;
884         }
885         else if ((record_mid == record_start) || (record_mid == record_end))
886         {
887           /* no mid for compare check to see if in the middle */
888           done=1;
889           xastir_snprintf(tempfile, sizeof(tempfile), "%s%s%s",
890                           msg_data[msg_index[record_mid]].call_sign,
891                           msg_data[msg_index[record_mid]].from_call_sign,
892                           msg_data[msg_index[record_mid]].seq);
893           if (strcmp(tempfill,tempfile)==0)
894           {
895             return_record = record_mid;
896             //fprintf(stderr,"record: %ld",return_record);
897           }
898         }
899       }
900       if (!done)   /* get data for record mid */
901       {
902         xastir_snprintf(tempfile, sizeof(tempfile), "%s%s%s",
903                         msg_data[msg_index[record_mid]].call_sign,
904                         msg_data[msg_index[record_mid]].from_call_sign,
905                         msg_data[msg_index[record_mid]].seq);
906 
907         if (strcmp(tempfill, tempfile) == 0)
908         {
909           return_record = record_mid;
910           //fprintf(stderr,"record %ld",return_record);
911           done = 1;
912           break;
913         }
914 
915         if(strcmp(tempfill, tempfile)<0)
916         {
917           record_end = record_mid;
918         }
919         else
920         {
921           record_start = record_mid;
922         }
923 
924         record_mid = record_start+(record_end-record_start)/2;
925       }
926     }
927   }
928   return(return_record);
929 }
930 
931 
932 
933 
934 
msg_replace_data(Message * m_fill,long record_num)935 void msg_replace_data(Message *m_fill, long record_num)
936 {
937   memcpy(&msg_data[msg_index[record_num]], m_fill, sizeof(Message));
938 }
939 
940 
941 
942 
943 
msg_get_data(Message * m_fill,long record_num)944 void msg_get_data(Message *m_fill, long record_num)
945 {
946   memcpy(m_fill, &msg_data[msg_index[record_num]], sizeof(Message));
947 }
948 
949 
950 
951 
952 
msg_update_ack_stamp(long record_num)953 void msg_update_ack_stamp(long record_num)
954 {
955 
956   //fprintf(stderr,"Attempting to update ack stamp: %ld\n",record_num);
957   if ( (record_num >= 0) && (record_num < msg_index_end) )
958   {
959     msg_data[msg_index[record_num]].last_ack_sent = sec_now();
960     //fprintf(stderr,"Ack stamp: %ld\n",msg_data[msg_index[record_num]].last_ack_sent);
961   }
962   //fprintf(stderr,"\n\n\n*** Record: %ld ***\n\n\n",record_num);
963 }
964 
965 
966 
967 
968 
969 // Called when we receive an ACK.  Sets the "acked" field in a
970 // Message which gets rid of the highlighting in the Send Message
971 // dialog for that message line.  This lets us know which messages
972 // have been acked and which have not.  If timeout is non-zero, then
973 // set acked to 2:  We use this in update_messages() to flag that
974 // "*TIMEOUT*" should prefix the string.  If cancelled is non-zero,
975 // set acked to 3:  We use this in update_messages() to flag that
976 // "*CANCELLED*" should prefix the string.
977 //
msg_record_ack(char * to_call_sign,char * my_call,char * seq,int timeout,int cancel)978 void msg_record_ack(char *to_call_sign,
979                     char *my_call,
980                     char *seq,
981                     int timeout,
982                     int cancel)
983 {
984   Message m_fill;
985   long record;
986   int do_update = 0;
987 
988   if (debug_level & 1)
989   {
990     fprintf(stderr,"Recording ack for message to: %s, seq: %s\n",
991             to_call_sign,
992             seq);
993   }
994 
995   // Find the corresponding message in msg_data[i], set the
996   // "acked" field to one.
997 
998   substr(m_fill.call_sign, to_call_sign, MAX_CALLSIGN);
999   (void)remove_trailing_asterisk(m_fill.call_sign);
1000 
1001   substr(m_fill.from_call_sign, my_call, MAX_CALLSIGN);
1002   (void)remove_trailing_asterisk(m_fill.from_call_sign);
1003 
1004   substr(m_fill.seq, seq, MAX_MESSAGE_ORDER);
1005   (void)remove_trailing_spaces(m_fill.seq);
1006   (void)remove_leading_spaces(m_fill.seq);
1007 
1008   // Look for a message with the same to_call_sign, my_call,
1009   // and seq number
1010   record = msg_find_data(&m_fill);
1011 
1012   if (record == -1L)   // No match yet, try another tactic.
1013   {
1014     if (seq[2] == '}' && strlen(seq) == 3)
1015     {
1016 
1017       // Try it again without the trailing '}' character
1018       m_fill.from_call_sign[2] = '\0';
1019 
1020       // Look for a message with the same to_call_sign,
1021       // my_call, and seq number (minus the trailing '}')
1022       record = msg_find_data(&m_fill);
1023     }
1024   }
1025 
1026   if(record != -1L)       // Found a match!
1027   {
1028     if (debug_level & 1)
1029     {
1030       fprintf(stderr,"Found in msg db, updating acked field %d -> 1, seq %s, record %ld\n",
1031               msg_data[msg_index[record]].acked,
1032               seq,
1033               record);
1034     }
1035     // Only cause an update if this is the first ack.  This
1036     // reduces dialog "flashing" a great deal
1037     if ( msg_data[msg_index[record]].acked == 0 )
1038     {
1039 
1040       // Check for my callsign (including SSID).  If found,
1041       // update any open message dialogs
1042       if (is_my_call(msg_data[msg_index[record]].from_call_sign, 1) )
1043       {
1044 
1045         //fprintf(stderr,"From: %s\tTo: %s\n",
1046         //    msg_data[msg_index[record]].from_call_sign,
1047         //    msg_data[msg_index[record]].call_sign);
1048 
1049         do_update++;
1050       }
1051     }
1052     else    // This message has already been acked.
1053     {
1054     }
1055 
1056     if (cancel)
1057     {
1058       msg_data[msg_index[record]].acked = (char)3;
1059     }
1060     else if (timeout)
1061     {
1062       msg_data[msg_index[record]].acked = (char)2;
1063     }
1064     else
1065     {
1066       msg_data[msg_index[record]].acked = (char)1;
1067     }
1068 
1069     // Set the interval to zero so that we don't display it
1070     // anymore in the dialog.  Same for tries.
1071     msg_data[msg_index[record]].interval = 0;
1072     msg_data[msg_index[record]].tries = 0;
1073 
1074     if (debug_level & 1)
1075     {
1076       fprintf(stderr,"Found in msg db, updating acked field %d -> 1, seq %s, record %ld\n\n",
1077               msg_data[msg_index[record]].acked,
1078               seq,
1079               record);
1080     }
1081   }
1082   else
1083   {
1084     if (debug_level & 1)
1085     {
1086       fprintf(stderr,"Matching message not found\n");
1087     }
1088   }
1089 
1090   if (do_update)
1091   {
1092 
1093     update_messages(1); // Force an update
1094 
1095     // Call check_popup_messages() here in order to pop up any
1096     // closed Send Message dialogs.  For first ack's or
1097     // CANCELLED messages it is less important, but for TIMEOUT
1098     // messages it is very important.
1099     //
1100     (void)check_popup_window(m_fill.call_sign, 2);  // Calls update_messages()
1101   }
1102 }
1103 
1104 
1105 
1106 
1107 
1108 // Called when we receive a REJ packet (reject).  Sets the "acked"
1109 // field in a Message to 4 to indicate that the message has been
1110 // rejected by the remote station.  This gets rid of the
1111 // highlighting in the Send Message dialog for that message line.
1112 // This lets us know which messages have been rejected and which
1113 // have not.  We use this in update_messages() to flag that
1114 // "*REJECTED*" should prefix the string.
1115 //
1116 // The most common source of REJ packets would be from sending to a
1117 // D700A who's buffers are full, so that it can't take another
1118 // message.
1119 //
msg_record_rej(char * to_call_sign,char * my_call,char * seq)1120 void msg_record_rej(char *to_call_sign,
1121                     char *my_call,
1122                     char *seq)
1123 {
1124   Message m_fill;
1125   long record;
1126   int do_update = 0;
1127 
1128   if (debug_level & 1)
1129   {
1130     fprintf(stderr,"Recording rej for message to: %s, seq: %s\n",
1131             to_call_sign,
1132             seq);
1133   }
1134 
1135   // Find the corresponding message in msg_data[i], set the
1136   // "acked" field to four.
1137 
1138   substr(m_fill.call_sign, to_call_sign, MAX_CALLSIGN);
1139   (void)remove_trailing_asterisk(m_fill.call_sign);
1140 
1141   substr(m_fill.from_call_sign, my_call, MAX_CALLSIGN);
1142   (void)remove_trailing_asterisk(m_fill.from_call_sign);
1143 
1144   substr(m_fill.seq, seq, MAX_MESSAGE_ORDER);
1145   (void)remove_trailing_spaces(m_fill.seq);
1146   (void)remove_leading_spaces(m_fill.seq);
1147 
1148   // Look for a message with the same to_call_sign, my_call,
1149   // and seq number
1150   record = msg_find_data(&m_fill);
1151 
1152   if (record == -1L)   // No match yet, try another tactic.
1153   {
1154     if (seq[2] == '}' && strlen(seq) == 3)
1155     {
1156 
1157       // Try it again without the trailing '}' character
1158       m_fill.from_call_sign[2] = '\0';
1159 
1160       // Look for a message with the same to_call_sign,
1161       // my_call, and seq number (minus the trailing '}')
1162       record = msg_find_data(&m_fill);
1163     }
1164   }
1165 
1166   if(record != -1L)       // Found a match!
1167   {
1168     if (debug_level & 1)
1169     {
1170       fprintf(stderr,"Found in msg db, updating acked field %d -> 4, seq %s, record %ld\n",
1171               msg_data[msg_index[record]].acked,
1172               seq,
1173               record);
1174     }
1175     // Only cause an update if this is the first rej.  This
1176     // reduces dialog "flashing" a great deal
1177     if ( msg_data[msg_index[record]].acked == 0 )
1178     {
1179 
1180       // Check for my callsign (including SSID).  If found,
1181       // update any open message dialogs
1182       if (is_my_call(msg_data[msg_index[record]].from_call_sign, 1) )
1183       {
1184 
1185         //fprintf(stderr,"From: %s\tTo: %s\n",
1186         //    msg_data[msg_index[record]].from_call_sign,
1187         //    msg_data[msg_index[record]].call_sign);
1188 
1189         do_update++;
1190       }
1191     }
1192     else    // This message has already been acked.
1193     {
1194     }
1195 
1196     // Actually record the REJ here
1197     msg_data[msg_index[record]].acked = (char)4;
1198 
1199     // Set the interval to zero so that we don't display it
1200     // anymore in the dialog.  Same for tries.
1201     msg_data[msg_index[record]].interval = 0;
1202     msg_data[msg_index[record]].tries = 0;
1203 
1204     if (debug_level & 1)
1205     {
1206       fprintf(stderr,"Found in msg db, updating acked field %d -> 4, seq %s, record %ld\n\n",
1207               msg_data[msg_index[record]].acked,
1208               seq,
1209               record);
1210     }
1211   }
1212   else
1213   {
1214     if (debug_level & 1)
1215     {
1216       fprintf(stderr,"Matching message not found\n");
1217     }
1218   }
1219 
1220   if (do_update)
1221   {
1222 
1223     update_messages(1); // Force an update
1224 
1225     // Call check_popup_messages() here in order to pop up any
1226     // closed Send Message dialogs.  For first ack's or
1227     // CANCELLED messages it is less important, but for TIMEOUT
1228     // messages it is very important.
1229     //
1230     (void)check_popup_window(m_fill.call_sign, 2);  // Calls update_messages()
1231   }
1232 }
1233 
1234 
1235 
1236 
1237 
1238 // Called from check_and_transmit_messages().  Updates the interval
1239 // field in our message record for the message currently being
1240 // transmitted.  We'll use this in the Send Message dialog to
1241 // display the current message interval.
1242 //
msg_record_interval_tries(char * to_call_sign,char * my_call,char * seq,time_t interval,int tries)1243 void msg_record_interval_tries(char *to_call_sign,
1244                                char *my_call,
1245                                char *seq,
1246                                time_t interval,
1247                                int tries)
1248 {
1249   Message m_fill;
1250   long record;
1251 
1252   if (debug_level & 1)
1253   {
1254     fprintf(stderr,"Recording interval for message to: %s, seq: %s\n",
1255             to_call_sign,
1256             seq);
1257   }
1258 
1259   // Find the corresponding message in msg_data[i]
1260 
1261   substr(m_fill.call_sign, to_call_sign, MAX_CALLSIGN);
1262   (void)remove_trailing_asterisk(m_fill.call_sign);
1263 
1264   substr(m_fill.from_call_sign, my_call, MAX_CALLSIGN);
1265   (void)remove_trailing_asterisk(m_fill.from_call_sign);
1266 
1267   substr(m_fill.seq, seq, MAX_MESSAGE_ORDER);
1268   (void)remove_trailing_spaces(m_fill.seq);
1269   (void)remove_leading_spaces(m_fill.seq);
1270 
1271   // Look for a message with the same to_call_sign, my_call,
1272   // and seq number
1273   record = msg_find_data(&m_fill);
1274   if(record != -1L)       // Found a match!
1275   {
1276     if (debug_level & 1)
1277     {
1278       fprintf(stderr,
1279               "Found in msg db, updating interval field %ld -> 1, seq %s, record %ld\n",
1280               (long)msg_data[msg_index[record]].interval,
1281               seq,
1282               record);
1283     }
1284 
1285     msg_data[msg_index[record]].interval = interval;
1286     msg_data[msg_index[record]].tries = tries;
1287   }
1288   else
1289   {
1290     if (debug_level & 1)
1291     {
1292       fprintf(stderr,"Matching message not found\n");
1293     }
1294   }
1295 
1296   update_messages(1); // Force an update
1297 }
1298 
1299 
1300 
1301 
1302 
1303 // Returns: time_t for last_ack_sent
1304 //          -1 if the message doesn't pass our tests
1305 //           0 if it is a new message.
1306 //
1307 // Also returns the record number found if not passed a NULL pointer
1308 // in record_out or -1L if it's a new record.
1309 //
msg_data_add(char * call_sign,char * from_call,char * data,char * seq,char type,char from,long * record_out)1310 time_t msg_data_add(char *call_sign, char *from_call, char *data,
1311                     char *seq, char type, char from, long *record_out)
1312 {
1313   Message m_fill;
1314   long record;
1315   char time_data[MAX_TIME];
1316   int do_msg_update = 0;
1317   time_t last_ack_sent;
1318   int distance = -1;
1319   char temp[10];
1320   int group_message = 0;
1321 
1322 
1323   if (debug_level & 1)
1324   {
1325     fprintf(stderr,"msg_data_add start\n");
1326   }
1327   //fprintf(stderr,"from:%s, to:%s, seq:%s\n", from_call, call_sign, seq);
1328 
1329   // Set the default output condition.  We'll change this later if
1330   // we need to.
1331   if (record_out != NULL)
1332   {
1333     *record_out = -1l;
1334   }
1335 
1336   // Check for some reasonable string in call_sign parameter
1337   if (call_sign == NULL || strlen(call_sign) == 0)
1338   {
1339     if (debug_level & 1)
1340     {
1341       fprintf(stderr,"msg_data_add():call_sign was NULL or empty, exiting\n");
1342     }
1343     return((time_t)-1l);
1344   }
1345   //else
1346   //fprintf(stderr,"msg_data_add():call_sign: %s\n", call_sign);
1347 
1348   if ( (data != NULL) && (strlen(data) > MAX_MESSAGE_LENGTH) )
1349   {
1350     if (debug_level & 2)
1351     {
1352       fprintf(stderr,"msg_data_add:  Message length too long\n");
1353     }
1354     return((time_t)-1l);
1355   }
1356 
1357   substr(m_fill.call_sign, call_sign, MAX_CALLSIGN);
1358   (void)remove_trailing_asterisk(m_fill.call_sign);
1359 
1360   substr(m_fill.from_call_sign, from_call, MAX_CALLSIGN);
1361   (void)remove_trailing_asterisk(m_fill.call_sign);
1362 
1363   substr(m_fill.seq, seq, MAX_MESSAGE_ORDER);
1364   (void)remove_trailing_spaces(m_fill.seq);
1365   (void)remove_leading_spaces(m_fill.seq);
1366 
1367   // If the sequence number is blank, then it may have been a query,
1368   // directed query, or group message.  Assume it is a new message in
1369   // each case and add it.
1370 
1371   if (seq[0] != '\0')     // Normal station->station messaging or
1372   {
1373     // bulletins
1374     // Look for a message with the same call_sign,
1375     // from_call_sign, and seq number
1376     record = msg_find_data(&m_fill);
1377     //fprintf(stderr,"RECORD %ld  \n",record);
1378     //fprintf(stderr,"Normal station->station message\n");
1379   }
1380   else    // Group message/query/etc.
1381   {
1382     record = -1L;
1383     group_message++;    // Flag it as a group message
1384     //fprintf(stderr,"Group message/query/etc\n");
1385   }
1386   msg_clear_data(&m_fill);
1387   if(record != -1L)   /* fill old data */
1388   {
1389     msg_get_data(&m_fill, record);
1390     last_ack_sent = m_fill.last_ack_sent;
1391     //fprintf(stderr,"Found: last_ack_sent: %ld\n",m_fill.last_ack_sent);
1392 
1393     //fprintf(stderr,"Found a duplicate message.  Updating fields, seq %s\n",seq);
1394 
1395     // If message is different this time, do an update to the
1396     // send message window and update the sec_heard field.  The
1397     // remote station must have restarted and is re-using the
1398     // sequence numbers.  What a pain!
1399     if (strcmp(m_fill.message_line,data) != 0)
1400     {
1401       m_fill.sec_heard = sec_now();
1402       last_ack_sent = (time_t)0;
1403       //fprintf(stderr,"Message is different this time: Setting last_ack_sent to 0\n");
1404 
1405       if (type != MESSAGE_BULLETIN)   // Not a bulletin
1406       {
1407         do_msg_update++;
1408       }
1409     }
1410 
1411     // If message is the same, but the sec_heard field is quite
1412     // old (more than 8 hours), the remote station must have
1413     // restarted, is re-using the sequence numbers, and just
1414     // happened to send the same message with the same sequence
1415     // number.  Again, what a pain!  Either that, or we
1416     // connected to a spigot with a _really_ long queue!
1417     if (m_fill.sec_heard < (sec_now() - (8 * 60 * 60) ))
1418     {
1419       m_fill.sec_heard = sec_now();
1420       last_ack_sent = (time_t)0;
1421       //fprintf(stderr,"Found >8hrs old: Setting last_ack_sent to 0\n");
1422 
1423       if (type != MESSAGE_BULLETIN)   // Not a bulletin
1424       {
1425         do_msg_update++;
1426       }
1427     }
1428 
1429     // Check for zero time
1430     if (m_fill.sec_heard == (time_t)0)
1431     {
1432       m_fill.sec_heard = sec_now();
1433       fprintf(stderr,"Zero time on a previous message.\n");
1434     }
1435   }
1436   else
1437   {
1438     // Only do this if it's a new message.  This keeps things
1439     // more in sequence by not updating the time stamps
1440     // constantly on old messages that don't get ack'ed.
1441     m_fill.sec_heard = sec_now();
1442     last_ack_sent = (time_t)0;
1443     //fprintf(stderr,"New msg: Setting last_ack_sent to 0\n");
1444 
1445     if (type != MESSAGE_BULLETIN)   // Not a bulletin
1446     {
1447       //fprintf(stderr,"Found new message\n");
1448       do_msg_update++;    // Always do an update to the
1449       // message window for new messages
1450     }
1451   }
1452 
1453   /* FROM */
1454   m_fill.data_via=from;
1455   m_fill.active=RECORD_ACTIVE;
1456   m_fill.type=type;
1457   if (m_fill.heard_via_tnc != VIA_TNC)
1458   {
1459     m_fill.heard_via_tnc = (from == 'T') ? VIA_TNC : NOT_VIA_TNC;
1460   }
1461 
1462   distance = (int)(distance_from_my_station(from_call,temp) + 0.9999);
1463 
1464   if (distance != 0)      // Have a posit from the sending station
1465   {
1466     m_fill.position_known = 1;
1467     //fprintf(stderr,"Position known: %s\n",from_call);
1468   }
1469   else
1470   {
1471     //fprintf(stderr,"Position not known: %s\n",from_call);
1472   }
1473 
1474   substr(m_fill.call_sign,call_sign,MAX_CALLSIGN);
1475   (void)remove_trailing_asterisk(m_fill.call_sign);
1476 
1477   substr(m_fill.from_call_sign,from_call,MAX_CALLSIGN);
1478   (void)remove_trailing_asterisk(m_fill.from_call_sign);
1479 
1480   // Update the message field
1481   substr(m_fill.message_line,data,MAX_MESSAGE_LENGTH);
1482 
1483   substr(m_fill.seq,seq,MAX_MESSAGE_ORDER);
1484   (void)remove_trailing_spaces(m_fill.seq);
1485   (void)remove_leading_spaces(m_fill.seq);
1486 
1487   // Create a timestamp from the current time
1488   xastir_snprintf(m_fill.packet_time,
1489                   sizeof(m_fill.packet_time),
1490                   "%s",
1491                   get_time(time_data));
1492 
1493   if(record == -1L)       // No old record found
1494   {
1495     if (group_message)
1496     {
1497       m_fill.acked = 1;  // Group msgs/queries need no ack
1498     }
1499     else
1500     {
1501       m_fill.acked = 0;  // We can't have been acked yet
1502     }
1503 
1504     m_fill.interval = 0;
1505     m_fill.tries = 0;
1506 
1507     // We'll be sending an ack right away if this is a new
1508     // message, so might as well set the time now so that we
1509     // don't care about failing to set it in
1510     // msg_update_ack_stamp due to the record number being -1.
1511     m_fill.last_ack_sent = sec_now();
1512 
1513     msg_input_database(&m_fill);    // Create a new entry
1514     //fprintf(stderr,"No record found: Setting last_ack_sent to sec_now()00\n");
1515   }
1516   else    // Old record found
1517   {
1518     //fprintf(stderr,"Replacing the message in the database, seq %s\n",seq);
1519     msg_replace_data(&m_fill, record);  // Copy fields from m_fill to record
1520   }
1521 
1522   /* display messages */
1523   if (type == MESSAGE_MESSAGE)
1524   {
1525     all_messages(from,call_sign,from_call,data);
1526   }
1527 
1528   // Check for my callsign (including SSID).  If found, update any
1529   // open message dialogs
1530   if (       is_my_call(m_fill.from_call_sign, 1)
1531              || is_my_call(m_fill.call_sign, 1) )
1532   {
1533 
1534     if (do_msg_update)
1535     {
1536       update_messages(1); // Force an update
1537     }
1538   }
1539 
1540   if (debug_level & 1)
1541   {
1542     fprintf(stderr,"msg_data_add end\n");
1543   }
1544 
1545   // Return the important variables we'll need
1546   if (record_out != NULL)
1547   {
1548     *record_out = record;
1549   }
1550 
1551   //fprintf(stderr,"\nrecord_out:%ld record %ld\n",*record_out,record);
1552   return(last_ack_sent);
1553 
1554 }   // End of msg_data_add()
1555 
1556 
1557 
1558 
1559 
1560 // alert_data_add:  Function which adds NWS weather alerts to the
1561 // alert hash.
1562 //
1563 // This function adds alerts directly to the alert hash, bypassing
1564 // the message list and associated message-scan functions.
1565 //
alert_data_add(char * call_sign,char * from_call,char * data,char * seq,char type,char from)1566 void alert_data_add(char *call_sign, char *from_call, char *data,
1567                     char *seq, char type, char from)
1568 {
1569   Message m_fill;
1570   char time_data[MAX_TIME];
1571   char user_base_dir[MAX_VALUE];
1572 
1573   if (debug_level & 2)
1574   {
1575     fprintf(stderr,"alert_data_add start\n");
1576   }
1577 
1578 
1579   if (log_wx_alert_data && from != DATA_VIA_FILE)
1580   {
1581     char temp_msg[MAX_MESSAGE_LENGTH+1];
1582 
1583     // Attempt to reconstruct the original weather alert packet
1584     // here, minus the path.
1585     xastir_snprintf(temp_msg,
1586                     sizeof(temp_msg),
1587                     "%s>APRS::%-9s:%s{%s",
1588                     from_call,
1589                     call_sign,
1590                     data,
1591                     seq);
1592     log_data( get_user_base_dir(LOGFILE_WX_ALERT, user_base_dir,
1593                                 sizeof(user_base_dir)),
1594               temp_msg);
1595     //        fprintf(stderr, "%s\n", temp_msg);
1596   }
1597 
1598 
1599   if ( (data != NULL) && (strlen(data) > MAX_MESSAGE_LENGTH) )
1600   {
1601     if (debug_level & 2)
1602     {
1603       fprintf(stderr,"alert_data_add:  Message length too long\n");
1604     }
1605     return;
1606   }
1607 
1608   substr(m_fill.call_sign, call_sign, MAX_CALLSIGN);
1609   (void)remove_trailing_asterisk(m_fill.call_sign);
1610 
1611   substr(m_fill.from_call_sign, from_call, MAX_CALLSIGN);
1612   (void)remove_trailing_asterisk(m_fill.call_sign);
1613 
1614   substr(m_fill.seq, seq, MAX_MESSAGE_ORDER);
1615   (void)remove_trailing_spaces(m_fill.seq);
1616   (void)remove_leading_spaces(m_fill.seq);
1617 
1618   m_fill.sec_heard = sec_now();
1619 
1620   /* FROM */
1621   m_fill.data_via=from;
1622   m_fill.active=RECORD_ACTIVE;
1623   m_fill.type=type;
1624 
1625   // We don't have a value filled in yet here!
1626   //if (m_fill.heard_via_tnc != VIA_TNC)
1627   m_fill.heard_via_tnc = (from == 'T') ? VIA_TNC : NOT_VIA_TNC;
1628 
1629   substr(m_fill.call_sign,call_sign,MAX_CALLSIGN);
1630   (void)remove_trailing_asterisk(m_fill.call_sign);
1631 
1632   substr(m_fill.from_call_sign,from_call,MAX_CALLSIGN);
1633   (void)remove_trailing_asterisk(m_fill.from_call_sign);
1634 
1635   // Update the message field
1636   substr(m_fill.message_line,data,MAX_MESSAGE_LENGTH);
1637 
1638   substr(m_fill.seq,seq,MAX_MESSAGE_ORDER);
1639   (void)remove_trailing_spaces(m_fill.seq);
1640   (void)remove_leading_spaces(m_fill.seq);
1641 
1642   // Create a timestamp from the current time
1643   xastir_snprintf(m_fill.packet_time,
1644                   sizeof(m_fill.packet_time),
1645                   "%s",
1646                   get_time(time_data));
1647 
1648   // Go try to add it to our alert hash.  alert_build_list() will
1649   // check for duplicates before adding it.
1650 
1651   alert_build_list(&m_fill);
1652 
1653   // This function fills in the Shapefile filename and index
1654   // so that we can later draw it.
1655   fill_in_new_alert_entries();
1656 
1657   if (debug_level & 2)
1658   {
1659     fprintf(stderr,"alert_data_add end\n");
1660   }
1661 
1662 }   // End of alert_data_add()
1663 
1664 
1665 
1666 
1667 
1668 // What I'd like to do for the following routine:  Use
1669 // XmTextGetInsertionPosition() or XmTextGetCursorPosition() to
1670 // find the last of the text.  Could also save the position for
1671 // each SendMessage window.  Compare the timestamps of messages
1672 // found with the last update time.  If newer, then add them to
1673 // the end.  This should stop the incessant scrolling.
1674 
1675 // Another idea, easier method:  Create a buffer.  Snag out the
1676 // messages from the array and sort by time.  Put them into a
1677 // buffer.  Figure out the length of the text widget, and append
1678 // the extra length of the buffer onto the end of the text widget.
1679 // Once the message data is turned into a linked list, it might
1680 // be sorted already by time, so this window will look better
1681 // anyway.
1682 
1683 // Calling update_messages with force == 1 will cause an update
1684 // no matter what message_update_time() says.
update_messages(int force)1685 void update_messages(int force)
1686 {
1687   static XmTextPosition pos;
1688   char temp1[MAX_CALLSIGN+1];
1689   char temp2[500];
1690   char stemp[20];
1691   long i;
1692   int mw_p;
1693   char *temp_ptr;
1694 
1695 
1696   if ( message_update_time() || force)
1697   {
1698 
1699     //fprintf(stderr,"update_messages()\n");
1700 
1701     //fprintf(stderr,"Um %d\n",(int)sec_now() );
1702 
1703     /* go through all mw_p's! */
1704 
1705     // Perform this for each message window
1706     for (mw_p=0; msg_index && mw_p < MAX_MESSAGE_WINDOWS; mw_p++)
1707     {
1708       //pos=0;
1709 
1710       begin_critical_section(&send_message_dialog_lock, "db.c:update_messages" );
1711 
1712       if (mw[mw_p].send_message_dialog!=NULL/* && mw[mw_p].message_group==1*/)
1713       {
1714 
1715         //fprintf(stderr,"\n");
1716 
1717         //fprintf(stderr,"found send_message_dialog\n");
1718 
1719         // Clear the text from message window
1720         XmTextReplace(mw[mw_p].send_message_text,
1721                       (XmTextPosition) 0,
1722                       XmTextGetLastPosition(mw[mw_p].send_message_text),
1723                       "");
1724 
1725         // Snag the callsign you're dealing with from the message dialogue
1726         if (mw[mw_p].send_message_call_data != NULL)
1727         {
1728           temp_ptr = XmTextFieldGetString(mw[mw_p].send_message_call_data);
1729           xastir_snprintf(temp1,
1730                           sizeof(temp1),
1731                           "%s",
1732                           temp_ptr);
1733           XtFree(temp_ptr);
1734 
1735           new_message_data--;
1736           if (new_message_data<0)
1737           {
1738             new_message_data=0;
1739           }
1740 
1741           if(strlen(temp1)>0)     // We got a callsign from the dialog so
1742           {
1743             // create a linked list of the message indexes in time-sorted order
1744 
1745             typedef struct _index_record
1746             {
1747               int index;
1748               time_t sec_heard;
1749               struct _index_record *next;
1750             } index_record;
1751             index_record *head = NULL;
1752             index_record *p_prev = NULL;
1753             index_record *p_next = NULL;
1754 
1755             // Allocate the first record (a dummy record)
1756             head = (index_record *)malloc(sizeof(index_record));
1757             CHECKMALLOC(head);
1758 
1759             head->index = -1;
1760             head->sec_heard = (time_t)0;
1761             head->next = NULL;
1762 
1763             (void)remove_trailing_spaces(temp1);
1764             (void)to_upper(temp1);
1765 
1766             pos = 0;
1767             // Loop through looking for messages to/from
1768             // that callsign (including SSID)
1769             for (i = 0; i < msg_index_end; i++)
1770             {
1771               if (msg_data[msg_index[i]].active == RECORD_ACTIVE
1772                   && (strcmp(temp1, msg_data[msg_index[i]].from_call_sign) == 0
1773                       || strcmp(temp1,msg_data[msg_index[i]].call_sign) == 0)
1774                   && (is_my_call(msg_data[msg_index[i]].from_call_sign, 1)
1775                       || is_my_call(msg_data[msg_index[i]].call_sign, 1)
1776                       || mw[mw_p].message_group ) )
1777               {
1778                 int done = 0;
1779 
1780                 // Message matches our parameters so
1781                 // save the relevant data about the
1782                 // message in our linked list.  Compare
1783                 // the sec_heard field to see whether
1784                 // we're higher or lower, and insert the
1785                 // record at the correct spot in the
1786                 // list.  We end up with a time-sorted
1787                 // list.
1788                 p_prev  = head;
1789                 p_next = p_prev->next;
1790                 while (!done && (p_next != NULL))    // Loop until end of list or record inserted
1791                 {
1792 
1793                   //fprintf(stderr,"Looping, looking for insertion spot\n");
1794 
1795                   if (p_next->sec_heard <= msg_data[msg_index[i]].sec_heard)
1796                   {
1797                     // Advance one record
1798                     p_prev = p_next;
1799                     p_next = p_prev->next;
1800                   }
1801                   else    // We found the correct insertion spot
1802                   {
1803                     done++;
1804                   }
1805                 }
1806 
1807                 //fprintf(stderr,"Inserting\n");
1808 
1809                 // Add the record in between p_prev and
1810                 // p_next, even if we're at the end of
1811                 // the list (in that case p_next will be
1812                 // NULL.
1813                 p_prev->next = (index_record *)malloc(sizeof(index_record));
1814                 CHECKMALLOC(p_prev->next);
1815 
1816                 p_prev->next->next = p_next; // Link to rest of records or NULL
1817                 p_prev->next->index = i;
1818                 p_prev->next->sec_heard = msg_data[msg_index[i]].sec_heard;
1819                 // Remember to free this entire linked list before exiting the loop for
1820                 // this message window!
1821               }
1822             }
1823             // Done processing the entire list for this
1824             // message window.
1825 
1826             //fprintf(stderr,"Done inserting/looping\n");
1827 
1828             if (head->next != NULL)     // We have messages to display
1829             {
1830               int done = 0;
1831 
1832               //fprintf(stderr,"We have messages to display\n");
1833 
1834               // Run through the linked list and dump the
1835               // info out.  It's now in time-sorted order.
1836 
1837               // Another optimization would be to keep a count of records added, then
1838               // later when we were dumping it out to the window, only dump the last
1839               // XX records out.
1840 
1841               p_prev = head->next;    // Skip the first dummy record
1842               p_next = p_prev->next;
1843               while (!done && (p_prev != NULL))    // Loop until end of list
1844               {
1845                 int j = p_prev->index;  // Snag the index out of the record
1846                 char prefix[50];
1847                 char interval_str[50];
1848                 int offset = 22;    // Offset for highlighting
1849 
1850 
1851                 //fprintf(stderr,"\nLooping through, reading messages\n");
1852 
1853                 //fprintf(stderr,"acked: %d\n",msg_data[msg_index[j]].acked);
1854 
1855                 // Message matches so snag the important pieces into a string
1856                 xastir_snprintf(stemp, sizeof(stemp),
1857                                 "%c%c/%c%c %c%c:%c%c",
1858                                 msg_data[msg_index[j]].packet_time[0],
1859                                 msg_data[msg_index[j]].packet_time[1],
1860                                 msg_data[msg_index[j]].packet_time[2],
1861                                 msg_data[msg_index[j]].packet_time[3],
1862                                 msg_data[msg_index[j]].packet_time[8],
1863                                 msg_data[msg_index[j]].packet_time[9],
1864                                 msg_data[msg_index[j]].packet_time[10],
1865                                 msg_data[msg_index[j]].packet_time[11]
1866                                );
1867 
1868                 // Somewhere in here we appear to be losing the first message.  It
1869                 // doesn't get written to the window later in the QSO.  Same for
1870                 // closing the window and re-opening it, putting the same callsign
1871                 // in and pressing "New Call" button.  First message is missing.
1872 
1873                 // Label the message line with who sent it.
1874                 // If acked = 2 a timeout has occurred
1875                 // If acked = 3 a cancel has occurred
1876                 if (msg_data[msg_index[j]].acked == 2)
1877                 {
1878                   xastir_snprintf(prefix,
1879                                   sizeof(prefix),
1880                                   "%s ",
1881                                   langcode("WPUPMSB016") ); // "*TIMEOUT*"
1882                 }
1883                 else if (msg_data[msg_index[j]].acked == 3)
1884                 {
1885                   xastir_snprintf(prefix,
1886                                   sizeof(prefix),
1887                                   "%s ",
1888                                   langcode("WPUPMSB017") ); // "*CANCELLED*"
1889                 }
1890                 else if (msg_data[msg_index[j]].acked == 4)
1891                 {
1892                   xastir_snprintf(prefix,
1893                                   sizeof(prefix),
1894                                   "%s ",
1895                                   langcode("WPUPMSB018") ); // "*REJECTED*"
1896                 }
1897                 else
1898                 {
1899                   prefix[0] = '\0';
1900                 }
1901 
1902                 if (msg_data[msg_index[j]].interval)
1903                 {
1904                   xastir_snprintf(interval_str,
1905                                   sizeof(interval_str),
1906                                   ">%d/%lds",
1907                                   msg_data[msg_index[j]].tries + 1,
1908                                   (long)msg_data[msg_index[j]].interval);
1909 
1910                   // Don't highlight the interval
1911                   // value
1912                   offset = offset + strlen(interval_str);
1913                 }
1914                 else
1915                 {
1916                   interval_str[0] = '\0';
1917                 }
1918 
1919                 xastir_snprintf(temp2, sizeof(temp2),
1920                                 "%s %-9s%s>%s%s\n",
1921                                 // Debug code.  Trying to find sorting error
1922                                 //"%ld  %s  %-9s>%s\n",
1923                                 //msg_data[msg_index[j]].sec_heard,
1924                                 stemp,
1925                                 msg_data[msg_index[j]].from_call_sign,
1926                                 interval_str,
1927                                 prefix,
1928                                 msg_data[msg_index[j]].message_line);
1929 
1930                 //fprintf(stderr,"message: %s\n", msg_data[msg_index[j]].message_line);
1931                 //fprintf(stderr,"update_messages: %s|%s", temp1, temp2);
1932 
1933                 if (debug_level & 2)
1934                 {
1935                   fprintf(stderr,"update_messages: %s|%s\n", temp1, temp2);
1936                 }
1937                 // Replace the text from pos to pos+strlen(temp2) by the string "temp2"
1938                 if (mw[mw_p].send_message_text != NULL)
1939                 {
1940 
1941                   // Insert the text at the end
1942                   //                                    XmTextReplace(mw[mw_p].send_message_text,
1943                   //                                            pos,
1944                   //                                            pos+strlen(temp2),
1945                   //                                            temp2);
1946 
1947                   XmTextInsert(mw[mw_p].send_message_text,
1948                                pos,
1949                                temp2);
1950 
1951                   // Set highlighting based on the
1952                   // "acked" field.  Callsign
1953                   // match here includes SSID.
1954                   //fprintf(stderr,"acked: %d\t",msg_data[msg_index[j]].acked);
1955                   if ( (msg_data[msg_index[j]].acked == 0)    // Not acked yet
1956                        && ( is_my_call(msg_data[msg_index[j]].from_call_sign, 1)) )
1957                   {
1958                     //fprintf(stderr,"Setting underline\t");
1959                     XmTextSetHighlight(mw[mw_p].send_message_text,
1960                                        pos+offset,
1961                                        pos+strlen(temp2),
1962                                        //XmHIGHLIGHT_SECONDARY_SELECTED); // Underlining
1963                                        XmHIGHLIGHT_SELECTED);         // Reverse Video
1964                   }
1965                   else    // Message was acked, get rid of highlighting
1966                   {
1967                     //fprintf(stderr,"Setting normal\t");
1968                     XmTextSetHighlight(mw[mw_p].send_message_text,
1969                                        pos+offset,
1970                                        pos+strlen(temp2),
1971                                        XmHIGHLIGHT_NORMAL);
1972                   }
1973 
1974                   //fprintf(stderr,"Text: %s\n",temp2);
1975 
1976                   pos += strlen(temp2);
1977 
1978                 }
1979 
1980                 // Advance to the next record in the list
1981                 p_prev = p_next;
1982                 if (p_next != NULL)
1983                 {
1984                   p_next = p_prev->next;
1985                 }
1986 
1987               }   // End of while
1988             }   // End of if
1989             else    // No messages matched, list is empty
1990             {
1991             }
1992 
1993 // What does this do?  Move all of the text?
1994 //                        if (pos > 0) {
1995 //                            if (mw[mw_p].send_message_text != NULL) {
1996 //                                XmTextReplace(mw[mw_p].send_message_text,
1997 //                                        --pos,
1998 //                                        XmTextGetLastPosition(mw[mw_p].send_message_text),
1999 //                                        "");
2000 //                            }
2001 //                        }
2002 
2003             //fprintf(stderr,"Free'ing list\n");
2004 
2005             // De-allocate the linked list
2006             p_prev = head;
2007             while (p_prev != NULL)
2008             {
2009 
2010               //fprintf(stderr,"You're free!\n");
2011 
2012               p_next = p_prev->next;
2013               free(p_prev);
2014               p_prev = p_next;
2015             }
2016 
2017             // Show the last added message in the window
2018             XmTextShowPosition(mw[mw_p].send_message_text,
2019                                pos);
2020           }
2021         }
2022       }
2023 
2024       end_critical_section(&send_message_dialog_lock, "db.c:update_messages" );
2025 
2026     }
2027     last_message_update = sec_now();
2028 
2029     //fprintf(stderr,"Message index end: %ld\n",msg_index_end);
2030 
2031   }
2032 }
2033 
2034 
2035 
2036 
2037 
mdelete_messages_from(char * from)2038 void mdelete_messages_from(char *from)
2039 {
2040   long i;
2041 
2042   // Mark message records with RECORD_NOTACTIVE.  This will mark
2043   // them for re-use.
2044   for (i = 0; msg_index && i < msg_index_end; i++)
2045     if (strcmp(msg_data[i].call_sign, my_callsign) == 0 && strcmp(msg_data[i].from_call_sign, from) == 0)
2046     {
2047       msg_data[i].active = RECORD_NOTACTIVE;
2048     }
2049 }
2050 
2051 
2052 
2053 
2054 
mdelete_messages_to(char * to)2055 void mdelete_messages_to(char *to)
2056 {
2057   long i;
2058 
2059   // Mark message records with RECORD_NOTACTIVE.  This will mark
2060   // them for re-use.
2061   for (i = 0; msg_index && i < msg_index_end; i++)
2062     if (strcmp(msg_data[i].call_sign, to) == 0)
2063     {
2064       msg_data[i].active = RECORD_NOTACTIVE;
2065     }
2066 }
2067 
2068 
2069 
2070 
2071 
mdelete_messages(char * to_from)2072 void mdelete_messages(char *to_from)
2073 {
2074   long i;
2075 
2076   // Mark message records with RECORD_NOTACTIVE.  This will mark
2077   // them for re-use.
2078   for (i = 0; msg_index && i < msg_index_end; i++)
2079     if (strcmp(msg_data[i].call_sign, to_from) == 0 || strcmp(msg_data[i].from_call_sign, to_from) == 0)
2080     {
2081       msg_data[i].active = RECORD_NOTACTIVE;
2082     }
2083 }
2084 
2085 
2086 
2087 
2088 
mdata_delete_type(const char msg_type,const time_t reference_time)2089 void mdata_delete_type(const char msg_type, const time_t reference_time)
2090 {
2091   long i;
2092 
2093   // Mark message records with RECORD_NOTACTIVE.  This will mark
2094   // them for re-use.
2095   for (i = 0; msg_index && i < msg_index_end; i++)
2096 
2097     if ((msg_type == '\0' || msg_type == msg_data[i].type)
2098         && msg_data[i].active == RECORD_ACTIVE
2099         && msg_data[i].sec_heard < reference_time)
2100 
2101     {
2102       msg_data[i].active = RECORD_NOTACTIVE;
2103     }
2104 }
2105 
2106 
2107 
2108 
2109 
check_message_remove(time_t curr_sec)2110 void check_message_remove(time_t curr_sec)         // called in timing loop
2111 {
2112 
2113   // Time to check for old messages again?  (Currently every ten
2114   // minutes)
2115 #ifdef EXPIRE_DEBUG
2116   if ( last_message_remove < (curr_sec - DEBUG_MESSAGE_REMOVE_CYCLE) )
2117 #else // EXPIRE_DEBUG
2118   if ( last_message_remove < (curr_sec - MESSAGE_REMOVE_CYCLE) )
2119 #endif
2120   {
2121     // Yes it is.  Mark all messages that are older than
2122     // sec_remove with the RECORD_NOTACTIVE flag.  This will
2123     // mark them for re-use.
2124 #ifdef EXPIRE_DEBUG
2125     mdata_delete_type('\0', curr_sec-DEBUG_MESSAGE_REMOVE);
2126 #else   // EXPIRE_DEBUG
2127     mdata_delete_type('\0', curr_sec-sec_remove);
2128 #endif
2129 
2130     last_message_remove = curr_sec;
2131   }
2132 
2133   // Should we sort them at this point so that the unused ones are
2134   // near the end?  It looks like the message input functions do
2135   // this, so I guess we don't need to do it here.
2136 }
2137 
2138 
2139 
2140 
2141 
mscan_file(char msg_type,void (* function)(Message *))2142 void mscan_file(char msg_type, void (*function)(Message *))
2143 {
2144   long i;
2145 
2146   for (i = 0; msg_index && i < msg_index_end; i++)
2147     if ((msg_type == '\0' || msg_type == msg_data[msg_index[i]].type) &&
2148         msg_data[msg_index[i]].active == RECORD_ACTIVE)
2149     {
2150       function(&msg_data[msg_index[i]]);
2151     }
2152 }
2153 
2154 
2155 
2156 
2157 
mprint_record(Message * m_fill)2158 void mprint_record(Message *m_fill)
2159 {
2160   fprintf(stderr,
2161           "%-9s>%-9s %s:%5s %s:%c :%s\n",
2162           m_fill->from_call_sign,
2163           m_fill->call_sign,
2164           langcode("WPUPMSB013"), // "seq"
2165           m_fill->seq,
2166           langcode("WPUPMSB014"), // "type"
2167           m_fill->type,
2168           m_fill->message_line);
2169 }
2170 
2171 
2172 
2173 
2174 
mdisplay_file(char msg_type)2175 void mdisplay_file(char msg_type)
2176 {
2177   fprintf(stderr,"\n\n");
2178   mscan_file(msg_type, mprint_record);
2179   fprintf(stderr,"\tmsg_index_end %ld, msg_index_max %ld\n", msg_index_end, msg_index_max);
2180 }
2181 
2182 
2183 
2184 
2185 
2186 /////////////////////////////////////// Station Data ///////////////////////////////////////////
2187 
2188 
2189 
2190 
2191 
pad_callsign(char * callsignout,char * callsignin)2192 void pad_callsign(char *callsignout, char *callsignin)
2193 {
2194   int i,l;
2195 
2196   l=(int)strlen(callsignin);
2197   for(i=0; i<9; i++)
2198   {
2199     if(i<l)
2200     {
2201       if(isalnum((int)callsignin[i]) || callsignin[i]=='-')
2202       {
2203         callsignout[i]=callsignin[i];
2204       }
2205       else
2206       {
2207         callsignout[i] = ' ';
2208       }
2209     }
2210     else
2211     {
2212       callsignout[i] = ' ';
2213     }
2214   }
2215   callsignout[i] = '\0';
2216 }
2217 
2218 
2219 
2220 
2221 
2222 // Check for valid overlay characters:  'A-Z', '0-9', and 'a-j'.  If
2223 // 'a-j', it's from a compressed posit, and we need to convert it to
2224 // '0-9'.
overlay_symbol(char symbol,char data,DataRow * fill)2225 void overlay_symbol(char symbol, char data, DataRow *fill)
2226 {
2227 
2228   if ( data != '/' && data !='\\')    // Symbol overlay
2229   {
2230 
2231     if (data >= 'a' && data <= 'j')
2232     {
2233       // Found a compressed posit numerical overlay
2234       data = data - 'a'+'0';  // Convert to a digit
2235     }
2236     if ( (data >= '0' && data <= '9')
2237          || (data >= 'A' && data <= 'Z') )
2238     {
2239       // Found normal overlay character
2240       fill->aprs_symbol.aprs_type = '\\';
2241       fill->aprs_symbol.special_overlay = data;
2242     }
2243     else
2244     {
2245       // Bad overlay character.  Don't use it.  Insert the
2246       // normal alternate table character instead.
2247       fill->aprs_symbol.aprs_type = '\\';
2248       fill->aprs_symbol.special_overlay='\0';
2249     }
2250   }
2251   else      // No overlay character
2252   {
2253     fill->aprs_symbol.aprs_type = data;
2254     fill->aprs_symbol.special_overlay='\0';
2255   }
2256   fill->aprs_symbol.aprs_symbol = symbol;
2257 }
2258 
2259 
2260 
2261 
2262 
id_callsign(char * call_sign,char * to_call)2263 APRS_Symbol *id_callsign(char *call_sign, char * to_call)
2264 {
2265   char *ptr;
2266   char *id = "/aUfbYX's><OjRkv";
2267   char hold[MAX_CALLSIGN+1];
2268   int index;
2269   static APRS_Symbol symbol;
2270 
2271   symbol.aprs_symbol = '/';
2272   symbol.special_overlay = '\0';
2273   symbol.aprs_type ='/';
2274   ptr=strchr(call_sign,'-');
2275   if(ptr!=NULL)                      /* get symbol from SSID */
2276     if((index=atoi(ptr+1))<= 15)
2277     {
2278       symbol.aprs_symbol = id[index];
2279     }
2280 
2281   if (strncmp(to_call, "GPS", 3) == 0 || strncmp(to_call, "SPC", 3) == 0 || strncmp(to_call, "SYM", 3) == 0)
2282   {
2283     substr(hold, to_call+3, 3);
2284     if ((ptr = strpbrk(hold, "->,")) != NULL)
2285     {
2286       *ptr = '\0';
2287     }
2288 
2289     if (strlen(hold) >= 2)
2290     {
2291       switch (hold[0])
2292       {
2293         case 'A':
2294           symbol.aprs_type = '\\';
2295         /* Falls through. */
2296 
2297         case 'P':
2298           if (('0' <= hold[1] && hold[1] <= '9') || ('A' <= hold[1] && hold[1] <= 'Z'))
2299           {
2300             symbol.aprs_symbol = hold[1];
2301           }
2302 
2303           break;
2304 
2305         case 'O':
2306           symbol.aprs_type = '\\';
2307         /* Falls through. */
2308 
2309         case 'B':
2310           switch (hold[1])
2311           {
2312             case 'B':
2313               symbol.aprs_symbol = '!';
2314               break;
2315             case 'C':
2316               symbol.aprs_symbol = '"';
2317               break;
2318             case 'D':
2319               symbol.aprs_symbol = '#';
2320               break;
2321             case 'E':
2322               symbol.aprs_symbol = '$';
2323               break;
2324             case 'F':
2325               symbol.aprs_symbol = '%';
2326               break;
2327             case 'G':
2328               symbol.aprs_symbol = '&';
2329               break;
2330             case 'H':
2331               symbol.aprs_symbol = '\'';
2332               break;
2333             case 'I':
2334               symbol.aprs_symbol = '(';
2335               break;
2336             case 'J':
2337               symbol.aprs_symbol = ')';
2338               break;
2339             case 'K':
2340               symbol.aprs_symbol = '*';
2341               break;
2342             case 'L':
2343               symbol.aprs_symbol = '+';
2344               break;
2345             case 'M':
2346               symbol.aprs_symbol = ',';
2347               break;
2348             case 'N':
2349               symbol.aprs_symbol = '-';
2350               break;
2351             case 'O':
2352               symbol.aprs_symbol = '.';
2353               break;
2354             case 'P':
2355               symbol.aprs_symbol = '/';
2356               break;
2357           }
2358           break;
2359 
2360         case 'D':
2361           symbol.aprs_type = '\\';
2362         /* Falls through. */
2363 
2364         case 'H':
2365           switch (hold[1])
2366           {
2367             case 'S':
2368               symbol.aprs_symbol = '[';
2369               break;
2370             case 'T':
2371               symbol.aprs_symbol = '\\';
2372               break;
2373             case 'U':
2374               symbol.aprs_symbol = ']';
2375               break;
2376             case 'V':
2377               symbol.aprs_symbol = '^';
2378               break;
2379             case 'W':
2380               symbol.aprs_symbol = '_';
2381               break;
2382             case 'X':
2383               symbol.aprs_symbol = '`';
2384               break;
2385           }
2386           break;
2387 
2388         case 'N':
2389           symbol.aprs_type = '\\';
2390         /* Falls through. */
2391 
2392         case 'M':
2393           switch (hold[1])
2394           {
2395             case 'R':
2396               symbol.aprs_symbol = ':';
2397               break;
2398             case 'S':
2399               symbol.aprs_symbol = ';';
2400               break;
2401             case 'T':
2402               symbol.aprs_symbol = '<';
2403               break;
2404             case 'U':
2405               symbol.aprs_symbol = '=';
2406               break;
2407             case 'V':
2408               symbol.aprs_symbol = '>';
2409               break;
2410             case 'W':
2411               symbol.aprs_symbol = '?';
2412               break;
2413             case 'X':
2414               symbol.aprs_symbol = '@';
2415               break;
2416           }
2417           break;
2418 
2419         case 'Q':
2420           symbol.aprs_type = '\\';
2421         /* Falls through. */
2422 
2423         case 'J':
2424           switch (hold[1])
2425           {
2426             case '1':
2427               symbol.aprs_symbol = '{';
2428               break;
2429             case '2':
2430               symbol.aprs_symbol = '|';
2431               break;
2432             case '3':
2433               symbol.aprs_symbol = '}';
2434               break;
2435             case '4':
2436               symbol.aprs_symbol = '~';
2437               break;
2438           }
2439           break;
2440 
2441         case 'S':
2442           symbol.aprs_type = '\\';
2443         /* Falls through. */
2444 
2445         case 'L':
2446           if ('A' <= hold[1] && hold[1] <= 'Z')
2447           {
2448             symbol.aprs_symbol = tolower((int)hold[1]);
2449           }
2450 
2451           break;
2452       }
2453       if (hold[2])
2454       {
2455         if (hold[2] >= 'a' && hold[2] <= 'j')
2456         {
2457           // Compressed mode numeric overlay
2458           symbol.special_overlay = hold[2] - 'a';
2459         }
2460         else if ( (hold[2] >= '0' && hold[2] <= '9')
2461                   || (hold[2] >= 'A' && hold[2] <= 'Z') )
2462         {
2463           // Normal overlay character
2464           symbol.special_overlay = hold[2];
2465         }
2466         else
2467         {
2468           // Bad overlay character found
2469           symbol.special_overlay = '\0';
2470         }
2471       }
2472       else
2473       {
2474         // No overlay character found
2475         symbol.special_overlay = '\0';
2476       }
2477     }
2478   }
2479   return(&symbol);
2480 }
2481 
2482 
2483 
2484 
2485 
2486 /******************************** Sort begin *************************** ****/
2487 
2488 
2489 
2490 
2491 
clear_sort_file(char * filename)2492 void  clear_sort_file(char *filename)
2493 {
2494   char ptr_filename[400];
2495 
2496   xastir_snprintf(ptr_filename, sizeof(ptr_filename), "%s-ptr", filename);
2497   (void)unlink(filename);
2498   (void)unlink(ptr_filename);
2499 }
2500 
2501 
2502 
2503 
2504 
sort_reset_pointers(FILE * pointer,long new_data_ptr,long records,int type,long start_ptr)2505 void sort_reset_pointers(FILE *pointer,long new_data_ptr,long records, int type, long start_ptr)
2506 {
2507   long temp[13000];
2508   long buffn,start_buffn;
2509   long cp_records;
2510   long max_buffer;
2511   int my_size;
2512 
2513   my_size=(int)sizeof(new_data_ptr);
2514   max_buffer=13000l;
2515   if(type==0)
2516   {
2517     /* before start_ptr */
2518     /* copy back pointers */
2519     for(buffn=records; buffn > start_ptr; buffn-=max_buffer)
2520     {
2521       start_buffn=buffn-max_buffer;
2522       if(start_buffn<start_ptr)
2523       {
2524         start_buffn=start_ptr;
2525       }
2526 
2527       cp_records=buffn-start_buffn;
2528       (void)fseek(pointer,(my_size*start_buffn),SEEK_SET);
2529       if(fread(&temp,(my_size*cp_records),1,pointer)==1)
2530       {
2531         (void)fseek(pointer,(my_size*(start_buffn+1)),SEEK_SET);
2532         (void)fwrite(&temp,(my_size*cp_records),1,pointer);
2533       }
2534     }
2535     /* copy new pointer in */
2536     (void)fseek(pointer,(my_size*start_ptr),SEEK_SET);
2537     (void)fwrite(&new_data_ptr,(size_t)my_size,1,pointer);
2538   }
2539 }
2540 
2541 
2542 
2543 
2544 
sort_input_database(char * filename,char * fill,int size)2545 long sort_input_database(char *filename, char *fill, int size)
2546 {
2547   FILE *my_data;
2548   FILE *pointer;
2549   char file_data[2000];
2550 
2551   char ptr_filename[400];
2552 
2553   char tempfile[2000];
2554   char tempfill[2000];
2555 
2556   int ptr_size;
2557   long data_ptr;
2558   long new_data_ptr;
2559   long return_records;
2560   long records;
2561   long record_start;
2562   long record_end;
2563   long record_mid;
2564   int done;
2565 
2566   ptr_size=(int)sizeof(new_data_ptr);
2567   xastir_snprintf(ptr_filename, sizeof(ptr_filename), "%s-ptr", filename);
2568 
2569   /* get first string to sort on */
2570   if (1 != sscanf(fill,"%1999s",tempfill))
2571   {
2572     fprintf(stderr,"sort_input_database(1): sscanf failed to parse\n");
2573   }
2574 
2575   data_ptr=0l;
2576   my_data=NULL;
2577   return_records=0l;
2578   pointer = fopen(ptr_filename,"r+");
2579   /* check if file is there */
2580   if(pointer == NULL)
2581   {
2582     pointer = fopen(ptr_filename,"a+");
2583   }
2584 
2585   if(pointer!=NULL)
2586   {
2587     my_data = fopen(filename,"a+");
2588     if(my_data!=NULL)
2589     {
2590 
2591       // Next statement needed for Solaris 7, as the fopen above
2592       // doesn't put the filepointer at the end of the file.
2593       (void) fseek(my_data,0l,SEEK_END);  //KD6ZWR
2594 
2595       new_data_ptr = data_ptr = ftell(my_data);
2596       (void)fwrite(fill,(size_t)size,1,my_data);
2597       records = (data_ptr/size);
2598       return_records=records+1;
2599       if(records<1)
2600       {
2601         /* no data yet */
2602         (void)fseek(pointer,0l,SEEK_SET);
2603         (void)fwrite(&data_ptr,(size_t)ptr_size,1,pointer);
2604       }
2605       else
2606       {
2607         /* more than one record*/
2608         (void)fseek(pointer,(ptr_size*records),SEEK_SET);
2609         (void)fwrite(&data_ptr,(size_t)ptr_size,1,pointer);
2610         record_start=0l;
2611         record_end=records;
2612         record_mid=(record_end-record_start)/2;
2613         done=0;
2614         while(!done)
2615         {
2616           /*fprintf(stderr,"Records Start %ld, Mid %ld, End %ld\n",record_start,record_mid,record_end);*/
2617           /* get data for record start */
2618           (void)fseek(pointer,(ptr_size*record_start),SEEK_SET);
2619           if (fread(&data_ptr,(size_t)ptr_size,1,pointer) != 0)
2620           {
2621             // we are explicitly ignoring the return value,
2622             // but are doing it this way to silence GCC warnings
2623           }
2624           (void)fseek(my_data,data_ptr,SEEK_SET);
2625           if(fread(file_data,(size_t)size,1,my_data)==1)
2626           {
2627             /* COMPARE HERE */
2628             if (1 != sscanf(file_data,"%1999s",tempfile))
2629             {
2630               fprintf(stderr,"sort_input_database(2): sscanf failed to parse\n");
2631             }
2632             if(strcasecmp(tempfill,tempfile)<0)
2633             {
2634               /* file name comes before */
2635               /*fprintf(stderr,"END - Before start\n");*/
2636               done=1;
2637               /* now place pointer before start*/
2638               sort_reset_pointers(pointer,new_data_ptr,records,0,record_start);
2639             }
2640             else
2641             {
2642               /* get data for record end */
2643               (void)fseek(pointer,(ptr_size*record_end),SEEK_SET);
2644               if (fread(&data_ptr,(size_t)ptr_size,1,pointer) != 0)
2645               {
2646                 // we are explicitly ignoring the return value,
2647                 // but are doing it this way to silence GCC
2648                 // warnings
2649               }
2650 
2651               (void)fseek(my_data,data_ptr,SEEK_SET);
2652               if(fread(file_data,(size_t)size,1,my_data)==1)
2653               {
2654                 /* COMPARE HERE */
2655                 if (1 != sscanf(file_data,"%1999s",tempfile))
2656                 {
2657                   fprintf(stderr,"sort_input_database(3): sscanf failed to parse\n");
2658                 }
2659                 if(strcasecmp(tempfill,tempfile)>0)
2660                 {
2661                   /* file name comes after */
2662                   /*fprintf(stderr,"END - After end\n");*/
2663                   done=1;
2664                   /* now place pointer after end */
2665                 }
2666                 else
2667                 {
2668                   if((record_mid==record_start) || (record_mid==record_end))
2669                   {
2670                     /* no mid for compare check to see if in the middle */
2671                     /*fprintf(stderr,"END - NO Middle\n");*/
2672                     done=1;
2673                     /* now place pointer before start*/
2674                     if (record_mid==record_start)
2675                     {
2676                       sort_reset_pointers(pointer,new_data_ptr,records,0,record_mid+1);
2677                     }
2678                     else
2679                     {
2680                       sort_reset_pointers(pointer,new_data_ptr,records,0,record_mid-1);
2681                     }
2682                   }
2683                   else
2684                   {
2685                     /* get data for record mid */
2686                     (void)fseek(pointer,(ptr_size*record_mid),SEEK_SET);
2687                     if (fread(&data_ptr,(size_t)ptr_size,1,pointer) != 0)
2688                     {
2689                       // we are explicitly
2690                       // ignoring the return
2691                       // value, but are doing it
2692                       // this way to silence GCC
2693                       // warnings
2694                     }
2695 
2696                     (void)fseek(my_data,data_ptr,SEEK_SET);
2697                     if(fread(file_data,(size_t)size,1,my_data)==1)
2698                     {
2699                       /* COMPARE HERE */
2700                       if (1 != sscanf(file_data,"%1999s",tempfile))
2701                       {
2702                         fprintf(stderr,"sort_input_database(4): sscanf failed to parse\n");
2703                       }
2704                       if(strcasecmp(tempfill,tempfile)<0)
2705                       {
2706                         /* checking comes before */
2707                         /*record_start=0l;*/
2708                         record_end=record_mid;
2709                         record_mid=record_start+(record_end-record_start)/2;
2710                         /*fprintf(stderr,"TOP %ld, mid %ld\n",record_mid,record_end);*/
2711                       }
2712                       else
2713                       {
2714                         /* checking comes after*/
2715                         record_start=record_mid;
2716                         /*record_end=end*/
2717                         record_mid=record_start+(record_end-record_start)/2;
2718                         /*fprintf(stderr,"BOTTOM start %ld, mid %ld\n",record_start,record_mid);*/
2719                       }
2720                     }
2721                   }
2722                 }
2723               }
2724             }
2725           }
2726         }
2727       }
2728     }
2729     else
2730     {
2731       fprintf(stderr,"Could not open file %s\n",filename);
2732     }
2733   }
2734   else
2735   {
2736     fprintf(stderr,"Could not open file %s\n",filename);
2737   }
2738 
2739   if(my_data!=NULL)
2740   {
2741     (void)fclose(my_data);
2742   }
2743 
2744   if(pointer!=NULL)
2745   {
2746     (void)fclose(pointer);
2747   }
2748 
2749   return(return_records);
2750 }
2751 
2752 
2753 
2754 
2755 
2756 /******************** sort end **********************/
2757 
2758 
2759 
2760 // is_altnet()
2761 //
2762 // Returns true if station fits the current altnet description.
2763 //
is_altnet(DataRow * p_station)2764 int is_altnet(DataRow *p_station)
2765 {
2766   char temp_altnet_call[20+1];
2767   char temp2[20+1];
2768   char *net_ptr;
2769   int  altnet_match;
2770   int  result;
2771 
2772 
2773   // Snag a possible altnet call out of the record for later use
2774   if (p_station->node_path_ptr != NULL)
2775   {
2776     substr(temp_altnet_call, p_station->node_path_ptr, MAX_CALLSIGN);
2777   }
2778   else
2779   {
2780     temp_altnet_call[0] = '\0';
2781   }
2782 
2783   // Save for later
2784   xastir_snprintf(temp2,
2785                   sizeof(temp2),
2786                   "%s",
2787                   temp_altnet_call);
2788 
2789   if ((net_ptr = strchr(temp_altnet_call, ',')))
2790   {
2791     *net_ptr = '\0';  // Chop the string at the first ',' character
2792   }
2793 
2794   for (altnet_match = (int)strlen(altnet_call); altnet && altnet_call[altnet_match-1] == '*'; altnet_match--);
2795 
2796   result = (!strncmp(temp_altnet_call, altnet_call, (size_t)altnet_match)
2797             || !strcmp(temp_altnet_call, "local")
2798             || !strncmp(temp_altnet_call, "SPC", 3)
2799             || !strcmp(temp_altnet_call, "SPECL")
2800             //                 || is_my_call(p_station->call_sign,1)); // Check SSID as well
2801             || ( is_my_station(p_station) ) ) ;  // It's my callsign/SSID
2802 
2803   if ( (debug_level & 1) && result )
2804   {
2805     fprintf(stderr,"%s  %-9s  %s\n", altnet_call, temp_altnet_call, temp2 );
2806   }
2807 
2808   return(result);
2809 }
2810 
2811 
2812 
2813 
2814 
2815 // Function which checks various filtering criteria (the Select struct)
2816 // and decides whether this station/object should be displayed.
2817 //
2818 // 0 = don't draw this station/object
2819 // 1 = ok to draw this station/object
2820 //
ok_to_draw_station(DataRow * p_station)2821 int ok_to_draw_station(DataRow *p_station)
2822 {
2823   time_t secs_now = sec_now();
2824   // Check overall flag
2825   if (Select_.none)
2826   {
2827     return 0;
2828   }
2829 
2830   // Check tactical flag
2831   if (Select_.tactical
2832       && (p_station->tactical_call_sign == NULL
2833           || p_station->tactical_call_sign[0] == '\0'))
2834   {
2835     return 0;
2836   }
2837 
2838   // Check for my station and my objects/items
2839 //    if (strcmp(p_station->call_sign, my_callsign) == 0
2840 //        || (is_my_call(p_station->origin, 1)        // If station is owned by me (including SSID)
2841 //            && (   p_station->flag & ST_OBJECT      // And it's an object
2842 //                || p_station->flag & ST_ITEM) ) ) { // or an item
2843 //    if ( is_my_station(p_station) || is_my_object_item(p_station) ) {
2844   if ( is_my_station(p_station) )
2845   {
2846     if (!Select_.mine)
2847     {
2848       return 0;
2849     }
2850   }
2851   // Not mine, so check these next things
2852   else
2853   {
2854     // Check whether we wish to display TNC heard stations
2855     if (p_station->flag & ST_VIATNC)
2856     {
2857       if (!Select_.tnc)
2858       {
2859         return 0;
2860       }
2861 
2862       // Check whether we wish to display directly heard stations
2863       if (p_station->flag & ST_DIRECT
2864           && secs_now < (p_station->direct_heard + st_direct_timeout))
2865       {
2866         if (!Select_.direct)
2867         {
2868           return 0;
2869         }
2870       }
2871       // Check whether we wish to display stations heard via a digi
2872       else
2873       {
2874         if (!Select_.via_digi)
2875         {
2876           return 0;
2877         }
2878       }
2879     }
2880     // Check whether we wish to display net stations
2881     else
2882     {
2883       if (!Select_.net)
2884       {
2885         return 0;
2886       }
2887     }
2888 
2889     //N7IPB - check for aircraft and if so check aircraft timeout
2890     // return 0 if timedout - else continue
2891     if ((aircraft_sec_clear != 0)
2892         && ((p_station->aprs_symbol.aprs_symbol == '^')
2893             || (p_station->aprs_symbol.aprs_symbol == '\'')
2894             || (p_station->aprs_symbol.aprs_symbol == 'X')))
2895     {
2896       if ((p_station->sec_heard + (aircraft_sec_clear * 5)) < secs_now)
2897       {
2898         return 0;
2899       }
2900     }
2901     // Check if we want to display data past the clear time
2902     if (!Select_.old_data)
2903     {
2904       if ((p_station->sec_heard + sec_clear) < secs_now)
2905       {
2906         return 0;
2907       }
2908     }
2909   }
2910 
2911 
2912   // Check whether object or item
2913   if (p_station->flag & (ST_OBJECT | ST_ITEM))
2914   {
2915     // Check whether we wish to display objects/items
2916     if (!Select_.objects ||
2917         (!Select_.weather_objects && !Select_.gauge_objects && !Select_.aircraft_objects && !Select_.vessel_objects && !Select_.other_objects))
2918     {
2919       return 0;
2920     }
2921 
2922     // Check if WX info and we wish to see it
2923     if (p_station->weather_data)
2924     {
2925       return Select_.weather_objects;
2926     }
2927     // Check if water gauge and we wish to see it
2928     else if (p_station->aprs_symbol.aprs_type == '/'
2929              && p_station->aprs_symbol.aprs_symbol == 'w')
2930     {
2931       return Select_.gauge_objects;
2932     }
2933     // Check if aircraft and we wish to see it
2934     else if (p_station->aprs_symbol.aprs_type == '/'
2935              && ((p_station->aprs_symbol.aprs_symbol == '^')
2936                  || (p_station->aprs_symbol.aprs_symbol == '\'')
2937                  || (p_station->aprs_symbol.aprs_symbol == 'X')) )
2938     {
2939       return Select_.aircraft_objects;
2940     }
2941     // Check if vessel and we wish to see it
2942     else if (p_station->aprs_symbol.aprs_type == '/'
2943              && ((p_station->aprs_symbol.aprs_symbol == 's')
2944                  || (p_station->aprs_symbol.aprs_symbol == 'Y')) )
2945     {
2946       return Select_.vessel_objects;
2947     }
2948     // Check if we wish to see other objects/items
2949     else
2950     {
2951       return Select_.other_objects;
2952     }
2953   }
2954   else      // Not an object or item
2955   {
2956     if (!Select_.stations ||
2957         (!Select_.fixed_stations && !Select_.moving_stations && !Select_.weather_stations))
2958     {
2959       return 0;
2960     }
2961 
2962     // Check if we wish to see weather stations
2963     if (p_station->weather_data)
2964     {
2965       // We have weather data
2966 
2967       // Check whether it is a citizen's weather station.
2968       // Note that a "CW" prefix is Uruguay and "DW" prefix is
2969       // Phillipines, so let's be careful how we filter here.
2970       // All Cititzen's weather stations seen to date have had
2971       // CW or DW and then four digits.
2972       if ( (strncasecmp(p_station->call_sign,"CW",2) == 0)
2973            || (strncasecmp(p_station->call_sign,"DW",2) == 0) )
2974       {
2975         if ( is_num_chr(p_station->call_sign[2])
2976              && is_num_chr(p_station->call_sign[3])
2977              && is_num_chr(p_station->call_sign[4])
2978              && is_num_chr(p_station->call_sign[5]) )
2979         {
2980           return(Select_.weather_stations && Select_.CWOP_wx_stations);
2981         }
2982         else
2983         {
2984           return Select_.weather_stations;
2985         }
2986       }
2987       else
2988       {
2989         return Select_.weather_stations;
2990       }
2991     }
2992     // Check if we wish to see other stations
2993     else
2994     {
2995       if (p_station->flag & ST_MOVING)
2996       {
2997         return Select_.moving_stations;
2998       }
2999       else
3000       {
3001         return Select_.fixed_stations;
3002       }
3003     }
3004   }
3005 }
3006 
3007 
3008 
3009 
3010 
3011 // display_station
3012 //
3013 // single is 1 if the calling station wants to update only a
3014 // single station.  If updating multiple stations in a row, then
3015 // "single" will be passed to us as a zero.
3016 //
3017 // If current course/speed/altitude are absent, we check the last
3018 // track point to try to snag those numbers.
3019 //
display_station(Widget w,DataRow * p_station,int single)3020 void display_station(Widget w, DataRow *p_station, int single)
3021 {
3022   char temp_altitude[20];
3023   char temp_course[20];
3024   char temp_speed[20];
3025   char dr_speed[20];
3026   char temp_call[MAX_TACTICAL_CALL+1];
3027   char wx_tm[50];
3028   char temp_wx_temp[30];
3029   char temp_wx_wind[40];
3030   char temp_my_distance[20];
3031   char temp_my_course[20];
3032   char temp1_my_course[20];
3033   char temp2_my_gauge_data[50];
3034   time_t temp_sec_heard;
3035   int temp_show_last_heard;
3036   long l_lon, l_lat;
3037   char orient;
3038   float value;
3039   char tmp[7+1];
3040   int speed_ok = 0;
3041   int course_ok = 0;
3042   int wx_ghost = 0;
3043   Pixmap drawing_target;
3044   WeatherRow *weather = p_station->weather_data;
3045   time_t secs_now = sec_now();
3046   int ambiguity_flag;
3047   long ambiguity_coord_lon, ambiguity_coord_lat;
3048   size_t temp_len;
3049 
3050 
3051   if (debug_level & 128)
3052   {
3053     fprintf(stderr,"Display station (%s) called for Single=%d.\n", p_station->call_sign, single);
3054   }
3055 
3056   if (!ok_to_draw_station(p_station))
3057   {
3058     return;
3059   }
3060 
3061   // Set up call string for display
3062   if (Display_.callsign)
3063   {
3064     if (p_station->tactical_call_sign
3065         && p_station->tactical_call_sign[0] != '\0')
3066     {
3067       // Display tactical callsign instead if it has one
3068       // defined.
3069       xastir_snprintf(temp_call,
3070                       sizeof(temp_call),
3071                       "%s",
3072                       p_station->tactical_call_sign);
3073     }
3074     else
3075     {
3076       // Display normal callsign.
3077       xastir_snprintf(temp_call,
3078                       sizeof(temp_call),
3079                       "%s",
3080                       p_station->call_sign);
3081     }
3082   }
3083   else
3084   {
3085     temp_call[0] = '\0';
3086   }
3087 
3088   // Set up altitude string for display
3089   temp_altitude[0] = '\0';
3090 
3091   if (Display_.altitude)
3092   {
3093     // Check whether we have altitude in the current data
3094     if (strlen(p_station->altitude)>0)
3095     {
3096       // Found it in the current data
3097       xastir_snprintf(temp_altitude, sizeof(temp_altitude), "%.0f%s",
3098                       atof(p_station->altitude) * cvt_m2len, un_alt);
3099     }
3100 
3101     // Else check whether the previous position had altitude.
3102     // Note that newest_trackpoint if it exists should be the
3103     // same as the current data, so we have to go back one
3104     // further trackpoint.
3105     else if ( (p_station->newest_trackpoint != NULL)
3106               && (p_station->newest_trackpoint->prev != NULL) )
3107     {
3108       if ( p_station->newest_trackpoint->prev->altitude > -99999l)
3109       {
3110         // Found it in the tracklog
3111         xastir_snprintf(temp_altitude, sizeof(temp_altitude), "%.0f%s",
3112                         (float)(p_station->newest_trackpoint->prev->altitude * cvt_dm2len),
3113                         un_alt);
3114 
3115 //                fprintf(stderr,"Trail data              with altitude: %s : %s\n",
3116 //                    p_station->call_sign,
3117 //                    temp_altitude);
3118       }
3119       else
3120       {
3121         //fprintf(stderr,"Trail data w/o altitude                %s\n",
3122         //    p_station->call_sign);
3123       }
3124     }
3125   }
3126 
3127   // Set up speed and course strings for display
3128   temp_speed[0] = '\0';
3129   dr_speed[0] = '\0';
3130   temp_course[0] = '\0';
3131 
3132   if (Display_.speed || Display_.dr_data)
3133   {
3134     // don't display 'fixed' stations speed and course.
3135     // Check whether we have speed in the current data and it's
3136     // >= 0.
3137     if ( (strlen(p_station->speed)>0) && (atof(p_station->speed) >= 0) )
3138     {
3139       speed_ok++;
3140       xastir_snprintf(tmp,
3141                       sizeof(tmp),
3142                       "%s",
3143                       un_spd);
3144       if (Display_.speed_short)
3145       {
3146         tmp[0] = '\0';  // without unit
3147       }
3148 
3149       xastir_snprintf(temp_speed, sizeof(temp_speed), "%.0f%s",
3150                       atof(p_station->speed)*cvt_kn2len,tmp);
3151     }
3152     // Else check whether the previous position had speed
3153     // Note that newest_trackpoint if it exists should be the
3154     // same as the current data, so we have to go back one
3155     // further trackpoint.
3156     else if ( (p_station->newest_trackpoint != NULL)
3157               && (p_station->newest_trackpoint->prev != NULL) )
3158     {
3159 
3160       xastir_snprintf(tmp,
3161                       sizeof(tmp),
3162                       "%s",
3163                       un_spd);
3164 
3165       if (Display_.speed_short)
3166       {
3167         tmp[0] = '\0';  // without unit
3168       }
3169 
3170       if ( p_station->newest_trackpoint->prev->speed > 0)
3171       {
3172         speed_ok++;
3173 
3174         xastir_snprintf(temp_speed, sizeof(temp_speed), "%.0f%s",
3175                         p_station->newest_trackpoint->prev->speed * cvt_hm2len,
3176                         tmp);
3177       }
3178     }
3179   }
3180 
3181   if (Display_.course || Display_.dr_data)
3182   {
3183     // Check whether we have course in the current data
3184     if ( (strlen(p_station->course)>0) && (atof(p_station->course) > 0) )
3185     {
3186       course_ok++;
3187       xastir_snprintf(temp_course, sizeof(temp_course), "%.0f\xB0",
3188                       atof(p_station->course));
3189     }
3190     // Else check whether the previous position had a course
3191     // Note that newest_trackpoint if it exists should be the
3192     // same as the current data, so we have to go back one
3193     // further trackpoint.
3194     else if ( (p_station->newest_trackpoint != NULL)
3195               && (p_station->newest_trackpoint->prev != NULL) )
3196     {
3197       if( p_station->newest_trackpoint->prev->course > 0 )
3198       {
3199         course_ok++;
3200         xastir_snprintf(temp_course, sizeof(temp_course), "%.0f\xB0",
3201                         (float)p_station->newest_trackpoint->prev->course);
3202       }
3203     }
3204   }
3205 
3206   // Save the speed into the dr string for later
3207   xastir_snprintf(dr_speed,
3208                   sizeof(dr_speed),
3209                   "%s",
3210                   temp_speed);
3211 
3212   if (!speed_ok  || !Display_.speed)
3213   {
3214     temp_speed[0] = '\0';
3215   }
3216 
3217   if (!course_ok || !Display_.course)
3218   {
3219     temp_course[0] = '\0';
3220   }
3221 
3222   // Set up distance and bearing strings for display
3223   temp_my_distance[0] = '\0';
3224   temp_my_course[0] = '\0';
3225 
3226   if (Display_.dist_bearing && strcmp(p_station->call_sign,my_callsign) != 0)
3227   {
3228     l_lat = convert_lat_s2l(my_lat);
3229     l_lon = convert_lon_s2l(my_long);
3230 
3231     // Get distance in nautical miles, convert to current measurement standard
3232     value = cvt_kn2len * calc_distance_course(l_lat,l_lon,
3233             p_station->coord_lat,p_station->coord_lon,temp1_my_course,sizeof(temp1_my_course));
3234 
3235     if (value < 5.0)
3236     {
3237       sprintf(temp_my_distance,"%0.1f%s",value,un_dst);
3238     }
3239     else
3240     {
3241       sprintf(temp_my_distance,"%0.0f%s",value,un_dst);
3242     }
3243 
3244     xastir_snprintf(temp_my_course, sizeof(temp_my_course), "%.0f\xB0",
3245                     atof(temp1_my_course));
3246   }
3247 
3248   // Set up weather strings for display
3249   temp_wx_temp[0] = '\0';
3250   temp_wx_wind[0] = '\0';
3251 
3252   if (weather != NULL)
3253   {
3254     // wx_ghost = 1 if the weather data is too old to display
3255     wx_ghost = (int)(((sec_old + weather->wx_sec_time)) < secs_now);
3256   }
3257 
3258   if (Display_.weather
3259       && Display_.weather_text
3260       && weather != NULL      // We have weather data
3261       && !wx_ghost)           // Weather is current, display it
3262   {
3263 
3264     if (strlen(weather->wx_temp) > 0)
3265     {
3266       xastir_snprintf(tmp,
3267                       sizeof(tmp),
3268                       "T:");
3269       if (Display_.temperature_only)
3270       {
3271         tmp[0] = '\0';
3272       }
3273 
3274       if (english_units)
3275         xastir_snprintf(temp_wx_temp, sizeof(temp_wx_temp), "%s%.0f\xB0%s",
3276                         tmp, atof(weather->wx_temp),"F ");
3277       else
3278         xastir_snprintf(temp_wx_temp, sizeof(temp_wx_temp), "%s%.0f\xB0%s",
3279                         tmp,((atof(weather->wx_temp)-32.0)*5.0)/9.0,"C ");
3280     }
3281 
3282     if (!Display_.temperature_only)
3283     {
3284       if (strlen(weather->wx_hum) > 0)
3285       {
3286         xastir_snprintf(wx_tm, sizeof(wx_tm), "H:%.0f%%", atof(weather->wx_hum));
3287         strncat(temp_wx_temp,
3288                 wx_tm,
3289                 sizeof(temp_wx_temp) - 1 - strlen(temp_wx_temp));
3290       }
3291 
3292       if (strlen(weather->wx_speed) > 0)
3293       {
3294         xastir_snprintf(temp_wx_wind, sizeof(temp_wx_wind), "S:%.0f%s ",
3295                         atof(weather->wx_speed)*cvt_mi2len,un_spd);
3296       }
3297 
3298       if (strlen(weather->wx_gust) > 0)
3299       {
3300         xastir_snprintf(wx_tm, sizeof(wx_tm), "G:%.0f%s ",
3301                         atof(weather->wx_gust)*cvt_mi2len,un_spd);
3302         strncat(temp_wx_wind,
3303                 wx_tm,
3304                 sizeof(temp_wx_wind) - 1 - strlen(temp_wx_wind));
3305       }
3306 
3307       if (strlen(weather->wx_course) > 0)
3308       {
3309         xastir_snprintf(wx_tm, sizeof(wx_tm), "C:%.0f\xB0", atof(weather->wx_course));
3310         strncat(temp_wx_wind,
3311                 wx_tm,
3312                 sizeof(temp_wx_wind) - 1 - strlen(temp_wx_wind));
3313       }
3314 
3315       temp_len = strlen(temp_wx_wind);
3316       if ((temp_len > 0) && (temp_wx_wind[temp_len-1] == ' '))
3317       {
3318         temp_wx_wind[temp_len-1] = '\0';  // delete blank at EOL
3319       }
3320     }
3321 
3322     temp_len = strlen(temp_wx_temp);
3323     if ((temp_len > 0) && (temp_wx_temp[strlen(temp_wx_temp)-1] == ' '))
3324     {
3325       temp_wx_temp[temp_len-1] = '\0';  // delete blank at EOL
3326     }
3327   }
3328 
3329 
3330   (void)remove_trailing_asterisk(p_station->call_sign);  // DK7IN: is this needed here?
3331 
3332   if (Display_.symbol_rotate)
3333   {
3334     orient = symbol_orient(p_station->course);  // rotate symbol
3335   }
3336   else
3337   {
3338     orient = ' ';
3339   }
3340 
3341   // Prevents my own call from "ghosting"?
3342   //    temp_sec_heard = (strcmp(p_station->call_sign, my_callsign) == 0) ? secs_now: p_station->sec_heard;
3343   temp_sec_heard = (is_my_station(p_station)) ? secs_now : p_station->sec_heard;
3344 
3345   // Check whether it's a locally-owned object/item
3346 //    if ( (is_my_call(p_station->origin,1))          // If station is owned by me (including SSID)
3347 //            && ( (p_station->flag & ST_OBJECT)      // And it's an object
3348 //              || (p_station->flag & ST_ITEM) ) ) {  // or an item
3349 //    if ( is_my_object_item(p_station) ) {
3350 //        temp_sec_heard = secs_now; // We don't want our own objects/items to "ghost"
3351 //    }
3352 
3353   // Show last heard times only for others stations and their
3354   // objects/items.
3355   //    temp_show_last_heard = (strcmp(p_station->call_sign, my_callsign) == 0) ? 0 : Display_.last_heard;
3356   temp_show_last_heard = (is_my_station(p_station)) ? 0 : Display_.last_heard;
3357 
3358 
3359 
3360   //------------------------------------------------------------------------------------------
3361 
3362   // If we're only planning on updating a single station at this time, we go
3363   // through the drawing calls twice, the first time drawing directly onto
3364   // the screen.
3365   if (!pending_ID_message && single)
3366   {
3367     drawing_target = XtWindow(da);
3368   }
3369   else
3370   {
3371     drawing_target = pixmap_final;
3372   }
3373 
3374   //_do_the_drawing:
3375 
3376   // Check whether it's a locally-owned object/item
3377 //    if ( (is_my_call(p_station->origin,1))                  // If station is owned by me (including SSID)
3378 //            && ( (p_station->flag & ST_OBJECT)       // And it's an object
3379 //              || (p_station->flag & ST_ITEM  ) ) ) { // or an item
3380 //    if ( is_my_object_item(p_station) ) {
3381 //        temp_sec_heard = secs_now; // We don't want our own objects/items to "ghost"
3382   // This isn't quite right since if it's a moving object, passing an incorrect
3383   // sec_heard should give the wrong results.
3384 //    }
3385 
3386   ambiguity_flag = 0; // Default
3387 
3388   if (Display_.ambiguity && p_station->pos_amb)
3389   {
3390     ambiguity_flag = 1;
3391     draw_ambiguity(p_station->coord_lon,
3392                    p_station->coord_lat,
3393                    p_station->pos_amb,
3394                    &ambiguity_coord_lon, // New longitude may get passed back to us
3395                    &ambiguity_coord_lat, // New latitude may get passed back to us
3396                    temp_sec_heard,
3397                    drawing_target);
3398   }
3399 
3400   // Check for DF'ing data, draw DF circles if present and enabled
3401   if (Display_.df_data && strlen(p_station->signal_gain) == 7)    // There's an SHGD defined
3402   {
3403     //fprintf(stderr,"SHGD:%s\n",p_station->signal_gain);
3404     draw_DF_circle( (ambiguity_flag) ? ambiguity_coord_lon : p_station->coord_lon,
3405                     (ambiguity_flag) ? ambiguity_coord_lat : p_station->coord_lat,
3406                     p_station->signal_gain,
3407                     temp_sec_heard,
3408                     drawing_target);
3409   }
3410 
3411   // Check for DF'ing beam heading/NRQ data
3412   if (Display_.df_data && (strlen(p_station->bearing) == 3) && (strlen(p_station->NRQ) == 3))
3413   {
3414     //fprintf(stderr,"Bearing: %s\n",p_station->signal_gain,NRQ);
3415     if (p_station->df_color == -1)
3416     {
3417       p_station->df_color = rand() % MAX_TRAIL_COLORS;
3418     }
3419 
3420     draw_bearing( (ambiguity_flag) ? ambiguity_coord_lon : p_station->coord_lon,
3421                   (ambiguity_flag) ? ambiguity_coord_lat : p_station->coord_lat,
3422                   p_station->course,
3423                   p_station->bearing,
3424                   p_station->NRQ,
3425                   trail_colors[p_station->df_color],
3426                   Display_.df_beamwidth_data, Display_.df_bearing_data,
3427                   temp_sec_heard,
3428                   drawing_target);
3429   }
3430 
3431   // Check whether to draw dead-reckoning data by KJ5O
3432   if (Display_.dr_data
3433       && ( (p_station->flag & ST_MOVING)
3434            //        && (p_station->newest_trackpoint!=0
3435            && course_ok
3436            && speed_ok
3437            && scale_y < 8000
3438            && atof(dr_speed) > 0) )
3439   {
3440 
3441     // Does it make sense to try to do dead-reckoning on an
3442     // object that has position ambiguity enabled?  I don't
3443     // think so!
3444     //
3445     if ( ! ambiguity_flag && ( (secs_now-temp_sec_heard) < dead_reckoning_timeout) )
3446     {
3447 
3448       draw_deadreckoning_features(p_station,
3449                                   drawing_target,
3450                                   w);
3451     }
3452   }
3453 
3454   if (p_station->aprs_symbol.area_object.type != AREA_NONE)
3455   {
3456     draw_area( (ambiguity_flag) ? ambiguity_coord_lon : p_station->coord_lon,
3457                (ambiguity_flag) ? ambiguity_coord_lat : p_station->coord_lat,
3458                p_station->aprs_symbol.area_object.type,
3459                p_station->aprs_symbol.area_object.color,
3460                p_station->aprs_symbol.area_object.sqrt_lat_off,
3461                p_station->aprs_symbol.area_object.sqrt_lon_off,
3462                p_station->aprs_symbol.area_object.corridor_width,
3463                temp_sec_heard,
3464                drawing_target);
3465   }
3466 
3467 
3468   // Draw additional stuff if this is the tracked station
3469   if (is_tracked_station(p_station->call_sign))
3470   {
3471     //WE7U
3472     draw_pod_circle( (ambiguity_flag) ? ambiguity_coord_lon : p_station->coord_lon,
3473                      (ambiguity_flag) ? ambiguity_coord_lat : p_station->coord_lat,
3474                      0.0020 * scale_y,
3475                      colors[0x0e],   // Yellow
3476                      drawing_target,
3477                      temp_sec_heard);
3478     draw_pod_circle( (ambiguity_flag) ? ambiguity_coord_lon : p_station->coord_lon,
3479                      (ambiguity_flag) ? ambiguity_coord_lat : p_station->coord_lat,
3480                      0.0023 * scale_y,
3481                      colors[0x44],   // Red
3482                      drawing_target,
3483                      temp_sec_heard);
3484     draw_pod_circle( (ambiguity_flag) ? ambiguity_coord_lon : p_station->coord_lon,
3485                      (ambiguity_flag) ? ambiguity_coord_lat : p_station->coord_lat,
3486                      0.0026 * scale_y,
3487                      colors[0x61],   // Blue
3488                      drawing_target,
3489                      temp_sec_heard);
3490   }
3491 
3492 
3493   // Draw additional stuff if this is a storm and the weather data
3494   // is not too old to display.
3495   if ( (weather != NULL) && weather->wx_storm && !wx_ghost )
3496   {
3497     char temp[4];
3498 
3499 
3500     //fprintf(stderr,"Plotting a storm symbol:%s:%s:%s:\n",
3501     //    weather->wx_hurricane_radius,
3502     //    weather->wx_trop_storm_radius,
3503     //    weather->wx_whole_gale_radius);
3504 
3505     // Still need to draw the circles in different colors for the
3506     // different ranges.  Might be nice to tint it as well.
3507 
3508     xastir_snprintf(temp,
3509                     sizeof(temp),
3510                     "%s",
3511                     weather->wx_hurricane_radius);
3512 
3513     if ( (temp[0] != '\0') && (strncmp(temp,"000",3) != 0) )
3514     {
3515 
3516       draw_pod_circle( (ambiguity_flag) ? ambiguity_coord_lon : p_station->coord_lon,
3517                        (ambiguity_flag) ? ambiguity_coord_lat : p_station->coord_lat,
3518                        atof(temp) * 1.15078, // nautical miles to miles
3519                        colors[0x44],   // Red
3520                        drawing_target,
3521                        temp_sec_heard);
3522     }
3523 
3524     xastir_snprintf(temp,
3525                     sizeof(temp),
3526                     "%s",
3527                     weather->wx_trop_storm_radius);
3528 
3529     if ( (temp[0] != '\0') && (strncmp(temp,"000",3) != 0) )
3530     {
3531       draw_pod_circle( (ambiguity_flag) ? ambiguity_coord_lon : p_station->coord_lon,
3532                        (ambiguity_flag) ? ambiguity_coord_lat : p_station->coord_lat,
3533                        atof(temp) * 1.15078, // nautical miles to miles
3534                        colors[0x0e],   // Yellow
3535                        drawing_target,
3536                        temp_sec_heard);
3537     }
3538 
3539     xastir_snprintf(temp,
3540                     sizeof(temp),
3541                     "%s",
3542                     weather->wx_whole_gale_radius);
3543 
3544     if ( (temp[0] != '\0') && (strncmp(temp,"000",3) != 0) )
3545     {
3546       draw_pod_circle( (ambiguity_flag) ? ambiguity_coord_lon : p_station->coord_lon,
3547                        (ambiguity_flag) ? ambiguity_coord_lat : p_station->coord_lat,
3548                        atof(temp) * 1.15078, // nautical miles to miles
3549                        colors[0x0a],   // Green
3550                        drawing_target,
3551                        temp_sec_heard);
3552     }
3553   }
3554 
3555 
3556   // Draw wind barb if selected and we have wind, but not a severe
3557   // storm (wind barbs just confuse the matter).
3558   if (Display_.weather && Display_.wind_barb
3559       && weather != NULL && atoi(weather->wx_speed) >= 5
3560       && !weather->wx_storm
3561       && !wx_ghost )
3562   {
3563     draw_wind_barb( (ambiguity_flag) ? ambiguity_coord_lon : p_station->coord_lon,
3564                     (ambiguity_flag) ? ambiguity_coord_lat : p_station->coord_lat,
3565                     weather->wx_speed,
3566                     weather->wx_course,
3567                     temp_sec_heard,
3568                     drawing_target);
3569   }
3570 
3571 
3572   // WE7U
3573   //
3574   // Draw truncation/rounding rectangles plus error ellipses.
3575   //
3576   //
3577   // We need to keep track of ellipse northing/easting radii plus
3578   // rectangle northing/easting offsets.  If both sets are present
3579   // we'll need to draw the summation of both geometric figures.
3580   // Check that the math works at/near the poles.  We may need to keep
3581   // track of truncation/rounding rectangles separately if some
3582   // devices or software use one method, some the other.
3583   //
3584   if (!ambiguity_flag)
3585   {
3586 
3587     // Check whether we're at a close enough zoom level to have
3588     // the ellipses/rectangles be visible, else skip drawing for
3589     // efficiency.
3590     //
3591     //fprintf(stderr,"scale_y: %ld\t", scale_y);
3592     if (scale_y < 17)   // 60' figures are good out to about zoom 16
3593     {
3594 
3595       // Here we may have to check what type of device is being used (if
3596       // possible to determine) to decide whether to draw a truncation/
3597       // rounding rectangles or GPS error ellipses.  Truncation rectangles
3598       // have the symbol at one corner, rounding have it in the middle.
3599       // Based on the precision inherent in the packet we wish to draw a
3600       // GPS error ellipse instead, the decision point is when the packet
3601       // precision is adequate to show ~6 meters.
3602       //
3603       // OpenTracker APRS:  Truncation, rectangle
3604       // OpenTracker Base91:Truncation, ellipse
3605       // OpenTracker OpenTrac: Truncation, ellipse
3606       // TinyTrak APRS:     Truncation, rectangle
3607       // TinyTrak NMEA:     Truncation, ellipse/rectangle based on precision
3608       // TinyTrak Mic-E:    Truncation, rectangle
3609       // GPGGA:             Truncation, ellipse/rectangle based on precision/HDOP/Augmentation
3610       // GPRMC:             Truncation, ellipse/rectangle based on precision
3611       // GPGLL:             Truncation, ellipse/rectangle based on precision
3612       // Xastir APRS:       Truncation, rectangle
3613       // Xastir Base91:     Truncation, ellipse
3614       // UI-View APRS:      ??, rectangle
3615       // UI-View Base91:    ??, ellipse
3616       // APRS+SA APRS:      ??, rectangle
3617       // APRS+SA Base91:    ??, ellipse
3618       // PocketAPRS:        ??, rectangle
3619       // SmartAPRS:         ??, rectangle
3620       // HamHUD:            Truncation, ??
3621       // HamHUD GPRMC:      Truncation, ellipse/rectangle based on precision
3622       // Linksys NSLU2:     ??, rectangle
3623       // AGW Tracker:       ??, ??
3624       // APRSPoint:         ??, rectangle
3625       // APRSce:            ??, rectangle
3626       // APRSdos APRS:      ??, rectangle
3627       // APRSdos Base91:    ??, ellipse
3628       // BalloonTrack:      ??, ??
3629       // DMapper:           ??, ??
3630       // JavAPRS APRS:      ??, rectangle
3631       // JavAPRS Base91:    ??, ellipse
3632       // WinAPRS APRS:      ??, rectangle
3633       // WinAPRS Base91:    ??, ellipse
3634       // MacAPRS APRS:      ??, rectangle
3635       // MacAPRS Base91:    ??, ellipse
3636       // MacAPRSOSX APRS:   ??, rectangle
3637       // MacAPRSOSX Base91: ??, ellipse
3638       // X-APRS APRS:       ??, rectangle
3639       // X-APRS Base91:     ??, ellipse
3640       // OziAPRS:           ??, rectangle
3641       // NetAPRS:           ??, rectangle
3642       // APRS SCS:          ??, ??
3643       // RadioMobile:       ??, rectangle
3644       // KPC-3:             ??, rectangle
3645       // MicroTNC:          ??, rectangle
3646       // TigerTrak:         ??, rectangle
3647       // PicoPacket:        ??, rectangle
3648       // MIM:               ??, rectangle
3649       // Mic-Encoder:       ??, rectangle
3650       // Pic-Encoder:       ??, rectangle
3651       // Generic Mic-E:     ??, rectangle
3652       // D7A/D7E:           ??, rectangle
3653       // D700A:             ??, rectangle
3654       // Alinco DR-135:     ??, rectangle
3655       // Alinco DR-620:     ??, rectangle
3656       // Alinco DR-635:     ??, rectangle
3657       // Other:             ??, ??
3658 
3659 
3660       // Initial try at drawing the error_ellipse_radius
3661       // circles around the posit.  error_ellipse_radius is in
3662       // centimeters.  Convert from cm to miles for
3663       // draw_pod_circle().
3664       //
3665       /*
3666                   draw_pod_circle( p_station->coord_lon,
3667                                    p_station->coord_lat,
3668                                    p_station->error_ellipse_radius / 100000.0 * 0.62137, // cm to mi
3669                                    colors[0x0f],  // White
3670                                    drawing_target,
3671                                    temp_sec_heard);
3672       */
3673       draw_precision_rectangle( p_station->coord_lon,
3674                                 p_station->coord_lat,
3675                                 p_station->error_ellipse_radius, // centimeters (not implemented yet)
3676                                 p_station->lat_precision, // 100ths of seconds latitude
3677                                 p_station->lon_precision, // 100ths of seconds longitude
3678                                 colors[0x0f],  // White
3679                                 drawing_target);
3680 
3681 
3682       // Perhaps draw vectors from the symbol out to the borders of these
3683       // odd figures?  Draw an outline without vectors to the symbol?
3684       // Have the color match the track color assigned to that station so
3685       // the geometric figures can be kept separate from nearby stations?
3686       //
3687       // draw_truncation_rectangle + error_ellipse (symbol at corner)
3688       // draw_rounding_rectangle + error_ellipse (symbol in middle)
3689 
3690     }
3691   }
3692 
3693   // Zero out the variable in case we don't use it below.
3694   temp2_my_gauge_data[0] = '\0';
3695 
3696   // If an H2O object, create a timestamp + last comment variable
3697   // (which should contain gage-height and/or water-flow numbers)
3698   // for use in the draw_symbol() function below.
3699   if (p_station->aprs_symbol.aprs_type == '/'
3700       && p_station->aprs_symbol.aprs_symbol == 'w'
3701       && (   p_station->flag & ST_OBJECT    // And it's an object
3702              || p_station->flag & ST_ITEM) )   // or an item
3703   {
3704 
3705     // NOTE:  Also check whether it was sent by the Firenet GAGE
3706     // script??  "GAGE-*"
3707 
3708     // NOTE:  Check most recent comment time against
3709     // p_station->sec_heard.  If they don't match, don't display the
3710     // comment.  This will make sure that older comment data doesn't get
3711     // displayed which can be quite misleading for stream gauges.
3712 
3713     // Check whether we have any comment data at all.  If so,
3714     // the first one will be the most recent comment and the one
3715     // we wish to display.
3716     if (p_station->comment_data != NULL)
3717     {
3718       CommentRow *ptr;
3719 //            time_t sec;
3720 //            struct tm *time;
3721 
3722 
3723       ptr = p_station->comment_data;
3724 
3725       // Check most recent comment's sec_heard time against
3726       // the station record's sec_heard time.  If they don't
3727       // match, don't display the comment.  This will make
3728       // sure that older comment data doesn't get displayed
3729       // which can be quite misleading for stream gauges.
3730       if (p_station->sec_heard == ptr->sec_heard)
3731       {
3732 
3733         // Note that text_ptr can be an empty string.
3734         // That's ok.
3735 
3736         // Also print the sec_heard timestamp so we know
3737         // when this particular gauge data was received
3738         // (Very important!).
3739 //                sec = ptr->sec_heard;
3740 //                time = localtime(&sec);
3741 
3742         xastir_snprintf(temp2_my_gauge_data,
3743                         sizeof(temp2_my_gauge_data),
3744                         "%s",
3745 //                    "%02d/%02d %02d:%02d %s",
3746 //                    time->tm_mon + 1,
3747 //                    time->tm_mday,
3748 //                    time->tm_hour,
3749 //                    time->tm_min,
3750                         ptr->text_ptr);
3751 //fprintf(stderr, "%s\n", temp2_my_gauge_data);
3752       }
3753     }
3754   }
3755 
3756   draw_symbol(w,
3757               p_station->aprs_symbol.aprs_type,
3758               p_station->aprs_symbol.aprs_symbol,
3759               p_station->aprs_symbol.special_overlay,
3760               (ambiguity_flag) ? ambiguity_coord_lon : p_station->coord_lon,
3761               (ambiguity_flag) ? ambiguity_coord_lat : p_station->coord_lat,
3762               temp_call,
3763               temp_altitude,
3764               temp_course,    // ??
3765               temp_speed,     // ??
3766               temp_my_distance,
3767               temp_my_course,
3768               // Display only if wx temp is current
3769               (wx_ghost) ? "" : temp_wx_temp,
3770               // Display only if wind speed is current
3771               (wx_ghost) ? "" : temp_wx_wind,
3772               temp_sec_heard,
3773               temp_show_last_heard,
3774               drawing_target,
3775               orient,
3776               p_station->aprs_symbol.area_object.type,
3777               p_station->signpost,
3778               temp2_my_gauge_data,
3779               1); // Increment "currently_selected_stations"
3780 
3781   // If it's a Waypoint symbol, draw a line from it to the
3782   // transmitting station.
3783   if (p_station->aprs_symbol.aprs_type == '\\'
3784       && p_station->aprs_symbol.aprs_symbol == '/')
3785   {
3786 
3787     draw_WP_line(p_station,
3788                  ambiguity_flag,
3789                  ambiguity_coord_lon,
3790                  ambiguity_coord_lat,
3791                  drawing_target,
3792                  w);
3793   }
3794 
3795   // Draw other points associated with the station, if any.
3796   // KG4NBB
3797   if (debug_level & 128)
3798   {
3799     fprintf(stderr,"  Number of multipoints = %d\n",p_station->num_multipoints);
3800   }
3801   if (p_station->num_multipoints != 0)
3802   {
3803     draw_multipoints( (ambiguity_flag) ? ambiguity_coord_lon : p_station->coord_lon,
3804                       (ambiguity_flag) ? ambiguity_coord_lat : p_station->coord_lat,
3805                       p_station->num_multipoints,
3806                       p_station->multipoint_data->multipoints,
3807                       p_station->type, p_station->style,
3808                       temp_sec_heard,
3809                       drawing_target);
3810   }
3811 
3812   temp_sec_heard = p_station->sec_heard;    // DK7IN: ???
3813 
3814   if (Display_.phg
3815       && (!(p_station->flag & ST_MOVING) || Display_.phg_of_moving))
3816   {
3817 
3818     // Check for Map View "eyeball" symbol
3819     if ( strncmp(p_station->power_gain,"RNG",3) == 0
3820          && p_station->aprs_symbol.aprs_type == '/'
3821          && p_station->aprs_symbol.aprs_symbol == 'E' )
3822     {
3823       // Map View "eyeball" symbol.  Don't draw the RNG ring
3824       // for it.
3825     }
3826     else if (strlen(p_station->power_gain) == 7)
3827     {
3828       // Station has PHG or RNG defined
3829       //
3830       draw_phg_rng( (ambiguity_flag) ? ambiguity_coord_lon : p_station->coord_lon,
3831                     (ambiguity_flag) ? ambiguity_coord_lat : p_station->coord_lat,
3832                     p_station->power_gain,
3833                     temp_sec_heard,
3834                     drawing_target);
3835     }
3836     else if (Display_.default_phg && !(p_station->flag & (ST_OBJECT | ST_ITEM)))
3837     {
3838       // No PHG defined and not an object/item.  Display a PHG
3839       // of 3130 as default as specified in the spec:  9W, 3dB
3840       // omni at 20 feet = 6.2 mile PHG radius.
3841       //
3842       draw_phg_rng( (ambiguity_flag) ? ambiguity_coord_lon : p_station->coord_lon,
3843                     (ambiguity_flag) ? ambiguity_coord_lat : p_station->coord_lat,
3844                     "PHG3130",
3845                     temp_sec_heard,
3846                     drawing_target);
3847     }
3848   }
3849 
3850 
3851   // Draw minimum proximity circle?
3852   if (p_station->probability_min[0] != '\0')
3853   {
3854     double range = atof(p_station->probability_min);
3855 
3856     // Draw red circle
3857     draw_pod_circle(p_station->coord_lon,
3858                     p_station->coord_lat,
3859                     range,
3860                     colors[0x44],
3861                     drawing_target,
3862                     temp_sec_heard);
3863   }
3864 
3865   // Draw maximum proximity circle?
3866   if (p_station->probability_max[0] != '\0')
3867   {
3868     double range = atof(p_station->probability_max);
3869 
3870     // Draw red circle
3871     draw_pod_circle(p_station->coord_lon,
3872                     p_station->coord_lat,
3873                     range,
3874                     colors[0x44],
3875                     drawing_target,
3876                     temp_sec_heard);
3877   }
3878 
3879   // DEBUG STUFF
3880   //            draw_pod_circle(x_long, y_lat, 1.5, colors[0x44], where);
3881   //            draw_pod_circle(x_long, y_lat, 3.0, colors[0x44], where);
3882 
3883 
3884   // Now if we just did the single drawing, we want to go back and draw
3885   // the same things onto pixmap_final so that when we do update from it
3886   // to the screen all of the stuff will be there.
3887 //    if (drawing_target == XtWindow(da)) {
3888 //        drawing_target = pixmap_final;
3889 //        goto _do_the_drawing;
3890 //    }
3891 }
3892 
3893 
3894 
3895 
3896 
3897 // draw line relative
draw_test_line(Widget w,long x,long y,long dx,long dy,long ofs)3898 void draw_test_line(Widget w, long x, long y, long dx, long dy, long ofs)
3899 {
3900 
3901   x += screen_width  - 10 - ofs;
3902   y += screen_height - 10;
3903   (void)XDrawLine(XtDisplay(w),
3904                   pixmap_final,
3905                   gc,
3906                   l16(x),
3907                   l16(y),
3908                   l16(x+dx),
3909                   l16(y+dy));
3910 }
3911 
3912 
3913 
3914 
3915 
3916 // draw text
draw_ruler_text(Widget w,char * text,long ofs)3917 void draw_ruler_text(Widget w, char * text, long ofs)
3918 {
3919   int x,y;
3920   int len;
3921 
3922   len = (int)strlen(text);
3923   x = screen_width  - 10 - ofs / 2;
3924   y = screen_height - 10;
3925   x -= len * 3;
3926   y -= 3;
3927   if (draw_labeled_grid_border==TRUE)
3928   {
3929     // move text up a few pixels to leave space for labeled border
3930     y = y - 15;
3931     x = x - 10;
3932   }
3933   draw_nice_string(w,pixmap_final,letter_style,x,y,text,0x10,0x20,len);
3934 }
3935 
3936 
3937 
3938 
3939 
3940 // Compute Range Scale in miles or kilometers.
3941 //
3942 // For this we need to figure out x-distance and y-distance across
3943 // the screen.  Take the smaller of the two, then figure out which
3944 // power of 2 miles fits from the center to the edge of the screen.
3945 // "For metric, use the nearest whole number kilometer in powers of
3946 // two of 1.5 km above the 1 mile scale.  At 1 mile and below, do
3947 // the conversion to meters where 1 mi is equal to 1600m..." (Bob
3948 // Bruninga's words).
draw_range_scale(Widget w)3949 void draw_range_scale(Widget w)
3950 {
3951   Dimension width, height;
3952   long x, x0, y, y0;
3953   double x_miles_km, y_miles_km, distance;
3954   char temp_course[10];
3955   long temp;
3956   double temp2;
3957   long range;
3958   int small_flag = 0;
3959   int x_screen, y_screen;
3960   int len;
3961   char text[80];
3962   int border_offset = 0;  // number of pixels to offset the scale if a labeled map border is drawn
3963 
3964 
3965   // Find out the screen values
3966   XtVaGetValues(da,XmNwidth, &width, XmNheight, &height, NULL);
3967 
3968   // Convert points to Xastir coordinate system
3969 
3970   // X
3971   x = center_longitude  - ((width *scale_x)/2);
3972   x0 = center_longitude; // Center of screen
3973 
3974   // Y
3975   y = center_latitude   - ((height*scale_y)/2);
3976   y0 = center_latitude;  // Center of screen
3977 
3978   // Compute distance from center to each edge
3979 
3980   // X distance.  Keep Y constant.
3981   x_miles_km = cvt_kn2len * calc_distance_course(y0,x0,y0,x,temp_course,sizeof(temp_course));
3982 
3983   // Y distance.  Keep X constant.
3984   y_miles_km = cvt_kn2len * calc_distance_course(y0,x0,y,x0,temp_course,sizeof(temp_course));
3985 
3986   // Choose the smaller distance
3987   if (x_miles_km < y_miles_km)
3988   {
3989     distance = x_miles_km;
3990   }
3991   else
3992   {
3993     distance = y_miles_km;
3994   }
3995 
3996   // Convert it to nearest power of two that fits inside
3997 
3998   if (english_units)   // English units
3999   {
4000     if (distance >= 1.0)
4001     {
4002       // Shift it right until it is less than 2.
4003       temp = (long)distance;
4004       range = 1;
4005       while (temp >= 2)
4006       {
4007         temp = temp / 2;
4008         range = range * 2;
4009       }
4010     }
4011     else    // Distance is less than one
4012     {
4013       // divide 1.0 by 2 until distance is greater
4014       small_flag++;
4015       temp2 = 1.0;
4016       range = 1;
4017       while (temp2 > distance)
4018       {
4019         //fprintf(stderr,"temp2: %f,  distance: %f\n", temp2, distance);
4020         temp2 = temp2 / 2.0;
4021         range = range * 2;
4022       }
4023     }
4024   }
4025   else    // Metric units
4026   {
4027     if (distance >= 12800.0)
4028     {
4029       range = 12800;
4030     }
4031     else if (distance >= 6400.0)
4032     {
4033       range = 6400;
4034     }
4035     else if (distance >= 3200.0)
4036     {
4037       range = 3200;
4038     }
4039     else if (distance >= 1600.0)
4040     {
4041       range = 1600;
4042     }
4043     else if (distance >= 800.0)
4044     {
4045       range = 800;
4046     }
4047     else if (distance >= 400.0)
4048     {
4049       range = 400;
4050     }
4051     else if (distance >= 200.0)
4052     {
4053       range = 200;
4054     }
4055     else if (distance >= 100.0)
4056     {
4057       range = 100;
4058     }
4059     else if (distance >= 50.0)
4060     {
4061       range = 50;
4062     }
4063     else if (distance >= 25.0)
4064     {
4065       range = 25;
4066     }
4067     else if (distance >= 12.0)
4068     {
4069       range = 12;
4070     }
4071     else if (distance >= 6.0)
4072     {
4073       range = 6;
4074     }
4075     else if (distance >= 3.0)
4076     {
4077       range = 3;
4078     }
4079     else
4080     {
4081       small_flag++;
4082       if (distance >= 1.6)
4083       {
4084         range = 1600;
4085       }
4086       else if (distance >= 0.8)
4087       {
4088         range = 800;
4089       }
4090       else if (distance >= 0.4)
4091       {
4092         range = 400;
4093       }
4094       else if (distance >= 0.2)
4095       {
4096         range = 200;
4097       }
4098       else if (distance >= 0.1)
4099       {
4100         range = 100;
4101       }
4102       else if (distance >= 0.05)
4103       {
4104         range = 50;
4105       }
4106       else if (distance >= 0.025)
4107       {
4108         range = 25;
4109       }
4110       else
4111       {
4112         range = 12;
4113       }
4114     }
4115   }
4116 
4117   //fprintf(stderr,"Distance: %f\t", distance);
4118   //fprintf(stderr,"Range: %ld\n", range);
4119 
4120   if (english_units)   // English units
4121   {
4122     if (small_flag)
4123     {
4124       xastir_snprintf(text,
4125                       sizeof(text),
4126                       "%s 1/%ld mi",
4127                       langcode("RANGE001"),   // "RANGE SCALE"
4128                       range);
4129     }
4130     else
4131     {
4132       xastir_snprintf(text,
4133                       sizeof(text),
4134                       "%s %ld mi",
4135                       langcode("RANGE001"),   // "RANGE SCALE"
4136                       range);
4137     }
4138   }
4139   else    // Metric units
4140   {
4141     if (small_flag)
4142     {
4143       xastir_snprintf(text,
4144                       sizeof(text),
4145                       "%s %ld m",
4146                       langcode("RANGE001"),   // "RANGE SCALE"
4147                       range);
4148     }
4149     else
4150     {
4151       xastir_snprintf(text,
4152                       sizeof(text),
4153                       "%s %ld km",
4154                       langcode("RANGE001"),   // "RANGE SCALE"
4155                       range);
4156     }
4157   }
4158 
4159   // Draw it on the screen
4160   len = (int)strlen(text);
4161   x_screen = 10;
4162   y_screen = screen_height - 5;
4163   if ((draw_labeled_grid_border==TRUE) && long_lat_grid)
4164   {
4165     border_offset = get_rotated_label_text_length_pixels(w, "0", FONT_BORDER) + 3;
4166     // don't draw range scale right on top of labeled border, move into map
4167     draw_nice_string(w,pixmap_final,letter_style,x_screen+border_offset,y_screen-border_offset-3,text,0x10,0x20,len);
4168   }
4169   else
4170   {
4171     // draw range scale in lower left corder of map
4172     draw_nice_string(w,pixmap_final,letter_style,x_screen,y_screen,text,0x10,0x20,len);
4173   }
4174 
4175 }
4176 
4177 
4178 
4179 
4180 
4181 /*
4182  *  Calculate and draw ruler on right bottom of screen
4183  */
draw_ruler(Widget w)4184 void draw_ruler(Widget w)
4185 {
4186   int ruler_pix;      // min size of ruler in pixel
4187   char unit[5+1];     // units
4188   char text[20];      // ruler text
4189   double ruler_siz;   // len of ruler in meters etc.
4190   int mag;
4191   int i;
4192   int dx, dy;
4193   int border_offset = 0;  // number of pixels to offset the scale if a labeled map border is drawn
4194 
4195   ruler_pix = (int)(screen_width / 9);        // ruler size (in pixels)
4196   ruler_siz = ruler_pix * scale_x * calc_dscale_x(center_longitude,center_latitude); // size in meter
4197 
4198   if(english_units)
4199   {
4200     if (ruler_siz > 1609.3/2)
4201     {
4202       xastir_snprintf(unit,
4203                       sizeof(unit),
4204                       "mi");
4205       ruler_siz /= 1609.3;
4206     }
4207     else
4208     {
4209       xastir_snprintf(unit,
4210                       sizeof(unit),
4211                       "ft");
4212       ruler_siz /= 0.3048;
4213     }
4214   }
4215   else
4216   {
4217     xastir_snprintf(unit,
4218                     sizeof(unit),
4219                     "m");
4220     if (ruler_siz > 1000/2)
4221     {
4222       xastir_snprintf(unit,
4223                       sizeof(unit),
4224                       "km");
4225       ruler_siz /= 1000.0;
4226     }
4227   }
4228 
4229   mag = 1;
4230   while (ruler_siz > 5.0)               // get magnitude
4231   {
4232     ruler_siz /= 10.0;
4233     mag *= 10;
4234   }
4235   // select best value and adjust ruler length
4236   if (ruler_siz > 2.0)
4237   {
4238     ruler_pix = (int)(ruler_pix * 5.0 / ruler_siz +0.5);
4239     ruler_siz = 5.0 * mag;
4240   }
4241   else
4242   {
4243     if (ruler_siz > 1.0)
4244     {
4245       ruler_pix = (int)(ruler_pix * 2.0 / ruler_siz +0.5);
4246       ruler_siz = 2.0 * mag;
4247     }
4248     else
4249     {
4250       ruler_pix = (int)(ruler_pix * 1.0 / ruler_siz +0.5);
4251       ruler_siz = 1.0 * mag;
4252     }
4253   }
4254   xastir_snprintf(text, sizeof(text), "%.0f %s",ruler_siz,unit);      // Set up string
4255   //fprintf(stderr,"Ruler: %s, %d\n",text,ruler_pix);
4256 
4257   (void)XSetLineAttributes(XtDisplay(w),gc,1,LineSolid,CapRound,JoinRound);
4258   (void)XSetForeground(XtDisplay(w),gc,colors[0x20]);         // white
4259   for (i = 8; i >= 0; i--)
4260   {
4261     dx = (((i / 3)+1) % 3)-1;         // looks complicated...
4262     dy = (((i % 3)+1) % 3)-1;         // I want 0 / 0 as last entry
4263     if ((draw_labeled_grid_border==TRUE) && long_lat_grid)
4264     {
4265       // move ruler up a few pixels to leave space for labeled border
4266       border_offset = get_rotated_label_text_length_pixels(w, "0", FONT_BORDER) + 3;
4267       dy = dy - border_offset - 3;
4268       dx = dx - border_offset - 3;
4269     }
4270 
4271     // If text on black background style selected, draw a black
4272     // rectangle in that corner of the map first so that the
4273     // scale lines show up well.
4274     //
4275     // If first time through and text-on-black style
4276     if ( (i == 8) && (letter_style == 2) )
4277     {
4278       XSetForeground(XtDisplay(w),gc,colors[0x10]);   // black
4279       (void)XSetLineAttributes(XtDisplay(w),gc,20,LineSolid,CapProjecting,JoinMiter);
4280       draw_test_line(w, dx, dy+5, ruler_pix, 0, ruler_pix);
4281 
4282       // Reset to needed parameters for drawing the scale
4283       (void)XSetLineAttributes(XtDisplay(w),gc,1,LineSolid,CapRound,JoinRound);
4284       (void)XSetForeground(XtDisplay(w),gc,colors[0x20]);         // white
4285     }
4286 
4287     if (i == 0)
4288     {
4289       (void)XSetForeground(XtDisplay(w),gc,colors[0x10]);  // black
4290     }
4291 
4292     draw_test_line(w,dx,dy,          ruler_pix,0,ruler_pix);        // hor line
4293     draw_test_line(w,dx,dy,              0,5,    ruler_pix);        // ver left
4294     draw_test_line(w,dx+ruler_pix,dy,    0,5,    ruler_pix);        // ver right
4295     if (text[0] == '2')
4296     {
4297       draw_test_line(w,dx+0.5*ruler_pix,dy,0,3,ruler_pix);  // ver middle
4298     }
4299 
4300     if (text[0] == '5')
4301     {
4302       draw_test_line(w,dx+0.2*ruler_pix,dy,0,3,ruler_pix);        // ver middle
4303       draw_test_line(w,dx+0.4*ruler_pix,dy,0,3,ruler_pix);        // ver middle
4304       draw_test_line(w,dx+0.6*ruler_pix,dy,0,3,ruler_pix);        // ver middle
4305       draw_test_line(w,dx+0.8*ruler_pix,dy,0,3,ruler_pix);        // ver middle
4306     }
4307   }
4308 
4309   draw_ruler_text(w,text,ruler_pix);
4310 
4311   draw_range_scale(w);
4312 }
4313 
4314 
4315 
4316 
4317 
4318 /*
4319  *  Display all stations on screen (trail, symbol, info text)
4320  */
display_file(Widget w)4321 void display_file(Widget w)
4322 {
4323   DataRow *p_station;         // pointer to station data
4324   time_t temp_sec_heard;      // time last heard
4325   time_t t_clr, t_old, now;
4326 
4327   if(debug_level & 1)
4328   {
4329     fprintf(stderr,"Display File Start\n");
4330   }
4331 
4332   // Keep track of how many station we are currently displaying on
4333   // the screen.  We'll display this number and the total number
4334   // of objects in the database as displayed/total on the status
4335   // line.  Each time we call display_station() we'll bump this
4336   // number.
4337   currently_selected_stations = 0;
4338 
4339   // Draw probability of detection circle, if enabled
4340   //draw_pod_circle(64000000l, 32400000l, 10, colors[0x44], pixmap_final);
4341 
4342   now = sec_now();
4343   t_old = now - sec_old;        // precalc compare times
4344   t_clr = now - sec_clear;
4345   temp_sec_heard = 0l;
4346   p_station = t_oldest;                // start with oldest station, have newest on top at t_newest
4347 
4348   while (p_station != NULL)
4349   {
4350 
4351     if (debug_level & 64)
4352     {
4353       fprintf(stderr,"display_file: Examining %s\n", p_station->call_sign);
4354     }
4355 
4356     // Skip deleted stations
4357     if ( !(p_station->flag & ST_ACTIVE) )
4358     {
4359 
4360       if (debug_level & 64)
4361       {
4362         fprintf(stderr,"display_file: ignored deleted %s\n", p_station->call_sign);
4363       }
4364 
4365       // Skip to the next station in the list
4366       p_station = p_station->t_newer;  // next station
4367       continue;
4368     }
4369 
4370     // Check for my objects/items
4371 //        if ( (is_my_call(p_station->origin, 1)        // If station is owned by me (including SSID)
4372 //                && (   p_station->flag & ST_OBJECT    // And it's an object
4373 //                    || p_station->flag & ST_ITEM) ) ) { // or an item
4374 //
4375     // This case is covered by the is_my_station() call, so we
4376     // don't need it here.
4377 //        if (is_my_object_item(p_station) ) {
4378 //            temp_sec_heard = now;
4379 //        }
4380 //        else {
4381     // Callsign match here includes checking SSID
4382 //            temp_sec_heard = (is_my_call(p_station->call_sign,1))?  now: p_station->sec_heard;
4383     temp_sec_heard = (is_my_station(p_station)) ? now : p_station->sec_heard;
4384 //        }
4385 
4386     // Skip far away station
4387     if ((p_station->flag & ST_INVIEW) == 0)
4388     {
4389       // we make better use of the In View flag in the future
4390 
4391       if (debug_level & 256)
4392       {
4393         fprintf(stderr,"display_file: Station outside viewport\n");
4394       }
4395 
4396       // Skip to the next station in the list
4397       p_station = p_station->t_newer;  // next station
4398       continue;
4399     }
4400 
4401     // Skip if we're running an altnet and this station's not in
4402     // it
4403     if ( altnet && !is_altnet(p_station) )
4404     {
4405 
4406       if (debug_level & 64)
4407       {
4408         fprintf(stderr,"display_file: Station %s skipped altnet\n",
4409                 p_station->call_sign);
4410       }
4411 
4412       // Skip to the next station in the list
4413       p_station = p_station->t_newer;  // next station
4414       continue;
4415     }
4416 
4417     if (debug_level & 256)
4418     {
4419       fprintf(stderr,"display_file:  Inview, check for trail\n");
4420     }
4421 
4422     // Display trail if we should
4423     if (Display_.trail && p_station->newest_trackpoint != NULL)
4424     {
4425       // ????????????   what is the difference? :
4426 
4427       if (debug_level & 256)
4428       {
4429         fprintf(stderr,"%s:    Trails on and have track data\n",
4430                 "display_file");
4431       }
4432 
4433       if (temp_sec_heard > t_clr)
4434       {
4435         // Not too old, so draw trail
4436 
4437         if (temp_sec_heard > t_old)
4438         {
4439           // New trail, so draw solid trail
4440 
4441           if (debug_level & 256)
4442           {
4443             fprintf(stderr,"Drawing Solid trail for %s, secs old: %ld\n",
4444                     p_station->call_sign,
4445                     (long)(now - temp_sec_heard) );
4446           }
4447           draw_trail(w,p_station,1);
4448         }
4449         else
4450         {
4451 
4452           if (debug_level & 256)
4453           {
4454             fprintf(stderr,"Drawing trail for %s, secs old: %ld\n",
4455                     p_station->call_sign,
4456                     (long)(now - temp_sec_heard) );
4457           }
4458           draw_trail(w,p_station,0);
4459         }
4460       }
4461       else
4462       {
4463         if (debug_level & 256)
4464         {
4465           fprintf(stderr,"Station too old\n");
4466         }
4467       }
4468     }
4469     else
4470     {
4471       if (debug_level & 256)
4472       {
4473         fprintf(stderr,"Station trails %d, track data %lx\n",
4474                 Display_.trail, (long int)p_station->newest_trackpoint);
4475       }
4476     }
4477 
4478     if (debug_level & 256)
4479     {
4480       fprintf(stderr,"calling display_station()\n");
4481     }
4482 
4483     // This routine will also update the
4484     // currently_selected_stations variable, if we're
4485     // updating all of the stations at once.
4486     display_station(w,p_station,0);
4487 
4488     p_station = p_station->t_newer;  // next station
4489   }
4490 
4491   draw_ruler(w);
4492 
4493   Draw_All_CAD_Objects(w);        // Draw all CAD objects, duh.
4494 
4495   // Check if we should mark where we found an address
4496   if (mark_destination && show_destination_mark)
4497   {
4498     int offset;
4499 
4500     // Set the line width in the GC.  Make it nice and fat.
4501     (void)XSetLineAttributes (XtDisplay (w), gc_tint, 7, LineSolid, CapButt,JoinMiter);
4502     (void)XSetForeground (XtDisplay (w), gc_tint, colors[0x27]);
4503     (void)(void)XSetFunction (XtDisplay (da), gc_tint, GXxor);
4504 
4505     // Scale it so that the 'X' stays the same size at all zoom
4506     // levels.
4507     offset = 25 * scale_y;
4508 
4509     // Make a big 'X'
4510     draw_vector(w,
4511                 destination_coord_lon-offset,  // x1
4512                 destination_coord_lat-offset,  // y1
4513                 destination_coord_lon+offset,  // x2
4514                 destination_coord_lat+offset,  // y2
4515                 gc_tint,
4516                 pixmap_final,
4517                 0);
4518 
4519     draw_vector(w,
4520                 destination_coord_lon+offset,  // x1
4521                 destination_coord_lat-offset,  // y1
4522                 destination_coord_lon-offset,  // x2
4523                 destination_coord_lat+offset,  // y2
4524                 gc_tint,
4525                 pixmap_final,
4526                 0);
4527   }
4528 
4529   // And last, draw the ALOHA circle
4530   if (Display_.aloha_circle)
4531   {
4532     if (aloha_radius != -1)
4533     {
4534       // if we actually have an aloha radius calculated already
4535       long l_lat,l_lon;
4536 
4537       l_lat = convert_lat_s2l(my_lat);
4538       l_lon = convert_lon_s2l(my_long);
4539       draw_aloha_circle(l_lon,
4540                         l_lat,
4541                         aloha_radius,
4542                         colors[0x0e],
4543                         pixmap_final);
4544     }
4545   }
4546 
4547   // Check whether currently_selected_stations has changed.  If
4548   // so, set station_count_save to 0 so that main.c will come
4549   // along and update the counts on the status line.
4550   if (currently_selected_stations != currently_selected_stations_save)
4551   {
4552     station_count_save = 0;   // Cause an update to occur
4553   }
4554   currently_selected_stations_save = currently_selected_stations;
4555 
4556 
4557   if (debug_level & 1)
4558   {
4559     fprintf(stderr,"Display File Stop\n");
4560   }
4561 }
4562 
4563 
4564 
4565 
4566 
4567 //////////////////////////////  Station Info  /////////////////////////////////////
4568 
4569 
4570 
4571 
4572 
4573 /*
4574  *  Delete Station Info PopUp
4575  */
Station_data_destroy_shell(Widget UNUSED (widget),XtPointer clientData,XtPointer UNUSED (callData))4576 void Station_data_destroy_shell(Widget UNUSED(widget), XtPointer clientData, XtPointer UNUSED(callData) )
4577 {
4578   Widget shell = (Widget) clientData;
4579   XtPopdown(shell);
4580 
4581   begin_critical_section(&db_station_info_lock, "db.c:Station_data_destroy_shell" );
4582 
4583   XtDestroyWidget(shell);
4584   db_station_info = (Widget)NULL;
4585 
4586   end_critical_section(&db_station_info_lock, "db.c:Station_data_destroy_shell" );
4587 
4588 }
4589 
4590 
4591 
4592 
4593 
4594 /*
4595  *  Store track data for current station
4596  */
Station_data_store_track(Widget UNUSED (w),XtPointer clientData,XtPointer UNUSED (callData))4597 void Station_data_store_track(Widget UNUSED(w), XtPointer clientData, XtPointer UNUSED(callData) )
4598 {
4599   DataRow *p_station = clientData;
4600 
4601   //busy_cursor(XtParent(w));
4602   busy_cursor(appshell);
4603 
4604   // Grey-out button so it doesn't get pressed twice
4605   XtSetSensitive(button_store_track,FALSE);
4606 
4607   // Store trail to file
4608   export_trail(p_station);
4609 
4610 #ifdef HAVE_LIBSHP
4611   // Save trail as a Shapefile map
4612   create_map_from_trail(p_station->call_sign);
4613 #endif  // HAVE_LIBSHP
4614 
4615   // store trail to kml file
4616   export_trail_as_kml(p_station);
4617 }
4618 
4619 
4620 
4621 
4622 
4623 /*
4624  *  Delete tracklog for current station
4625  */
Station_data_destroy_track(Widget UNUSED (widget),XtPointer clientData,XtPointer UNUSED (callData))4626 void Station_data_destroy_track( Widget UNUSED(widget), XtPointer clientData, XtPointer UNUSED(callData) )
4627 {
4628   DataRow *p_station = clientData;
4629 
4630   if (delete_trail(p_station))
4631   {
4632     redraw_on_new_data = 2;  // redraw immediately
4633   }
4634 }
4635 
4636 
4637 
4638 
4639 
4640 // This function merely reformats the button callback in order to
4641 // call wx_alert_double_click_action, which expects the parameter in
4642 // calldata instead of in clientData.
4643 //
Station_data_wx_alert(Widget w,XtPointer clientData,XtPointer UNUSED (calldata))4644 void Station_data_wx_alert(Widget w, XtPointer clientData, XtPointer UNUSED(calldata) )
4645 {
4646   //fprintf(stderr, "Station_data_wx_alert start\n");
4647   wx_alert_finger_output( w, clientData);
4648   //fprintf(stderr, "Station_data_wx_alert end\n");
4649 }
4650 
4651 
4652 
4653 
4654 
Station_data_add_fcc(Widget UNUSED (w),XtPointer clientData,XtPointer UNUSED (calldata))4655 void Station_data_add_fcc(Widget UNUSED(w), XtPointer clientData, XtPointer UNUSED(calldata) )
4656 {
4657   char temp[500];
4658   FccAppl my_data;
4659   char *station = (char *) clientData;
4660 
4661   (void)check_fcc_data();
4662   //busy_cursor(XtParent(w));
4663   busy_cursor(appshell);
4664   if (search_fcc_data_appl(station, &my_data)==1)
4665   {
4666     /*fprintf(stderr,"FCC call %s\n",station);*/
4667     xastir_snprintf(temp, sizeof(temp), "%s\n%s %s\n%s %s %s\n%s %s, %s %s, %s %s\n\n",
4668                     langcode("STIFCC0001"),
4669                     langcode("STIFCC0003"),my_data.name_licensee,
4670                     langcode("STIFCC0004"),my_data.text_street,my_data.text_pobox,
4671                     langcode("STIFCC0005"),my_data.city,
4672                     langcode("STIFCC0006"),my_data.state,
4673                     langcode("STIFCC0007"),my_data.zipcode);
4674     XmTextInsert(si_text,0,temp);
4675     XmTextShowPosition(si_text,0);
4676 
4677     fcc_lookup_pushed = 1;
4678   }
4679 }
4680 
4681 
4682 
4683 
4684 
Station_data_add_rac(Widget UNUSED (w),XtPointer clientData,XtPointer UNUSED (calldata))4685 void Station_data_add_rac(Widget UNUSED(w), XtPointer clientData, XtPointer UNUSED(calldata) )
4686 {
4687   char temp[512];
4688   char club[512];
4689   rac_record my_data;
4690   char *station = (char *) clientData;
4691 
4692   xastir_snprintf(temp,
4693                   sizeof(temp),
4694                   " ");
4695   (void)check_rac_data();
4696   //busy_cursor(XtParent(w));
4697   busy_cursor(appshell);
4698   if (search_rac_data(station, &my_data)==1)
4699   {
4700     /*fprintf(stderr,"IC call %s\n",station);*/
4701     xastir_snprintf(temp, sizeof(temp), "%s\n%s %s\n%s\n%s, %s\n%s\n",
4702                     langcode("STIFCC0002"),my_data.first_name,my_data.last_name,my_data.address,
4703                     my_data.city,my_data.province,my_data.postal_code);
4704 
4705     if (my_data.qual_a[0] == 'A')
4706       strncat(temp,
4707               langcode("STIFCC0008"),
4708               sizeof(temp) - 1 - strlen(temp));
4709 
4710     if (my_data.qual_d[0] == 'D')
4711       strncat(temp,
4712               langcode("STIFCC0009"),
4713               sizeof(temp) - 1 - strlen(temp));
4714 
4715     if (my_data.qual_b[0] == 'B' && my_data.qual_c[0] != 'C')
4716       strncat(temp,
4717               langcode("STIFCC0010"),
4718               sizeof(temp) - 1 - strlen(temp));
4719 
4720     if (my_data.qual_c[0] == 'C')
4721       strncat(temp,
4722               langcode("STIFCC0011"),
4723               sizeof(temp) - 1 - strlen(temp));
4724 
4725     strncat(temp,
4726             "\n",
4727             sizeof(temp) - 1 - strlen(temp));
4728 
4729     if (strlen(my_data.club_name) > 1)
4730     {
4731       xastir_snprintf(club, sizeof(club), "%s\n%s\n%s, %s\n%s\n",
4732                       my_data.club_name, my_data.club_address,
4733                       my_data.club_city, my_data.club_province, my_data.club_postal_code);
4734       strncat(temp,
4735               club,
4736               sizeof(temp) - 1 - strlen(temp));
4737     }
4738     strncat(temp,
4739             "\n",
4740             sizeof(temp) - 1 - strlen(temp));
4741     XmTextInsert(si_text,0,temp);
4742     XmTextShowPosition(si_text,0);
4743 
4744     rac_lookup_pushed = 1;
4745   }
4746 }
4747 
4748 
4749 
4750 
4751 
Station_query_trace(Widget UNUSED (w),XtPointer clientData,XtPointer UNUSED (calldata))4752 void Station_query_trace(Widget UNUSED(w), XtPointer clientData, XtPointer UNUSED(calldata) )
4753 {
4754   char *station = (char *) clientData;
4755   char temp[50];
4756   char call[25];
4757 
4758   pad_callsign(call,station);
4759   xastir_snprintf(temp, sizeof(temp), ":%s:?APRST", call);
4760 
4761   // Nice to return via the reverse path here?  No!  Better to use the
4762   // default paths instead of a calculated reverse path.
4763 
4764   transmit_message_data(station,temp,NULL);
4765 }
4766 
4767 
4768 
4769 
4770 
Station_query_messages(Widget UNUSED (w),XtPointer clientData,XtPointer UNUSED (calldata))4771 void Station_query_messages(Widget UNUSED(w), XtPointer clientData, XtPointer UNUSED(calldata) )
4772 {
4773   char *station = (char *) clientData;
4774   char temp[50];
4775   char call[25];
4776 
4777   pad_callsign(call,station);
4778   xastir_snprintf(temp, sizeof(temp), ":%s:?APRSM", call);
4779 
4780   // Nice to return via the reverse path here?  No!  Better to use the
4781   // default paths instead of a calculated reverse path.
4782 
4783   transmit_message_data(station,temp,NULL);
4784 }
4785 
4786 
4787 
4788 
4789 
Station_query_direct(Widget UNUSED (w),XtPointer clientData,XtPointer UNUSED (calldata))4790 void Station_query_direct(Widget UNUSED(w), XtPointer clientData, XtPointer UNUSED(calldata) )
4791 {
4792   char *station = (char *) clientData;
4793   char temp[50];
4794   char call[25];
4795 
4796   pad_callsign(call,station);
4797   xastir_snprintf(temp, sizeof(temp), ":%s:?APRSD", call);
4798 
4799   // Nice to return via the reverse path here?  No!  Better to use the
4800   // default paths instead of a calculated reverse path.
4801 
4802   transmit_message_data(station,temp,NULL);
4803 }
4804 
4805 
4806 
4807 
4808 
Station_query_version(Widget UNUSED (w),XtPointer clientData,XtPointer UNUSED (calldata))4809 void Station_query_version(Widget UNUSED(w), XtPointer clientData, XtPointer UNUSED(calldata) )
4810 {
4811   char *station = (char *) clientData;
4812   char temp[50];
4813   char call[25];
4814 
4815   pad_callsign(call,station);
4816   xastir_snprintf(temp, sizeof(temp), ":%s:?VER", call);
4817 
4818   // Nice to return via the reverse path here?  No!  Better to use the
4819   // default paths instead of a calculated reverse path.
4820 
4821   transmit_message_data(station,temp,NULL);
4822 }
4823 
4824 
4825 
4826 
4827 
General_query(Widget UNUSED (w),XtPointer clientData,XtPointer UNUSED (calldata))4828 void General_query(Widget UNUSED(w), XtPointer clientData, XtPointer UNUSED(calldata) )
4829 {
4830   char *location = (char *) clientData;
4831   char temp[50];
4832 
4833   xastir_snprintf(temp, sizeof(temp), "?APRS?%s", location);
4834   output_my_data(temp,-1,0,0,0,NULL);  // Not igating
4835 }
4836 
4837 
4838 
4839 
4840 
IGate_query(Widget UNUSED (w),XtPointer UNUSED (clientData),XtPointer UNUSED (calldata))4841 void IGate_query(Widget UNUSED(w), XtPointer UNUSED(clientData), XtPointer UNUSED(calldata) )
4842 {
4843   output_my_data("?IGATE?",-1,0,0,0,NULL); // Not igating
4844 }
4845 
4846 
4847 
4848 
4849 
WX_query(Widget UNUSED (w),XtPointer UNUSED (clientData),XtPointer UNUSED (calldata))4850 void WX_query(Widget UNUSED(w), XtPointer UNUSED(clientData), XtPointer UNUSED(calldata) )
4851 {
4852   output_my_data("?WX?",-1,0,0,0,NULL);    // Not igating
4853 }
4854 
4855 
4856 
4857 
4858 
4859 // Global variables for use with routines following
4860 Widget change_tactical_dialog = (Widget)NULL;
4861 Widget tactical_text = (Widget)NULL;
4862 DataRow *tactical_pointer = NULL;
4863 
4864 
Change_tactical_destroy_shell(Widget UNUSED (widget),XtPointer clientData,XtPointer UNUSED (callData))4865 void Change_tactical_destroy_shell( Widget UNUSED(widget), XtPointer clientData, XtPointer UNUSED(callData) )
4866 {
4867   Widget shell = (Widget) clientData;
4868   XtPopdown(shell);
4869   XtDestroyWidget(shell);
4870   change_tactical_dialog = (Widget)NULL;
4871 }
4872 
4873 
4874 
4875 
4876 
Change_tactical_change_data(Widget widget,XtPointer clientData,XtPointer callData)4877 void Change_tactical_change_data(Widget widget, XtPointer clientData, XtPointer callData)
4878 {
4879   char *temp;
4880 
4881   temp = XmTextGetString(tactical_text);
4882 
4883   if (tactical_pointer->tactical_call_sign == NULL)
4884   {
4885     // Malloc some memory to hold it.
4886     tactical_pointer->tactical_call_sign = (char *)malloc(MAX_TACTICAL_CALL+1);
4887   }
4888 
4889   if (tactical_pointer->tactical_call_sign != NULL)
4890   {
4891 
4892     // Check for blank tactical call.  If so, free the space.
4893     if (temp[0] == '\0')
4894     {
4895       free(tactical_pointer->tactical_call_sign);
4896       tactical_pointer->tactical_call_sign = NULL;
4897     }
4898     else
4899     {
4900       xastir_snprintf(tactical_pointer->tactical_call_sign,
4901                       MAX_TACTICAL_CALL+1,
4902                       "%s",
4903                       temp);
4904     }
4905 
4906     fprintf(stderr,
4907             "Assigned tactical call \"%s\" to %s\n",
4908             temp,
4909             tactical_pointer->call_sign);
4910 
4911     // Log the change in the tactical_calls.log file.
4912     // Also adds it to the tactical callsign hash here.
4913     log_tactical_call(tactical_pointer->call_sign,
4914                       tactical_pointer->tactical_call_sign);
4915   }
4916   else
4917   {
4918     fprintf(stderr,
4919             "Couldn't malloc space for tactical callsign\n");
4920   }
4921 
4922   XtFree(temp);
4923 
4924   redraw_on_new_data = 2;  // redraw now
4925 
4926   Change_tactical_destroy_shell(widget,clientData,callData);
4927 }
4928 
4929 
4930 
4931 
4932 
Change_tactical(Widget UNUSED (w),XtPointer UNUSED (clientData),XtPointer UNUSED (callData))4933 void Change_tactical(Widget UNUSED(w), XtPointer UNUSED(clientData), XtPointer UNUSED(callData) )
4934 {
4935   static Widget pane, my_form, button_ok, button_close, label, scrollwindow;
4936   Atom delw;
4937   Arg al[50];                     // Arg List
4938   register unsigned int ac = 0;   // Arg Count
4939 
4940   if (!change_tactical_dialog)
4941   {
4942     change_tactical_dialog =
4943       XtVaCreatePopupShell(langcode("WPUPSTI065"),
4944                            xmDialogShellWidgetClass,
4945                            appshell,
4946                            XmNdeleteResponse,XmDESTROY,
4947                            XmNdefaultPosition, FALSE,
4948                            XmNfontList, fontlist1,
4949                            NULL);
4950 
4951     pane = XtVaCreateWidget("Change Tactical pane",
4952                             xmPanedWindowWidgetClass,
4953                             change_tactical_dialog,
4954                             MY_FOREGROUND_COLOR,
4955                             MY_BACKGROUND_COLOR,
4956                             NULL);
4957 
4958     scrollwindow = XtVaCreateManagedWidget("Change Tactical scrollwindow",
4959                                            xmScrolledWindowWidgetClass,
4960                                            pane,
4961                                            XmNscrollingPolicy, XmAUTOMATIC,
4962                                            NULL);
4963 
4964     my_form =  XtVaCreateWidget("Change Tactical my_form",
4965                                 xmFormWidgetClass,
4966                                 scrollwindow,
4967                                 XmNfractionBase, 3,
4968                                 XmNautoUnmanage, FALSE,
4969                                 XmNshadowThickness, 1,
4970                                 MY_FOREGROUND_COLOR,
4971                                 MY_BACKGROUND_COLOR,
4972                                 NULL);
4973 
4974 
4975     // set args for color
4976     ac=0;
4977     XtSetArg(al[ac], XmNforeground, MY_FG_COLOR);
4978     ac++;
4979     XtSetArg(al[ac], XmNbackground, MY_BG_COLOR);
4980     ac++;
4981     XtSetArg(al[ac], XmNfontList, fontlist1);
4982     ac++;
4983 
4984     // Display the callsign or object/item name we're working on
4985     // in a label at the top of the dialog.  Otherwise we don't
4986     // know what station we're operating on.
4987     //
4988     label = XtVaCreateManagedWidget(tactical_pointer->call_sign,
4989                                     xmLabelWidgetClass,
4990                                     my_form,
4991                                     XmNtopAttachment, XmATTACH_FORM,
4992                                     XmNtopOffset, 10,
4993                                     XmNbottomAttachment, XmATTACH_NONE,
4994                                     XmNleftAttachment, XmATTACH_FORM,
4995                                     XmNleftOffset, 5,
4996                                     XmNrightAttachment, XmATTACH_NONE,
4997                                     MY_FOREGROUND_COLOR,
4998                                     MY_BACKGROUND_COLOR,
4999                                     XmNfontList, fontlist1,
5000                                     NULL);
5001 
5002     tactical_text = XtVaCreateManagedWidget("Change_Tactical text",
5003                                             xmTextWidgetClass,
5004                                             my_form,
5005                                             XmNeditable,   TRUE,
5006                                             XmNcursorPositionVisible, TRUE,
5007                                             XmNsensitive, TRUE,
5008                                             XmNshadowThickness,    1,
5009                                             XmNcolumns, MAX_TACTICAL_CALL,
5010                                             XmNwidth, ((MAX_TACTICAL_CALL*7)+2),
5011                                             XmNmaxLength, MAX_TACTICAL_CALL,
5012                                             XmNbackground, colors[0x0f],
5013                                             XmNtopOffset, 5,
5014                                             XmNtopAttachment,XmATTACH_WIDGET,
5015                                             XmNtopWidget, label,
5016                                             XmNbottomAttachment,XmATTACH_NONE,
5017                                             XmNleftAttachment, XmATTACH_FORM,
5018                                             XmNrightAttachment,XmATTACH_NONE,
5019                                             XmNnavigationType, XmTAB_GROUP,
5020                                             XmNfontList, fontlist1,
5021                                             NULL);
5022 
5023     // Fill in the current value of tactical callsign
5024     XmTextSetString(tactical_text, tactical_pointer->tactical_call_sign);
5025 
5026     button_ok = XtVaCreateManagedWidget(langcode("UNIOP00001"),
5027                                         xmPushButtonGadgetClass,
5028                                         my_form,
5029                                         XmNtopAttachment, XmATTACH_WIDGET,
5030                                         XmNtopWidget, tactical_text,
5031                                         XmNtopOffset, 5,
5032                                         XmNbottomAttachment, XmATTACH_FORM,
5033                                         XmNbottomOffset, 5,
5034                                         XmNleftAttachment, XmATTACH_POSITION,
5035                                         XmNleftPosition, 0,
5036                                         XmNrightAttachment, XmATTACH_POSITION,
5037                                         XmNrightPosition, 1,
5038                                         XmNnavigationType, XmTAB_GROUP,
5039                                         MY_FOREGROUND_COLOR,
5040                                         MY_BACKGROUND_COLOR,
5041                                         XmNfontList, fontlist1,
5042                                         NULL);
5043 
5044 
5045     button_close = XtVaCreateManagedWidget(langcode("UNIOP00003"),
5046                                            xmPushButtonGadgetClass,
5047                                            my_form,
5048                                            XmNtopAttachment, XmATTACH_WIDGET,
5049                                            XmNtopWidget, tactical_text,
5050                                            XmNtopOffset, 5,
5051                                            XmNbottomAttachment, XmATTACH_FORM,
5052                                            XmNbottomOffset, 5,
5053                                            XmNleftAttachment, XmATTACH_POSITION,
5054                                            XmNleftPosition, 2,
5055                                            XmNrightAttachment, XmATTACH_POSITION,
5056                                            XmNrightPosition, 3,
5057                                            XmNnavigationType, XmTAB_GROUP,
5058                                            MY_FOREGROUND_COLOR,
5059                                            MY_BACKGROUND_COLOR,
5060                                            XmNfontList, fontlist1,
5061                                            NULL);
5062 
5063     XtAddCallback(button_ok,
5064                   XmNactivateCallback,
5065                   Change_tactical_change_data,
5066                   change_tactical_dialog);
5067     XtAddCallback(button_close,
5068                   XmNactivateCallback,
5069                   Change_tactical_destroy_shell,
5070                   change_tactical_dialog);
5071 
5072     pos_dialog(change_tactical_dialog);
5073 
5074     delw = XmInternAtom(XtDisplay(change_tactical_dialog),"WM_DELETE_WINDOW", FALSE);
5075     XmAddWMProtocolCallback(change_tactical_dialog, delw, Change_tactical_destroy_shell, (XtPointer)change_tactical_dialog);
5076 
5077     XtManageChild(my_form);
5078     XtManageChild(pane);
5079 
5080     resize_dialog(my_form, change_tactical_dialog);
5081 
5082     XtPopup(change_tactical_dialog,XtGrabNone);
5083 
5084     // Move focus to the Close button.  This appears to
5085     // highlight the
5086     // button fine, but we're not able to hit the <Enter> key to
5087     // have that default function happen.  Note:  We _can_ hit
5088     // the
5089     // <SPACE> key, and that activates the option.
5090     //        XmUpdateDisplay(change_tactical_dialog);
5091     XmProcessTraversal(button_close, XmTRAVERSE_CURRENT);
5092 
5093   }
5094   else
5095   {
5096     (void)XRaiseWindow(XtDisplay(change_tactical_dialog),
5097                        XtWindow(change_tactical_dialog));
5098   }
5099 }
5100 
5101 
5102 
5103 
5104 
5105 /*
5106  *  Assign a tactical call to a station
5107  */
Assign_Tactical_Call(Widget w,XtPointer clientData,XtPointer UNUSED (calldata))5108 void Assign_Tactical_Call( Widget w, XtPointer clientData, XtPointer UNUSED(calldata) )
5109 {
5110   DataRow *p_station = clientData;
5111 
5112   //fprintf(stderr,"Object Name: %s\n", p_station->call_sign);
5113   tactical_pointer = p_station;
5114   Change_tactical(w, p_station, NULL);
5115 }
5116 
5117 
5118 
5119 
5120 
5121 /*
5122  *  Change the trail color for a station
5123  */
Change_trail_color(Widget UNUSED (w),XtPointer clientData,XtPointer UNUSED (calldata))5124 void Change_trail_color( Widget UNUSED(w), XtPointer clientData, XtPointer UNUSED(calldata) )
5125 {
5126   DataRow *p_station = clientData;
5127   int temp;
5128 
5129   temp = p_station->trail_color;
5130 
5131   // Increment to the next color, round-robin style
5132   temp = (temp + 1) % MAX_TRAIL_COLORS;
5133 
5134   // Test for and skip if my trail color
5135   if (temp == MY_TRAIL_COLOR)
5136   {
5137     temp = (temp + 1) % MAX_TRAIL_COLORS;
5138   }
5139 
5140   p_station->trail_color = temp;
5141 
5142   redraw_on_new_data = 2; // redraw symbols now
5143 }
5144 
5145 
5146 
5147 
5148 
PosTestExpose(Widget parent,XtPointer UNUSED (clientData),XEvent * UNUSED (event),Boolean * UNUSED (continueToDispatch))5149 static void PosTestExpose(Widget parent, XtPointer UNUSED(clientData), XEvent * UNUSED(event), Boolean * UNUSED(continueToDispatch) )
5150 {
5151   Position x, y;
5152 
5153   XtVaGetValues(parent, XmNx, &x, XmNy, &y, NULL);
5154 
5155   if (debug_level & 1)
5156   {
5157     fprintf(stderr,"Window Decoration Offsets:  X:%d\tY:%d\n", x, y);
5158   }
5159 
5160   // Store the new-found offets in global variables
5161   decoration_offset_x = (int)x;
5162   decoration_offset_y = (int)y;
5163 
5164   // Get rid of the event handler and the test dialog
5165   XtRemoveEventHandler(parent, ExposureMask, True, (XtEventHandler) PosTestExpose, (XtPointer)NULL);
5166   //    XtRemoveGrab(XtParent(parent));  // Not needed?
5167   XtDestroyWidget(XtParent(parent));
5168 }
5169 
5170 
5171 
5172 
5173 
5174 // Here's a stupid trick that we have to do in order to find out how big
5175 // window decorations are.  We need to know this information in order to
5176 // be able to kill/recreate dialogs in the same place each time.  If we
5177 // were to just get and set the X/Y values of the dialog, we would creep
5178 // across the screen by the size of the decorations each time.
5179 // I've seen it.  It's ugly.
5180 //
compute_decorations(void)5181 void compute_decorations( void )
5182 {
5183   Widget cdtest = (Widget)NULL;
5184   Widget cdform = (Widget)NULL;
5185   Cardinal n = 0;
5186   Arg args[50];
5187 
5188 
5189   // We'll create a dummy dialog at 0,0, then query its
5190   // position.  That'll give us back the position of the
5191   // widget.  Subtract 0,0 from it (easy huh?) and we get
5192   // the size of the window decorations.  Store these values
5193   // in global variables for later use.
5194 
5195   n = 0;
5196   XtSetArg(args[n], XmNx, 0);
5197   n++;
5198   XtSetArg(args[n], XmNy, 0);
5199   n++;
5200 
5201   cdtest = (Widget) XtVaCreatePopupShell("compute_decorations test",
5202                                          xmDialogShellWidgetClass,
5203                                          appshell,
5204                                          args, n,
5205                                          NULL);
5206 
5207   n = 0;
5208   XtSetArg(args[n], XmNwidth, 0);
5209   n++;    // Make it tiny
5210   XtSetArg(args[n], XmNheight, 0);
5211   n++;   // Make it tiny
5212   cdform = XmCreateForm(cdtest, "compute_decorations test form", args, n);
5213 
5214   XtAddEventHandler(cdform, ExposureMask, True, (XtEventHandler) PosTestExpose,
5215                     (XtPointer)NULL);
5216 
5217   XtManageChild(cdform);
5218   XtManageChild(cdtest);
5219 }
5220 
5221 
5222 
5223 
5224 
5225 // Enable/disable auto-update of Station_data dialog
station_data_auto_update_toggle(Widget UNUSED (widget),XtPointer UNUSED (clientData),XtPointer callData)5226 void station_data_auto_update_toggle ( Widget UNUSED(widget), XtPointer UNUSED(clientData), XtPointer callData)
5227 {
5228   XmToggleButtonCallbackStruct *state = (XmToggleButtonCallbackStruct *)callData;
5229 
5230   if(state->set)
5231   {
5232     station_data_auto_update = 1;
5233   }
5234   else
5235   {
5236     station_data_auto_update = 0;
5237   }
5238 }
5239 
5240 
5241 
5242 
5243 
5244 // Fill in the station data window with real data
station_data_fill_in(Widget w,XtPointer clientData,XtPointer calldata)5245 void station_data_fill_in ( Widget w, XtPointer clientData, XtPointer calldata )
5246 {
5247   DataRow *p_station;
5248   char *station = (char *) clientData;
5249   char temp[300];
5250   int pos, last_pos;
5251   char temp_my_distance[20];
5252   char temp_my_course[25];
5253   char temp1_my_course[20];
5254   float temp_out_C, e, humidex;
5255   long l_lat, l_lon;
5256   float value;
5257   WeatherRow *weather;
5258   time_t sec;
5259   struct tm *time;
5260   int i;
5261   int track_count = 0;
5262 
5263   // Maximum tracks listed in Station Info dialog.  This prevents
5264   // lockups on extremely long tracks.
5265 #define MAX_TRACK_LIST 50
5266 
5267 
5268   db_station_info_callsign = (char *) clientData; // Used for auto-updating this dialog
5269   temp_out_C=0;
5270   pos=0;
5271 
5272   begin_critical_section(&db_station_info_lock, "db.c:Station_data" );
5273 
5274   if (db_station_info == NULL)    // We don't have a dialog to write to
5275   {
5276 
5277     end_critical_section(&db_station_info_lock, "db.c:Station_data" );
5278 
5279     return;
5280   }
5281 
5282   if (!search_station_name(&p_station,station,1)  // Can't find call,
5283       || (p_station->flag & ST_ACTIVE) == 0)    // or found deleted objects
5284   {
5285 
5286     end_critical_section(&db_station_info_lock, "db.c:Station_data" );
5287 
5288     return;
5289   }
5290 
5291 
5292   // Clear the text
5293   XmTextSetString(si_text,NULL);
5294 
5295 
5296   // Weather Data ...
5297   if (p_station->weather_data != NULL
5298       // Make sure the timestamp on the weather is current
5299       && (int)(((sec_old + p_station->weather_data->wx_sec_time)) >= sec_now()) )
5300   {
5301 
5302     last_pos = pos;
5303 
5304     weather = p_station->weather_data;
5305 
5306     pos += strlen(temp);
5307     xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI024"),weather->wx_type,weather->wx_station);
5308     XmTextInsert(si_text,pos,temp);
5309     pos += strlen(temp);
5310     sprintf(temp, "\n");
5311     xastir_snprintf(temp, sizeof(temp), "\n");
5312     XmTextInsert(si_text,pos,temp);
5313     pos += strlen(temp);
5314     if (english_units)
5315     {
5316       xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI026"),weather->wx_course,weather->wx_speed);
5317     }
5318     else
5319     {
5320       xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI025"),weather->wx_course,(int)(atof(weather->wx_speed)*1.6094));
5321     }
5322 
5323     XmTextInsert(si_text,pos,temp);
5324     pos += strlen(temp);
5325 
5326     if (strlen(weather->wx_gust) > 0)
5327     {
5328       if (english_units)
5329       {
5330         xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI028"),weather->wx_gust);
5331       }
5332       else
5333       {
5334         xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI027"),(int)(atof(weather->wx_gust)*1.6094));
5335       }
5336 
5337       strncat(temp,
5338               "\n",
5339               sizeof(temp) - 1 - strlen(temp));
5340     }
5341     else
5342     {
5343       xastir_snprintf(temp, sizeof(temp), "\n");
5344     }
5345 
5346     XmTextInsert(si_text, pos, temp);
5347     pos += strlen(temp);
5348 
5349     if (strlen(weather->wx_temp) > 0)
5350     {
5351       if (english_units)
5352       {
5353         xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI030"),weather->wx_temp);
5354       }
5355       else
5356       {
5357         temp_out_C =(((atof(weather->wx_temp)-32)*5.0)/9.0);
5358         xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI029"),temp_out_C);
5359       }
5360       XmTextInsert(si_text,pos,temp);
5361       pos += strlen(temp);
5362     }
5363 
5364     if (strlen(weather->wx_hum) > 0)
5365     {
5366       xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI031"),weather->wx_hum);
5367       XmTextInsert(si_text,pos,temp);
5368       pos += strlen(temp);
5369     }
5370 
5371     // NOTE:  The below (Humidex) is not coded for english units, only for metric.
5372     // What is Humidex anyway?  Heat Index?  Wind Chill? --we7u
5373 
5374     // DK7IN: ??? english_units ???
5375     if (strlen(weather->wx_hum) > 0
5376         && strlen(weather->wx_temp) > 0
5377         && (!english_units) &&
5378         (atof(weather->wx_hum) > 0.0) )
5379     {
5380 
5381       e = (float)(6.112 * pow(10,(7.5 * temp_out_C)/(237.7 + temp_out_C)) * atof(weather->wx_hum) / 100.0);
5382       humidex = (temp_out_C + ((5.0/9.0) * (e-10.0)));
5383 
5384       xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI032"),humidex);
5385       XmTextInsert(si_text,pos,temp);
5386       pos += strlen(temp);
5387     }
5388 
5389     if (strlen(weather->wx_baro) > 0)
5390     {
5391       if (!english_units)    // hPa
5392       {
5393         xastir_snprintf(temp, sizeof(temp),
5394                         langcode("WPUPSTI033"),
5395                         weather->wx_baro);
5396       }
5397       else    // Inches Mercury
5398       {
5399         xastir_snprintf(temp, sizeof(temp),
5400                         langcode("WPUPSTI063"),
5401                         atof(weather->wx_baro)*0.02953);
5402       }
5403       XmTextInsert(si_text,pos,temp);
5404       pos += strlen(temp);
5405       xastir_snprintf(temp, sizeof(temp), "\n");
5406       XmTextInsert(si_text,pos,temp);
5407       pos += strlen(temp);
5408     }
5409     else
5410     {
5411       if(last_pos!=pos)
5412       {
5413         xastir_snprintf(temp, sizeof(temp), "\n");
5414         XmTextInsert(si_text,pos,temp);
5415         pos += strlen(temp);
5416       }
5417     }
5418 
5419     if (strlen(weather->wx_snow) > 0)
5420     {
5421       if(english_units)
5422       {
5423         xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI035"),atof(weather->wx_snow));
5424       }
5425       else
5426       {
5427         xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI034"),atof(weather->wx_snow)*2.54);
5428       }
5429       XmTextInsert(si_text,pos,temp);
5430       pos += strlen(temp);
5431       xastir_snprintf(temp, sizeof(temp), "\n");
5432       XmTextInsert(si_text,pos,temp);
5433       pos += strlen(temp);
5434     }
5435 
5436     if (strlen(weather->wx_rain) > 0 || strlen(weather->wx_prec_00) > 0
5437         || strlen(weather->wx_prec_24) > 0)
5438     {
5439       xastir_snprintf(temp, sizeof(temp), "%s", langcode("WPUPSTI036"));
5440       XmTextInsert(si_text,pos,temp);
5441       pos += strlen(temp);
5442     }
5443 
5444     if (strlen(weather->wx_rain) > 0)
5445     {
5446       if (english_units)
5447       {
5448         xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI038"),atof(weather->wx_rain)/100.0);
5449       }
5450       else
5451       {
5452         xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI037"),atof(weather->wx_rain)*.254);
5453       }
5454 
5455       XmTextInsert(si_text,pos,temp);
5456       pos += strlen(temp);
5457     }
5458 
5459     if (strlen(weather->wx_prec_24) > 0)
5460     {
5461       if(english_units)
5462       {
5463         xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI040"),atof(weather->wx_prec_24)/100.0);
5464       }
5465       else
5466       {
5467         xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI039"),atof(weather->wx_prec_24)*.254);
5468       }
5469 
5470       XmTextInsert(si_text,pos,temp);
5471       pos += strlen(temp);
5472     }
5473 
5474     if (strlen(weather->wx_prec_00) > 0)
5475     {
5476       if (english_units)
5477       {
5478         xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI042"),atof(weather->wx_prec_00)/100.0);
5479       }
5480       else
5481       {
5482         xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI041"),atof(weather->wx_prec_00)*.254);
5483       }
5484 
5485       XmTextInsert(si_text,pos,temp);
5486       pos += strlen(temp);
5487     }
5488 
5489     if (strlen(weather->wx_rain_total) > 0)
5490     {
5491       xastir_snprintf(temp, sizeof(temp), "\n%s",langcode("WPUPSTI046"));
5492       XmTextInsert(si_text,pos,temp);
5493       pos += strlen(temp);
5494       if (english_units)
5495       {
5496         xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI048"),atof(weather->wx_rain_total)/100.0);
5497       }
5498       else
5499       {
5500         xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI047"),atof(weather->wx_rain_total)*.254);
5501       }
5502 
5503       XmTextInsert(si_text,pos,temp);
5504       pos += strlen(temp);
5505     }
5506 
5507     // Fuel temp/moisture for RAWS weather stations
5508     if (strlen(weather->wx_fuel_temp) > 0)
5509     {
5510       if (english_units)
5511       {
5512         xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI061"),weather->wx_fuel_temp);
5513       }
5514       else
5515       {
5516         temp_out_C =(((atof(weather->wx_fuel_temp)-32)*5.0)/9.0);
5517         xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI060"),temp_out_C);
5518       }
5519       XmTextInsert(si_text,pos,temp);
5520       pos += strlen(temp);
5521     }
5522 
5523     if (strlen(weather->wx_fuel_moisture) > 0)
5524     {
5525       xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI062"),weather->wx_fuel_moisture);
5526       XmTextInsert(si_text,pos,temp);
5527       pos += strlen(temp);
5528     }
5529 
5530     xastir_snprintf(temp, sizeof(temp), "\n\n");
5531 
5532     XmTextInsert(si_text,pos,temp);
5533     pos += strlen(temp);
5534   }
5535 
5536 
5537   // Packets received ...
5538   xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI005"),p_station->num_packets);
5539   XmTextInsert(si_text,pos,temp);
5540   pos += strlen(temp);
5541 
5542   xastir_snprintf(temp,
5543                   sizeof(temp),
5544                   "%s",
5545                   p_station->packet_time);
5546   temp[2]='/';
5547   temp[3]='\0';
5548   XmTextInsert(si_text,pos,temp);
5549   pos += strlen(temp);
5550 
5551   xastir_snprintf(temp,
5552                   sizeof(temp),
5553                   "%s",
5554                   p_station->packet_time+2);
5555   temp[2]='/';
5556   temp[3]='\0';
5557   XmTextInsert(si_text,pos,temp);
5558   pos += strlen(temp);
5559 
5560   xastir_snprintf(temp,
5561                   sizeof(temp),
5562                   "%s",
5563                   p_station->packet_time+4);
5564   temp[4]=' ';
5565   temp[5]='\0';
5566   XmTextInsert(si_text,pos,temp);
5567   pos += strlen(temp);
5568 
5569   xastir_snprintf(temp,
5570                   sizeof(temp),
5571                   "%s",
5572                   p_station->packet_time+8);
5573   temp[2]=':';
5574   temp[3]='\0';
5575   XmTextInsert(si_text,pos,temp);
5576   pos += strlen(temp);
5577 
5578   xastir_snprintf(temp,
5579                   sizeof(temp),
5580                   "%s",
5581                   p_station->packet_time+10);
5582   temp[2]=':';
5583   temp[3]='\0';
5584   XmTextInsert(si_text,pos,temp);
5585   pos += strlen(temp);
5586 
5587   xastir_snprintf(temp,
5588                   sizeof(temp),
5589                   "%s",
5590                   p_station->packet_time+12);
5591   temp[2]='\n';
5592   temp[3]='\0';
5593   XmTextInsert(si_text,pos,temp);
5594   pos += strlen(temp);
5595 
5596   // Object
5597   if (strlen(p_station->origin) > 0)
5598   {
5599     xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI000"),p_station->origin);
5600     XmTextInsert(si_text,pos,temp);
5601     pos += strlen(temp);
5602     xastir_snprintf(temp, sizeof(temp), "\n");
5603     XmTextInsert(si_text,pos,temp);
5604     pos += strlen(temp);
5605   }
5606 
5607   // Print the tactical call, if any
5608   if (p_station->tactical_call_sign
5609       && p_station->tactical_call_sign[0] != '\0')
5610   {
5611     xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI065"), p_station->tactical_call_sign);
5612     XmTextInsert(si_text,pos,temp);
5613     pos += strlen(temp);
5614     xastir_snprintf(temp, sizeof(temp), "\n");
5615     XmTextInsert(si_text,pos,temp);
5616     pos += strlen(temp);
5617   }
5618 
5619   // Heard via TNC ...
5620   if ((p_station->flag & ST_VIATNC) != 0)          // test "via TNC" flag
5621   {
5622     xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI006"),p_station->heard_via_tnc_port);
5623     XmTextInsert(si_text,pos,temp);
5624     pos += strlen(temp);
5625   }
5626   else
5627   {
5628     xastir_snprintf(temp, sizeof(temp), "%s", langcode("WPUPSTI007"));
5629     XmTextInsert(si_text,pos,temp);
5630     pos += strlen(temp);
5631   }
5632 
5633   switch(p_station->data_via)
5634   {
5635     case('L'):
5636       xastir_snprintf(temp, sizeof(temp), "%s", langcode("WPUPSTI008"));
5637       break;
5638 
5639     case('T'):
5640       xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI009"),p_station->last_port_heard);
5641       break;
5642 
5643     case('I'):
5644       xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI010"),p_station->last_port_heard);
5645       break;
5646 
5647     case('F'):
5648       xastir_snprintf(temp, sizeof(temp), "%s", langcode("WPUPSTI011"));
5649       break;
5650 
5651     case(DATA_VIA_DATABASE):
5652       xastir_snprintf(temp, sizeof(temp), "last via db on interface %d",p_station->last_port_heard);
5653       break;
5654 
5655     default:
5656       xastir_snprintf(temp, sizeof(temp), "%s", langcode("WPUPSTI012"));
5657       break;
5658   }
5659   XmTextInsert(si_text,pos,temp);
5660   pos += strlen(temp);
5661 
5662   if (p_station->newest_trackpoint != NULL)
5663   {
5664     xastir_snprintf(temp, sizeof(temp), "%s", langcode("WPUPSTI013"));
5665     XmTextInsert(si_text,pos,temp);
5666     pos += strlen(temp);
5667   }
5668   xastir_snprintf(temp, sizeof(temp), "\n");
5669   XmTextInsert(si_text,pos,temp);
5670   pos += strlen(temp);
5671 
5672   // Echoed from: ...
5673   // Callsign check here includes checking SSID
5674   //    if (is_my_call(p_station->call_sign,1)) {
5675   if ( is_my_station(p_station) )
5676   {
5677     xastir_snprintf(temp, sizeof(temp), "%s", langcode("WPUPSTI055"));
5678     XmTextInsert(si_text,pos,temp);
5679     pos += strlen(temp);
5680     for (i=0; i<6; i++)
5681     {
5682       if (echo_digis[i][0] == '\0')
5683       {
5684         break;
5685       }
5686 
5687       xastir_snprintf(temp, sizeof(temp), " %s",echo_digis[i]);
5688       XmTextInsert(si_text,pos,temp);
5689       pos += strlen(temp);
5690     }
5691     xastir_snprintf(temp, sizeof(temp), "\n");
5692     XmTextInsert(si_text,pos,temp);
5693     pos += strlen(temp);
5694   }
5695 
5696   // Data Path ...
5697   if (p_station->node_path_ptr != NULL)
5698   {
5699     xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI043"),p_station->node_path_ptr);
5700   }
5701   else
5702   {
5703     xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI043"), "");
5704   }
5705 
5706   XmTextInsert(si_text,pos,temp);
5707   pos += strlen(temp);
5708   xastir_snprintf(temp, sizeof(temp), "\n");
5709   XmTextInsert(si_text,pos,temp);
5710   pos += strlen(temp);
5711 
5712   // Status ...
5713   if(p_station->status_data != NULL)     // Found at least one record
5714   {
5715     CommentRow *ptr;
5716 
5717     ptr = p_station->status_data;
5718 
5719     while (ptr != NULL)
5720     {
5721       // We don't care if the pointer is NULL.  This will
5722       // succeed anyway.  It'll just make an empty string.
5723 
5724       // Note that text_ptr may be an empty string.  That's
5725       // ok.
5726 
5727       //Also print the sec_heard timestamp.
5728       sec = ptr->sec_heard;
5729       time = localtime(&sec);
5730 
5731       xastir_snprintf(temp,
5732                       sizeof(temp),
5733                       langcode("WPUPSTI059"),
5734                       time->tm_mon + 1,
5735                       time->tm_mday,
5736                       time->tm_hour,
5737                       time->tm_min,
5738                       ptr->text_ptr);
5739       XmTextInsert(si_text,pos,temp);
5740       pos += strlen(temp);
5741 
5742       xastir_snprintf(temp, sizeof(temp), "\n");
5743 
5744       XmTextInsert(si_text,pos,temp);
5745       pos += strlen(temp);
5746       ptr = ptr->next;    // Advance to next record (if any)
5747     }
5748   }
5749 
5750 
5751 //    // Comments ...
5752 //    if(strlen(p_station->comments)>0) {
5753 //        xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI044"),p_station->comments);
5754 //        XmTextInsert(si_text,pos,temp);
5755 //        pos += strlen(temp);
5756 //        xastir_snprintf(temp, sizeof(temp), "\n");
5757 //        XmTextInsert(si_text,pos,temp);
5758 //        pos += strlen(temp);
5759 //    }
5760 
5761   // Comments ...
5762   if(p_station->comment_data != NULL)     // Found at least one record
5763   {
5764     CommentRow *ptr;
5765 
5766     ptr = p_station->comment_data;
5767 
5768     while (ptr != NULL)
5769     {
5770       // We don't care if the pointer is NULL.  This will
5771       // succeed anyway.  It'll just make an empty string.
5772 
5773       // Note that text_ptr can be an empty string.  That's
5774       // ok.
5775 
5776       //Also print the sec_heard timestamp.
5777       sec = ptr->sec_heard;
5778       time = localtime(&sec);
5779 
5780       xastir_snprintf(temp,
5781                       sizeof(temp),
5782                       langcode("WPUPSTI044"),
5783                       time->tm_mon + 1,
5784                       time->tm_mday,
5785                       time->tm_hour,
5786                       time->tm_min,
5787                       ptr->text_ptr);
5788       XmTextInsert(si_text,pos,temp);
5789       pos += strlen(temp);
5790 
5791       xastir_snprintf(temp, sizeof(temp), "\n");
5792 
5793       XmTextInsert(si_text,pos,temp);
5794       pos += strlen(temp);
5795       ptr = ptr->next;    // Advance to next record (if any)
5796     }
5797   }
5798 
5799   // Current Power Gain ...
5800   if (strlen(p_station->power_gain) == 7)
5801   {
5802     // Check for RNG instead of PHG
5803     if (p_station->power_gain[0] == 'R')
5804     {
5805       // Found a Range
5806       xastir_snprintf(temp,
5807                       sizeof(temp),
5808                       langcode("WPUPSTI067"),
5809                       atoi(&p_station->power_gain[3]));
5810     }
5811     else
5812     {
5813       // Found PHG
5814       phg_decode(langcode("WPUPSTI014"), // "Current Power Gain"
5815                  p_station->power_gain,
5816                  temp,
5817                  sizeof(temp) );
5818     }
5819 
5820     // Check for Map View symbol:  Eyeball symbol with // RNG
5821     // extension.
5822     if ( strncmp(p_station->power_gain,"RNG",3) == 0
5823          && p_station->aprs_symbol.aprs_type == '/'
5824          && p_station->aprs_symbol.aprs_symbol == 'E' )
5825     {
5826 
5827       //fprintf(stderr,"Found a Map View 'eyeball' symbol!\n");
5828 
5829       // Center_Zoom() normally fills in the values with the
5830       // current zoom/center for the map window.  We want to
5831       // be able to override these with our own values in this
5832       // case, derived from the object info.
5833       center_zoom_override++;
5834       Center_Zoom(w,NULL,(XtPointer)p_station);
5835     }
5836   }
5837   else if (p_station->flag & (ST_OBJECT | ST_ITEM))
5838   {
5839     xastir_snprintf(temp,
5840                     sizeof(temp),
5841                     "%s %s",
5842                     langcode("WPUPSTI014"), // "Current Power Gain:"
5843                     langcode("WPUPSTI068") );   // "none"
5844   }
5845   else if (english_units)
5846   {
5847     xastir_snprintf(temp,
5848                     sizeof(temp),
5849                     "%s %s (9W @ 20ft %s, 3dB %s, %s 6.2mi)",
5850                     langcode("WPUPSTI014"), // "Current Power Gain:"
5851                     langcode("WPUPSTI069"), // "default"
5852                     langcode("WPUPSTI070"), // "HAAT"
5853                     langcode("WPUPSTI071"), // "omni"
5854                     langcode("WPUPSTI072") ); // "range"
5855     //          "default (9W @ 20ft HAAT, 3dB omni, range 6.2mi)");
5856   }
5857   else
5858   {
5859     xastir_snprintf(temp,
5860                     sizeof(temp),
5861                     "%s %s (9W @ 6.1m %s, 3dB %s, %s 10.0km)",
5862                     langcode("WPUPSTI014"), // "Current Power Gain:"
5863                     langcode("WPUPSTI069"), // "default"
5864                     langcode("WPUPSTI070"), // "HAAT"
5865                     langcode("WPUPSTI071"), // "omni"
5866                     langcode("WPUPSTI072") ); // "range"
5867     //          "default (9W @ 6.1m HAAT, 3dB omni, range 10.0km)");
5868 
5869   }
5870 
5871   XmTextInsert(si_text,pos,temp);
5872   pos += strlen(temp);
5873   xastir_snprintf(temp, sizeof(temp), "\n");
5874   XmTextInsert(si_text,pos,temp);
5875   pos += strlen(temp);
5876 
5877   // Current DF Info ...
5878   if (strlen(p_station->signal_gain) == 7)
5879   {
5880     shg_decode(langcode("WPUPSTI057"), p_station->signal_gain, temp, sizeof(temp) );
5881     XmTextInsert(si_text,pos,temp);
5882     pos += strlen(temp);
5883     xastir_snprintf(temp, sizeof(temp), "\n");
5884     XmTextInsert(si_text,pos,temp);
5885     pos += strlen(temp);
5886   }
5887   if (strlen(p_station->bearing) == 3)
5888   {
5889     bearing_decode(langcode("WPUPSTI058"), p_station->bearing, p_station->NRQ, temp, sizeof(temp) );
5890     XmTextInsert(si_text,pos,temp);
5891     pos += strlen(temp);
5892     xastir_snprintf(temp, sizeof(temp), "\n");
5893     XmTextInsert(si_text,pos,temp);
5894     pos += strlen(temp);
5895   }
5896 
5897   // Signpost Data
5898   if (strlen(p_station->signpost) > 0)
5899   {
5900     xastir_snprintf(temp, sizeof(temp), "%s: %s",langcode("POPUPOB029"), p_station->signpost);
5901     XmTextInsert(si_text,pos,temp);
5902     pos += strlen(temp);
5903     xastir_snprintf(temp, sizeof(temp), "\n");
5904     XmTextInsert(si_text,pos,temp);
5905     pos += strlen(temp);
5906   }
5907 
5908   // Altitude ...
5909   last_pos=pos;
5910   if (strlen(p_station->altitude) > 0)
5911   {
5912     if (english_units)
5913     {
5914       xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI016"),atof(p_station->altitude)*3.28084,"ft");
5915     }
5916     else
5917     {
5918       xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI016"),atof(p_station->altitude),"m");
5919     }
5920 
5921     XmTextInsert(si_text,pos,temp);
5922     pos += strlen(temp);
5923   }
5924 
5925   // Course ...
5926   if (strlen(p_station->course) > 0)
5927   {
5928     xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI017"),p_station->course);
5929     XmTextInsert(si_text,pos,temp);
5930     pos += strlen(temp);
5931   }
5932 
5933   // Speed ...
5934   if (strlen(p_station->speed) > 0)
5935   {
5936     if (english_units)
5937     {
5938       xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI019"),atof(p_station->speed)*1.1508);
5939     }
5940 
5941     else
5942     {
5943       xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI018"),atof(p_station->speed)*1.852);
5944     }
5945 
5946     XmTextInsert(si_text,pos,temp);
5947     pos += strlen(temp);
5948   }
5949 
5950   if (last_pos!=pos)
5951   {
5952     xastir_snprintf(temp, sizeof(temp), "\n");
5953     XmTextInsert(si_text,pos,temp);
5954     pos += strlen(temp);
5955   }
5956 
5957   // Distance ...
5958   last_pos = pos;
5959 
5960   // do my course
5961   //    if (!is_my_call(p_station->call_sign,1)) { // Checks SSID as well
5962   if ( !(is_my_station(p_station)) )   // Checks SSID as well
5963   {
5964 
5965     l_lat = convert_lat_s2l(my_lat);
5966     l_lon = convert_lon_s2l(my_long);
5967 
5968     // Get distance in nautical miles.
5969     value = (float)calc_distance_course(l_lat,l_lon,p_station->coord_lat,
5970                                         p_station->coord_lon,temp1_my_course,sizeof(temp1_my_course));
5971 
5972     // n7tap: This is a quick hack to get some more useful values for
5973     //        distance to near ojects.
5974     if (english_units)
5975     {
5976       if (value*1.15078 < 0.99)
5977       {
5978         xastir_snprintf(temp_my_distance,
5979                         sizeof(temp_my_distance),
5980                         "%d %s",
5981                         (int)(value*1.15078*1760),
5982                         langcode("SPCHSTR004"));    // yards
5983       }
5984       else
5985       {
5986         xastir_snprintf(temp_my_distance,
5987                         sizeof(temp_my_distance),
5988                         langcode("WPUPSTI020"),     // miles
5989                         value*1.15078);
5990       }
5991     }
5992     else
5993     {
5994       if (value*1.852 < 0.99)
5995       {
5996         xastir_snprintf(temp_my_distance,
5997                         sizeof(temp_my_distance),
5998                         "%d %s",
5999                         (int)(value*1.852*1000),
6000                         langcode("UNIOP00031"));    // 'm' as in meters
6001       }
6002       else
6003       {
6004         xastir_snprintf(temp_my_distance,
6005                         sizeof(temp_my_distance),
6006                         langcode("WPUPSTI021"),     // km
6007                         value*1.852);
6008       }
6009     }
6010     xastir_snprintf(temp_my_course, sizeof(temp_my_course), "%s\xB0",temp1_my_course);
6011     xastir_snprintf(temp, sizeof(temp), langcode("WPUPSTI022"),temp_my_distance,temp_my_course);
6012     XmTextInsert(si_text,pos,temp);
6013     pos += strlen(temp);
6014   }
6015 
6016   if(last_pos!=pos)
6017   {
6018     xastir_snprintf(temp, sizeof(temp), "\n");
6019     XmTextInsert(si_text,pos,temp);
6020     pos += strlen(temp);
6021   }
6022 
6023   // Last Position
6024   sec  = p_station->sec_heard;
6025   time = localtime(&sec);
6026   xastir_snprintf(temp, sizeof(temp), "%s%02d/%02d  %02d:%02d   ",langcode("WPUPSTI023"),
6027                   time->tm_mon + 1, time->tm_mday,time->tm_hour,time->tm_min);
6028   XmTextInsert(si_text,pos,temp);
6029   pos += strlen(temp);
6030 
6031   if (coordinate_system == USE_UTM
6032       || coordinate_system == USE_UTM_SPECIAL)
6033   {
6034     convert_xastir_to_UTM_str(temp, sizeof(temp),
6035                               p_station->coord_lon, p_station->coord_lat);
6036     XmTextInsert(si_text,pos,temp);
6037     pos += strlen(temp);
6038   }
6039   else if (coordinate_system == USE_MGRS)
6040   {
6041     convert_xastir_to_MGRS_str(temp,
6042                                sizeof(temp),
6043                                p_station->coord_lon,
6044                                p_station->coord_lat,
6045                                0);
6046     XmTextInsert(si_text,pos,temp);
6047     pos += strlen(temp);
6048   }
6049   else
6050   {
6051     if (coordinate_system == USE_DDDDDD)
6052     {
6053       convert_lat_l2s(p_station->coord_lat, temp, sizeof(temp), CONVERT_DEC_DEG);
6054     }
6055     else if (coordinate_system == USE_DDMMSS)
6056     {
6057       convert_lat_l2s(p_station->coord_lat, temp, sizeof(temp), CONVERT_DMS_NORMAL);
6058     }
6059     else    // Assume coordinate_system == USE_DDMMMM
6060     {
6061       convert_lat_l2s(p_station->coord_lat, temp, sizeof(temp), CONVERT_HP_NORMAL);
6062     }
6063     XmTextInsert(si_text,pos,temp);
6064     pos += strlen(temp);
6065 
6066     xastir_snprintf(temp, sizeof(temp), "  ");
6067     XmTextInsert(si_text,pos,temp);
6068     pos += strlen(temp);
6069 
6070     if (coordinate_system == USE_DDDDDD)
6071     {
6072       convert_lon_l2s(p_station->coord_lon, temp, sizeof(temp), CONVERT_DEC_DEG);
6073     }
6074     else if (coordinate_system == USE_DDMMSS)
6075     {
6076       convert_lon_l2s(p_station->coord_lon, temp, sizeof(temp), CONVERT_DMS_NORMAL);
6077     }
6078     else    // Assume coordinate_system == USE_DDMMMM
6079     {
6080       convert_lon_l2s(p_station->coord_lon, temp, sizeof(temp), CONVERT_HP_NORMAL);
6081     }
6082     XmTextInsert(si_text,pos,temp);
6083     pos += strlen(temp);
6084   }
6085 
6086   if (p_station->altitude[0] != '\0')
6087   {
6088     xastir_snprintf(temp, sizeof(temp), " %5.0f%s", atof(p_station->altitude)*cvt_m2len, un_alt);
6089   }
6090   else
6091   {
6092     substr(temp,"        ",1+5+strlen(un_alt));
6093   }
6094   XmTextInsert(si_text,pos,temp);
6095   pos += strlen(temp);
6096 
6097   if (p_station->speed[0] != '\0')
6098   {
6099     xastir_snprintf(temp, sizeof(temp), " %4.0f%s",atof(p_station->speed)*cvt_kn2len,un_spd);
6100   }
6101   else
6102   {
6103     substr(temp,"         ",1+4+strlen(un_spd));
6104   }
6105   XmTextInsert(si_text,pos,temp);
6106   pos += strlen(temp);
6107 
6108   if (p_station->course[0] != '\0')
6109   {
6110     xastir_snprintf(temp, sizeof(temp), " %3d\xB0",atoi(p_station->course));
6111   }
6112   else
6113   {
6114     xastir_snprintf(temp, sizeof(temp), "     ");
6115   }
6116 
6117   XmTextInsert(si_text,pos,temp);
6118   pos += strlen(temp);
6119 
6120   // dl9sau
6121   // Maidenhead Grid Locator
6122   xastir_snprintf(temp, sizeof(temp), "  %s", sec_to_loc(p_station->coord_lon, p_station->coord_lat) );
6123   XmTextInsert(si_text,pos,temp);
6124   pos += strlen(temp);
6125 
6126   if ((p_station->flag & ST_DIRECT) != 0)
6127   {
6128     xastir_snprintf(temp, sizeof(temp), " *\n");
6129   }
6130 
6131   else
6132   {
6133     xastir_snprintf(temp, sizeof(temp), "  \n");
6134   }
6135 
6136   XmTextInsert(si_text,pos,temp);
6137   pos += strlen(temp);
6138 
6139   // list rest of trail data
6140   if (p_station->newest_trackpoint != NULL)
6141   {
6142     TrackRow *ptr;
6143 
6144     ptr = p_station->newest_trackpoint;
6145 
6146     // Skip the first (latest) trackpoint as if it exists, it'll
6147     // be the same as the data in the station record, which we
6148     // just printed out.
6149     if (ptr->prev != NULL)
6150     {
6151       ptr = ptr->prev;
6152     }
6153 
6154     while ( (ptr != NULL) && (track_count <= MAX_TRACK_LIST) )
6155     {
6156 
6157       track_count++;
6158 
6159       sec  = ptr->sec;
6160       time = localtime(&sec);
6161       if ((ptr->flag & TR_NEWTRK) != '\0')
6162         xastir_snprintf(temp, sizeof(temp), "            +  %02d/%02d  %02d:%02d   ",
6163                         time->tm_mon + 1,time->tm_mday,time->tm_hour,time->tm_min);
6164       else
6165         xastir_snprintf(temp, sizeof(temp), "               %02d/%02d  %02d:%02d   ",
6166                         time->tm_mon + 1,time->tm_mday,time->tm_hour,time->tm_min);
6167 
6168       XmTextInsert(si_text,pos,temp);
6169       pos += strlen(temp);
6170 
6171       if (coordinate_system == USE_UTM
6172           || coordinate_system == USE_UTM_SPECIAL)
6173       {
6174         convert_xastir_to_UTM_str(temp, sizeof(temp),
6175                                   ptr->trail_long_pos,
6176                                   ptr->trail_lat_pos);
6177         XmTextInsert(si_text,pos,temp);
6178         pos += strlen(temp);
6179       }
6180       else if (coordinate_system == USE_MGRS)
6181       {
6182         convert_xastir_to_MGRS_str(temp,
6183                                    sizeof(temp),
6184                                    ptr->trail_long_pos,
6185                                    ptr->trail_lat_pos,
6186                                    0);
6187         XmTextInsert(si_text,pos,temp);
6188         pos += strlen(temp);
6189       }
6190       else
6191       {
6192         if (coordinate_system == USE_DDDDDD)
6193         {
6194           convert_lat_l2s(ptr->trail_lat_pos,
6195                           temp,
6196                           sizeof(temp),
6197                           CONVERT_DEC_DEG);
6198         }
6199         else if (coordinate_system == USE_DDMMSS)
6200         {
6201           convert_lat_l2s(ptr->trail_lat_pos,
6202                           temp,
6203                           sizeof(temp),
6204                           CONVERT_DMS_NORMAL);
6205         }
6206         else    // Assume coordinate_system == USE_DDMMMM
6207         {
6208           convert_lat_l2s(ptr->trail_lat_pos,
6209                           temp,
6210                           sizeof(temp),
6211                           CONVERT_HP_NORMAL);
6212         }
6213         XmTextInsert(si_text,pos,temp);
6214         pos += strlen(temp);
6215 
6216         xastir_snprintf(temp, sizeof(temp), "  ");
6217         XmTextInsert(si_text,pos,temp);
6218         pos += strlen(temp);
6219 
6220         if (coordinate_system == USE_DDDDDD)
6221         {
6222           convert_lon_l2s(ptr->trail_long_pos,
6223                           temp,
6224                           sizeof(temp),
6225                           CONVERT_DEC_DEG);
6226         }
6227         else if (coordinate_system == USE_DDMMSS)
6228         {
6229           convert_lon_l2s(ptr->trail_long_pos,
6230                           temp,
6231                           sizeof(temp),
6232                           CONVERT_DMS_NORMAL);
6233         }
6234         else    // Assume coordinate_system == USE_DDMMMM
6235         {
6236           convert_lon_l2s(ptr->trail_long_pos,
6237                           temp,
6238                           sizeof(temp),
6239                           CONVERT_HP_NORMAL);
6240         }
6241         XmTextInsert(si_text,pos,temp);
6242         pos += strlen(temp);
6243       }
6244 
6245       if (ptr->altitude > -99999l)
6246         xastir_snprintf(temp, sizeof(temp), " %5.0f%s",
6247                         ptr->altitude * cvt_dm2len,
6248                         un_alt);
6249       else
6250       {
6251         substr(temp,"         ",1+5+strlen(un_alt));
6252       }
6253 
6254       XmTextInsert(si_text,pos,temp);
6255       pos += strlen(temp);
6256 
6257       if (ptr->speed >= 0)
6258         xastir_snprintf(temp, sizeof(temp), " %4.0f%s",
6259                         ptr->speed * cvt_hm2len,
6260                         un_spd);
6261       else
6262       {
6263         substr(temp,"         ",1+4+strlen(un_spd));
6264       }
6265 
6266       XmTextInsert(si_text,pos,temp);
6267       pos += strlen(temp);
6268 
6269       if (ptr->course >= 0)
6270         xastir_snprintf(temp, sizeof(temp), " %3d\xB0",
6271                         ptr->course);
6272       else
6273       {
6274         xastir_snprintf(temp, sizeof(temp), "     ");
6275       }
6276 
6277       XmTextInsert(si_text,pos,temp);
6278       pos += strlen(temp);
6279 
6280       // dl9sau
6281       xastir_snprintf(temp, sizeof(temp), "  %s",
6282                       sec_to_loc(ptr->trail_long_pos,
6283                                  ptr->trail_lat_pos) );
6284       XmTextInsert(si_text,pos,temp);
6285       pos += strlen(temp);
6286 
6287       if ((ptr->flag & TR_LOCAL) != '\0')
6288       {
6289         xastir_snprintf(temp, sizeof(temp), " *\n");
6290       }
6291       else
6292       {
6293         xastir_snprintf(temp, sizeof(temp), "  \n");
6294       }
6295 
6296       XmTextInsert(si_text,pos,temp);
6297       pos += strlen(temp);
6298 
6299       // Go back in time one trackpoint
6300       ptr = ptr->prev;
6301     }
6302   }
6303 
6304 
6305   if (fcc_lookup_pushed)
6306   {
6307     Station_data_add_fcc(w, clientData, calldata);
6308   }
6309   else if (rac_lookup_pushed)
6310   {
6311     Station_data_add_rac(w, clientData, calldata);
6312   }
6313 
6314   XmTextShowPosition(si_text,0);
6315 
6316   end_critical_section(&db_station_info_lock, "db.c:Station_data" );
6317 
6318 }
6319 
6320 
6321 
6322 
6323 
6324 /*
6325  * Track from Station_data
6326  *
6327  * Called by Station_data function below from the Track Station
6328  * button in Station Info.
6329  */
Track_from_Station_data(Widget UNUSED (w),XtPointer clientData,XtPointer UNUSED (calldata))6330 void Track_from_Station_data(Widget UNUSED(w), XtPointer clientData, XtPointer UNUSED(calldata) )
6331 {
6332   DataRow *p_station = clientData;
6333 
6334   if (p_station->call_sign[0] != '\0')
6335   {
6336     xastir_snprintf(tracking_station_call,
6337                     sizeof(tracking_station_call),
6338                     "%s",
6339                     p_station->call_sign);
6340     track_station_on = 1;
6341   }
6342   else
6343   {
6344     tracking_station_call[0] = '\0';
6345   }
6346 }
6347 
6348 /*
6349  * Clear DF from Station_data
6350  *
6351  * Called by Station_data function below from the Clear DF Bearing
6352  * button in Station Info.
6353  */
Clear_DF_from_Station_data(Widget UNUSED (w),XtPointer clientData,XtPointer UNUSED (calldata))6354 void Clear_DF_from_Station_data(Widget UNUSED(w), XtPointer clientData, XtPointer UNUSED(calldata) )
6355 {
6356   DataRow *p_station = clientData;
6357 
6358   if (strlen(p_station->bearing) == 3)
6359   {
6360     // we have DF data to clear
6361     p_station->bearing[0]='\0';
6362     p_station->NRQ[0]='\0';
6363   }
6364 }
6365 
6366 
6367 
6368 
6369 
6370 /*
6371  *  List station info and trail
6372  *  If calldata is non-NULL, then we drop straight through to the
6373  *  Modify->Object or Assign_Tactical_Call dialogs.
6374  *
6375  * Input parameters:
6376  *     clientData:  Station callsign
6377  *
6378  *     calldata: NULL = Station Info
6379  *               "1"  = Object -> Modify
6380  *               "2"  = Move Object
6381  *               "3"  = Assign Tactical Call
6382  *               "4"  = Send Message To
6383  *
6384  */
Station_data(Widget w,XtPointer clientData,XtPointer calldata)6385 void Station_data(Widget w, XtPointer clientData, XtPointer calldata)
6386 {
6387   DataRow *p_station;
6388   char *station = (char *) clientData;
6389   static char local_station[25];
6390   char temp[300];
6391   unsigned int n;
6392   Atom delw;
6393   static Widget  pane, form, button_cancel, button_message,
6394          button_nws, button_fcc, button_rac, button_clear_track,
6395          button_trace, button_messages, button_object_modify,
6396          button_direct, button_version, station_icon, station_call,
6397          station_type, station_data_auto_update_w,
6398          button_tactical, button_change_trail_color,
6399          button_track_station,button_clear_df,scrollwindow;
6400   Arg args[50];
6401   Pixmap icon;
6402   Position x,y;    // For saving current dialog position
6403 
6404 
6405   //fprintf(stderr,"db.c:Station_data start\n");
6406 
6407   busy_cursor(appshell);
6408 
6409   db_station_info_callsign = (char *) clientData; // Used for auto-updating this dialog
6410 
6411 
6412   // Make a copy of the name.
6413   xastir_snprintf(local_station,sizeof(local_station),"%s",station);
6414 
6415   if (search_station_name(&p_station,station,1)   // find call
6416       && (p_station->flag & ST_ACTIVE) != 0)      // ignore deleted objects
6417   {
6418   }
6419   else
6420   {
6421     fprintf(stderr,"Couldn't find station in database\n");
6422     return; // Don't update current/create new dialog
6423   }
6424 
6425 
6426   if (calldata != NULL)   // We were called from the
6427   {
6428     // Object->Modify, Assign Tactical Call,
6429     // or Send Message To menu items.
6430     if (strncmp(calldata,"1",1) == 0)
6431     {
6432       Modify_object(w, (XtPointer)p_station, calldata);
6433     }
6434     else if (strncmp(calldata,"2",1) == 0)
6435     {
6436       Modify_object(w, (XtPointer)p_station, calldata);
6437     }
6438     else if (strncmp(calldata,"3",1) == 0)
6439     {
6440       Assign_Tactical_Call(w, (XtPointer)p_station, calldata);
6441     }
6442     else if (strncmp(calldata,"4",1) == 0)
6443     {
6444       //fprintf(stderr,"Send Message To: %s\n", p_station->call_sign);
6445       Send_message_call(NULL, (XtPointer) p_station->call_sign, NULL);
6446     }
6447     return;
6448   }
6449 
6450 
6451   // If we haven't calculated our decoration offsets yet, do so now
6452   if ( (decoration_offset_x == 0) && (decoration_offset_y == 0) )
6453   {
6454     compute_decorations();
6455   }
6456 
6457   if (db_station_info != NULL)    // We already have a dialog
6458   {
6459 
6460     // This is a pain.  We can get the X/Y position, but when
6461     // we restore the new dialog to the same position we're
6462     // off by the width/height of our window decorations.  Call
6463     // above was added to pre-compute the offsets that we'll need.
6464     XtVaGetValues(db_station_info, XmNx, &x, XmNy, &y, NULL);
6465 
6466     // This call doesn't work.  It returns the widget location,
6467     // just like the XtVaGetValues call does.  I need the window
6468     // decoration location instead.
6469     //XtTranslateCoords(db_station_info, 0, 0, &xnew, &ynew);
6470     //fprintf(stderr,"%d:%d\t%d:%d\n", x, xnew, y, ynew);
6471 
6472     if (last_station_info_x == 0)
6473     {
6474       last_station_info_x = x - decoration_offset_x;
6475     }
6476 
6477     if (last_station_info_y == 0)
6478     {
6479       last_station_info_y = y - decoration_offset_y;
6480     }
6481 
6482     // Now get rid of the old dialog
6483     Station_data_destroy_shell(db_station_info, db_station_info, NULL);
6484   }
6485   else
6486   {
6487     // Clear the global state variables
6488     fcc_lookup_pushed = 0;
6489     rac_lookup_pushed = 0;
6490   }
6491 
6492 
6493   begin_critical_section(&db_station_info_lock, "db.c:Station_data" );
6494 
6495 
6496   if (db_station_info == NULL)
6497   {
6498     // Start building the dialog from the bottom up.  That way
6499     // we can keep the buttons attached to the bottom of the
6500     // form and the correct height, and let the text widget
6501     // grow/shrink as the dialog is resized.
6502 
6503     db_station_info = XtVaCreatePopupShell(langcode("WPUPSTI001"),
6504                                            xmDialogShellWidgetClass, appshell,
6505                                            XmNdeleteResponse, XmDESTROY,
6506                                            XmNdefaultPosition, FALSE,
6507                                            XmNfontList, fontlist1,
6508                                            NULL);
6509 
6510     pane = XtVaCreateWidget("Station Data pane",
6511                             xmPanedWindowWidgetClass, db_station_info,
6512                             XmNbackground, colors[0xff],
6513                             NULL);
6514 
6515     scrollwindow = XtVaCreateManagedWidget("State Data scrollwindow",
6516                                            xmScrolledWindowWidgetClass,
6517                                            pane,
6518                                            XmNscrollingPolicy, XmAUTOMATIC,
6519                                            NULL);
6520 
6521     form =  XtVaCreateWidget("Station Data form",
6522                              xmFormWidgetClass,
6523                              scrollwindow,
6524                              XmNfractionBase, 4,
6525                              XmNbackground, colors[0xff],
6526                              XmNautoUnmanage, FALSE,
6527                              XmNshadowThickness, 1,
6528                              NULL);
6529 
6530 
6531     // Start with the bottom row, left button
6532 
6533 
6534     button_clear_track = NULL;  // Need this later, don't delete!
6535     if (p_station->newest_trackpoint != NULL)
6536     {
6537       // [ Clear Track ]
6538       button_clear_track = XtVaCreateManagedWidget(langcode("WPUPSTI045"),xmPushButtonGadgetClass, form,
6539                            XmNtopAttachment, XmATTACH_NONE,
6540                            XmNbottomAttachment, XmATTACH_FORM,
6541                            XmNbottomOffset,5,
6542                            XmNleftAttachment, XmATTACH_FORM,
6543                            XmNleftOffset,5,
6544                            XmNrightAttachment, XmATTACH_POSITION,
6545                            XmNrightPosition, 1,
6546                            XmNbackground, colors[0xff],
6547                            XmNnavigationType, XmTAB_GROUP,
6548                            XmNfontList, fontlist1,
6549                            NULL);
6550       XtAddCallback(button_clear_track, XmNactivateCallback, Station_data_destroy_track,(XtPointer)p_station);
6551 
6552     }
6553     else
6554     {
6555       // DK7IN: I drop the version button for mobile stations
6556       // we just have too much buttons...
6557       // and should find another solution
6558       // [ Station Version Query ]
6559       button_version = XtVaCreateManagedWidget(langcode("WPUPSTI052"),xmPushButtonGadgetClass, form,
6560                        XmNtopAttachment, XmATTACH_NONE,
6561                        XmNbottomAttachment, XmATTACH_FORM,
6562                        XmNbottomOffset,5,
6563                        XmNleftAttachment, XmATTACH_FORM,
6564                        XmNleftOffset,5,
6565                        XmNrightAttachment, XmATTACH_POSITION,
6566                        XmNrightPosition, 1,
6567                        XmNbackground, colors[0xff],
6568                        XmNnavigationType, XmTAB_GROUP,
6569                        XmNfontList, fontlist1,
6570                        NULL);
6571       XtAddCallback(button_version, XmNactivateCallback, Station_query_version,(XtPointer)p_station->call_sign);
6572     }
6573 
6574     // [ Trace Query ]
6575     button_trace = XtVaCreateManagedWidget(langcode("WPUPSTI049"),xmPushButtonGadgetClass, form,
6576                                            XmNtopAttachment, XmATTACH_NONE,
6577                                            XmNbottomAttachment, XmATTACH_FORM,
6578                                            XmNbottomOffset,5,
6579                                            XmNleftAttachment, XmATTACH_POSITION,
6580                                            XmNleftPosition, 1,
6581                                            XmNrightAttachment, XmATTACH_POSITION,
6582                                            XmNrightPosition, 2,
6583                                            XmNbackground, colors[0xff],
6584                                            XmNnavigationType, XmTAB_GROUP,
6585                                            XmNfontList, fontlist1,
6586                                            NULL);
6587     XtAddCallback(button_trace, XmNactivateCallback, Station_query_trace,(XtPointer)p_station->call_sign);
6588 
6589     // [ Un-Acked Messages Query ]
6590     button_messages = XtVaCreateManagedWidget(langcode("WPUPSTI050"),xmPushButtonGadgetClass, form,
6591                       XmNtopAttachment, XmATTACH_NONE,
6592                       XmNbottomAttachment, XmATTACH_FORM,
6593                       XmNbottomOffset,5,
6594                       XmNleftAttachment, XmATTACH_POSITION,
6595                       XmNleftPosition, 2,
6596                       XmNrightAttachment, XmATTACH_POSITION,
6597                       XmNrightPosition, 3,
6598                       XmNbackground, colors[0xff],
6599                       XmNnavigationType, XmTAB_GROUP,
6600                       XmNfontList, fontlist1,
6601                       NULL);
6602     XtAddCallback(button_messages, XmNactivateCallback, Station_query_messages,(XtPointer)p_station->call_sign);
6603 
6604     // [ Direct Stations Query ]
6605     button_direct = XtVaCreateManagedWidget(langcode("WPUPSTI051"),xmPushButtonGadgetClass, form,
6606                                             XmNtopAttachment, XmATTACH_NONE,
6607                                             XmNbottomAttachment, XmATTACH_FORM,
6608                                             XmNbottomOffset,5,
6609                                             XmNleftAttachment, XmATTACH_POSITION,
6610                                             XmNleftPosition, 3,
6611                                             XmNrightAttachment, XmATTACH_POSITION,
6612                                             XmNrightPosition, 4,
6613                                             XmNbackground, colors[0xff],
6614                                             XmNnavigationType, XmTAB_GROUP,
6615                                             XmNfontList, fontlist1,
6616                                             NULL);
6617     XtAddCallback(button_direct, XmNactivateCallback, Station_query_direct,(XtPointer)p_station->call_sign);
6618 
6619 
6620     // Now proceed to the row above it, left button first
6621 
6622 
6623     // [ Store Track ] or single Position
6624     button_store_track = XtVaCreateManagedWidget(langcode("WPUPSTI054"),xmPushButtonGadgetClass, form,
6625                          XmNtopAttachment, XmATTACH_NONE,
6626                          //XmNtopWidget,XtParent(si_text),
6627                          XmNbottomAttachment, XmATTACH_WIDGET,
6628                          XmNbottomWidget, (button_clear_track) ? button_clear_track : button_version,
6629                          XmNbottomOffset, 1,
6630                          XmNleftAttachment, XmATTACH_FORM,
6631                          XmNleftOffset,5,
6632                          XmNrightAttachment, XmATTACH_POSITION,
6633                          XmNrightPosition, 1,
6634                          XmNbackground, colors[0xff],
6635                          XmNnavigationType, XmTAB_GROUP,
6636                          XmNfontList, fontlist1,
6637                          NULL);
6638     XtAddCallback(button_store_track,   XmNactivateCallback, Station_data_store_track,(XtPointer)p_station);
6639 
6640     if ( ((p_station->flag & ST_OBJECT) == 0) && ((p_station->flag & ST_ITEM) == 0) )   // Not an object/
6641     {
6642       // fprintf(stderr,"Not an object or item...\n");
6643       // [Send Message]
6644       button_message = XtVaCreateManagedWidget(langcode("WPUPSTI002"),xmPushButtonGadgetClass, form,
6645                        XmNtopAttachment, XmATTACH_NONE,
6646                        XmNbottomAttachment, XmATTACH_WIDGET,
6647                        XmNbottomWidget, button_trace,
6648                        XmNbottomOffset, 1,
6649                        XmNleftAttachment, XmATTACH_POSITION,
6650                        XmNleftPosition, 1,
6651                        XmNrightAttachment, XmATTACH_POSITION,
6652                        XmNrightPosition, 2,
6653                        XmNbackground, colors[0xff],
6654                        XmNnavigationType, XmTAB_GROUP,
6655                        XmNfontList, fontlist1,
6656                        NULL);
6657       XtAddCallback(button_message, XmNactivateCallback, Send_message_call,(XtPointer)p_station->call_sign);
6658     }
6659     else
6660     {
6661       // fprintf(stderr,"Found an object or item...\n");
6662       button_object_modify = XtVaCreateManagedWidget(langcode("WPUPSTI053"),xmPushButtonGadgetClass, form,
6663                              XmNtopAttachment, XmATTACH_NONE,
6664                              XmNbottomAttachment, XmATTACH_WIDGET,
6665                              XmNbottomWidget, button_trace,
6666                              XmNbottomOffset, 1,
6667                              XmNleftAttachment, XmATTACH_POSITION,
6668                              XmNleftPosition, 1,
6669                              XmNrightAttachment, XmATTACH_POSITION,
6670                              XmNrightPosition, 2,
6671                              XmNbackground, colors[0xff],
6672                              XmNnavigationType, XmTAB_GROUP,
6673                              XmNfontList, fontlist1,
6674                              NULL);
6675       XtAddCallback(button_object_modify,
6676                     XmNactivateCallback,
6677                     Modify_object,
6678                     (XtPointer)p_station);
6679     }
6680 
6681 
6682     // Check whether it is a non-weather alert object/item.  If
6683     // so, try to use the origin callsign instead of the object
6684     // for FCC/RAC lookups.
6685     //
6686     if ( (p_station->flag & ST_OBJECT) || (p_station->flag & ST_ITEM) )
6687     {
6688 
6689       // It turns out that objects transmitted by a station
6690       // called "WINLINK" are what mess up the RAC button for
6691       // Canadian stations.  Xastir sees the 'W' of WINLINK
6692       // (the originating station) and assumes it is a U.S.
6693       // station.  Here's a sample packet:
6694       //
6695       // WINLINK>APWL2K,TCPIP*,qAC,T2MIDWEST:;VE7SEP-10*240521z4826.2 NW12322.5 Wa145.690MHz 1200b R11m RMSPacket EMCOMM
6696       //
6697       // If match on "WINLINK":  Don't copy origin callsign
6698       // into local_station.  Use the object name instead
6699       // which should be a callsign.
6700       if (strncmp(p_station->origin,"WINLINK",7))
6701       {
6702         xastir_snprintf(local_station,sizeof(local_station),"%s",p_station->origin);
6703       }
6704     }
6705 
6706 
6707     // Add "Fetch NWS Info" button if it is an object or item
6708     // and has "WXSVR" in its path somewhere.
6709     //
6710     // Note from Dale Huguley:
6711     //   "I would say an object with 6 upper alpha chars for the
6712     //   "from" call and " {AAAAA" (space curly 5 alphanumerics)
6713     //   at the end is almost guaranteed to be from Wxsvr.
6714     //   Fingering for the six alphas and the first three
6715     //   characters after the curly brace should be a reliable
6716     //   finger - as in SEWSVR>APRS::a_bunch_of_info_in_here_
6717     //   {H45AA finger SEWSVRH45@wxsvr.net"
6718     //
6719     // Note from Curt:  I had to remove the space from the
6720     // search as well, 'cuz the multipoint objects don't have
6721     // the space before the final curly-brace.
6722     //
6723     if ( ( (p_station->flag & ST_OBJECT) || (p_station->flag & ST_ITEM) )
6724          && (p_station->comment_data != NULL)
6725          && ( strstr(p_station->comment_data->text_ptr, "{") != NULL ) )
6726     {
6727 
6728       static char temp[25];
6729       char *ptr3;
6730 
6731 
6732       button_nws = XtVaCreateManagedWidget(langcode("WPUPSTI064"),xmPushButtonGadgetClass, form,
6733                                            XmNtopAttachment, XmATTACH_NONE,
6734                                            XmNbottomAttachment, XmATTACH_WIDGET,
6735                                            XmNbottomWidget, button_messages,
6736                                            XmNbottomOffset, 1,
6737                                            XmNleftAttachment, XmATTACH_POSITION,
6738                                            XmNleftPosition, 2,
6739                                            XmNrightAttachment, XmATTACH_POSITION,
6740                                            XmNrightPosition, 3,
6741                                            XmNbackground, colors[0xff],
6742                                            XmNnavigationType, XmTAB_GROUP,
6743                                            XmNfontList, fontlist1,
6744                                            NULL);
6745 
6746       // We need to contruct the "special" finger address.
6747       // We'll use the FROM callsign and the first three chars
6748       // of the curly-brace portion of the comment field.
6749       // Callsign in this case is from the "origin" field.
6750       // The curly-brace text is at the end of one of the
6751       // "comment_data" records, hopefully the first one
6752       // checked (most recent).
6753       //
6754 
6755       xastir_snprintf(temp,
6756                       sizeof(temp),
6757                       "%s",
6758                       p_station->origin);
6759       temp[6] = '\0';
6760       ptr3 = strstr(p_station->comment_data->text_ptr,"{");
6761       ptr3++; // Skip over the '{' character
6762       strncat(temp,ptr3,3);
6763 
6764       //fprintf(stderr,"New Handle: %s\n", temp);
6765 
6766       XtAddCallback(button_nws,
6767                     XmNactivateCallback,
6768                     Station_data_wx_alert,
6769                     (XtPointer)temp);
6770     }
6771 
6772 
6773     // Add FCC button only if probable match.  The U.S. has
6774     // these prefixes assigned but not all are used for amateur
6775     // stations:
6776     //
6777     //   AAA-ALZ
6778     //   KAA-KZZ
6779     //   NAA-NZZ
6780     //   WAA-WZZ
6781     //
6782     else if ((! strncmp(local_station,"A",1)) || (!  strncmp(local_station,"K",1)) ||
6783              (! strncmp(local_station,"N",1)) || (! strncmp(local_station,"W",1))  )
6784     {
6785 
6786       button_fcc = XtVaCreateManagedWidget(langcode("WPUPSTI003"),xmPushButtonGadgetClass, form,
6787                                            XmNtopAttachment, XmATTACH_NONE,
6788                                            XmNbottomAttachment, XmATTACH_WIDGET,
6789                                            XmNbottomWidget, button_messages,
6790                                            XmNbottomOffset, 1,
6791                                            XmNleftAttachment, XmATTACH_POSITION,
6792                                            XmNleftPosition, 2,
6793                                            XmNrightAttachment, XmATTACH_POSITION,
6794                                            XmNrightPosition, 3,
6795                                            XmNbackground, colors[0xff],
6796                                            XmNnavigationType, XmTAB_GROUP,
6797                                            XmNfontList, fontlist1,
6798                                            NULL);
6799       XtAddCallback(button_fcc,
6800                     XmNactivateCallback,
6801                     Station_data_add_fcc,
6802                     (XtPointer)local_station);
6803 
6804       if ( ! check_fcc_data())
6805       {
6806         XtSetSensitive(button_fcc,FALSE);
6807       }
6808     }
6809 
6810 
6811     // Add RAC button only if probable match.  Canada has these
6812     // prefixes assigned but not all are used for amateur
6813     // stations:
6814     //
6815     //   CFA-CKZ
6816     //   CYA-CZZ
6817     //   VAA-VGZ
6818     //   VOA-VOZ
6819     //   VXA-VYZ
6820     //   XJA-XOZ
6821     //
6822     else if (!strncmp(local_station,"VA",2) || !strncmp(local_station,"VE",2) || !strncmp(local_station,"VO",2) || !strncmp(local_station,"VY",2))
6823     {
6824       button_rac = XtVaCreateManagedWidget(langcode("WPUPSTI004"),xmPushButtonGadgetClass, form,
6825                                            XmNtopAttachment, XmATTACH_NONE,
6826                                            XmNbottomAttachment, XmATTACH_WIDGET,
6827                                            XmNbottomWidget, button_messages,
6828                                            XmNbottomOffset, 1,
6829                                            XmNleftAttachment, XmATTACH_POSITION,
6830                                            XmNleftPosition, 2,
6831                                            XmNrightAttachment, XmATTACH_POSITION,
6832                                            XmNrightPosition, 3,
6833                                            XmNbackground, colors[0xff],
6834                                            XmNnavigationType, XmTAB_GROUP,
6835                                            XmNfontList, fontlist1,
6836                                            NULL);
6837       XtAddCallback(button_rac,
6838                     XmNactivateCallback,
6839                     Station_data_add_rac,
6840                     (XtPointer)local_station);
6841 
6842       if ( ! check_rac_data())
6843       {
6844         XtSetSensitive(button_rac,FALSE);
6845       }
6846     }
6847 
6848     button_cancel = XtVaCreateManagedWidget(langcode("UNIOP00003"),xmPushButtonGadgetClass, form,
6849                                             XmNtopAttachment, XmATTACH_NONE,
6850                                             XmNbottomAttachment, XmATTACH_WIDGET,
6851                                             XmNbottomWidget, button_direct,
6852                                             XmNbottomOffset, 1,
6853                                             XmNleftAttachment, XmATTACH_POSITION,
6854                                             XmNleftPosition, 3,
6855                                             XmNrightAttachment, XmATTACH_POSITION,
6856                                             XmNrightPosition, 4,
6857                                             XmNrightOffset, 5,
6858                                             XmNbackground, colors[0xff],
6859                                             XmNnavigationType, XmTAB_GROUP,
6860                                             XmNfontList, fontlist1,
6861                                             NULL);
6862     XtAddCallback(button_cancel, XmNactivateCallback, Station_data_destroy_shell, db_station_info);
6863 
6864     // Button to clear DF bearing data if we actually have some.
6865     if (strlen(p_station->bearing) == 3)
6866     {
6867       button_clear_df = XtVaCreateManagedWidget(langcode("WPUPSTI092"),xmPushButtonGadgetClass, form,
6868                         XmNtopAttachment, XmATTACH_NONE,
6869                         XmNbottomAttachment, XmATTACH_WIDGET,
6870                         XmNbottomWidget, button_cancel,
6871                         XmNbottomOffset, 1,
6872                         XmNleftAttachment, XmATTACH_POSITION,
6873                         XmNleftPosition, 3,
6874                         XmNrightAttachment, XmATTACH_POSITION,
6875                         XmNrightPosition, 4,
6876                         XmNrightOffset, 5,
6877                         XmNbackground, colors[0xff],
6878                         XmNnavigationType, XmTAB_GROUP,
6879                         XmNfontList, fontlist1,
6880                         NULL);
6881       XtAddCallback(button_clear_df, XmNactivateCallback,Clear_DF_from_Station_data, (XtPointer)p_station);
6882     }
6883 
6884     button_track_station = XtVaCreateManagedWidget(langcode("WPUPTSP001"),xmPushButtonGadgetClass, form,
6885                            XmNtopAttachment, XmATTACH_NONE,
6886                            XmNbottomAttachment, XmATTACH_WIDGET,
6887                            XmNbottomWidget, button_store_track,
6888                            XmNbottomOffset, 1,
6889                            XmNleftAttachment, XmATTACH_POSITION,
6890                            XmNleftPosition, 0,
6891                            XmNleftOffset,5,
6892                            XmNrightAttachment, XmATTACH_POSITION,
6893                            XmNrightPosition, 1,
6894                            //                            XmNrightOffset, 5,
6895                            XmNbackground, colors[0xff],
6896                            XmNnavigationType, XmTAB_GROUP,
6897                            XmNfontList, fontlist1,
6898                            NULL);
6899     XtAddCallback(button_track_station, XmNactivateCallback,Track_from_Station_data, (XtPointer)p_station);
6900 
6901 
6902 
6903     // Now build from the top of the dialog to the buttons.
6904 
6905 
6906     icon = XCreatePixmap(XtDisplay(appshell),RootWindowOfScreen(XtScreen(appshell)),
6907                          20,20,DefaultDepthOfScreen(XtScreen(appshell)));
6908 
6909     symbol(db_station_info,0,p_station->aprs_symbol.aprs_type,
6910            p_station->aprs_symbol.aprs_symbol,
6911            p_station->aprs_symbol.special_overlay,icon,0,0,0,' ');
6912 
6913     station_icon = XtVaCreateManagedWidget("Station Data icon", xmLabelWidgetClass, form,
6914                                            XmNtopAttachment, XmATTACH_FORM,
6915                                            XmNtopOffset, 2,
6916                                            XmNbottomAttachment, XmATTACH_NONE,
6917                                            XmNleftAttachment, XmATTACH_FORM,
6918                                            XmNleftOffset, 5,
6919                                            XmNrightAttachment, XmATTACH_NONE,
6920                                            XmNlabelType, XmPIXMAP,
6921                                            XmNlabelPixmap,icon,
6922                                            XmNbackground, colors[0xff],
6923                                            XmNfontList, fontlist1,
6924                                            NULL);
6925 
6926     station_type = XtVaCreateManagedWidget("Station Data type", xmTextFieldWidgetClass, form,
6927                                            XmNeditable,   FALSE,
6928                                            XmNcursorPositionVisible, FALSE,
6929                                            XmNtraversalOn, FALSE,
6930                                            XmNshadowThickness,       0,
6931                                            XmNcolumns,5,
6932                                            XmNwidth,((5*7)+2),
6933                                            XmNbackground, colors[0xff],
6934                                            XmNtopAttachment,XmATTACH_FORM,
6935                                            XmNtopOffset, 2,
6936                                            XmNbottomAttachment,XmATTACH_NONE,
6937                                            XmNleftAttachment, XmATTACH_WIDGET,
6938                                            XmNleftWidget,station_icon,
6939                                            XmNleftOffset,10,
6940                                            XmNrightAttachment,XmATTACH_NONE,
6941                                            XmNfontList, fontlist1,
6942                                            NULL);
6943 
6944     xastir_snprintf(temp, sizeof(temp), "%c%c%c", p_station->aprs_symbol.aprs_type,
6945                     p_station->aprs_symbol.aprs_symbol,
6946                     p_station->aprs_symbol.special_overlay);
6947 
6948     XmTextFieldSetString(station_type, temp);
6949     XtManageChild(station_type);
6950 
6951     station_call = XtVaCreateManagedWidget("Station Data call", xmTextFieldWidgetClass, form,
6952                                            XmNeditable,   FALSE,
6953                                            XmNcursorPositionVisible, FALSE,
6954                                            XmNtraversalOn, FALSE,
6955                                            XmNshadowThickness,       0,
6956                                            XmNcolumns,10,
6957                                            XmNwidth,((10*7)+2),
6958                                            XmNbackground, colors[0xff],
6959                                            XmNtopAttachment,XmATTACH_FORM,
6960                                            XmNtopOffset, 2,
6961                                            XmNbottomAttachment,XmATTACH_NONE,
6962                                            XmNleftAttachment, XmATTACH_WIDGET,
6963                                            XmNleftWidget, station_type,
6964                                            XmNleftOffset,10,
6965                                            XmNrightAttachment,XmATTACH_NONE,
6966                                            XmNfontList, fontlist1,
6967                                            NULL);
6968 
6969     XmTextFieldSetString(station_call,p_station->call_sign);
6970     XtManageChild(station_call);
6971 
6972     station_data_auto_update_w = XtVaCreateManagedWidget(langcode("WPUPSTI056"),
6973                                  xmToggleButtonGadgetClass, form,
6974                                  XmNtopAttachment,XmATTACH_FORM,
6975                                  XmNtopOffset, 2,
6976                                  XmNbottomAttachment,XmATTACH_NONE,
6977                                  XmNleftAttachment, XmATTACH_WIDGET,
6978                                  XmNleftWidget, station_call,
6979                                  XmNleftOffset,10,
6980                                  XmNrightAttachment,XmATTACH_NONE,
6981                                  XmNbackground,colors[0xff],
6982                                  XmNfontList, fontlist1,
6983                                  NULL);
6984     XtAddCallback(station_data_auto_update_w,XmNvalueChangedCallback,station_data_auto_update_toggle,"1");
6985 
6986     //Add tactical button at the top/right
6987     // "Assign Tactical Call"
6988     button_tactical = XtVaCreateManagedWidget(langcode("WPUPSTI066"),xmPushButtonGadgetClass, form,
6989                       XmNtopAttachment, XmATTACH_FORM,
6990                       XmNtopOffset, 5,
6991                       XmNbottomAttachment, XmATTACH_NONE,
6992                       XmNleftAttachment, XmATTACH_WIDGET,
6993                       XmNleftOffset, 10,
6994                       XmNleftWidget, station_data_auto_update_w,
6995                       XmNrightAttachment, XmATTACH_NONE,
6996                       XmNbackground, colors[0xff],
6997                       XmNnavigationType, XmTAB_GROUP,
6998                       XmNfontList, fontlist1,
6999                       NULL);
7000     XtAddCallback(button_tactical,
7001                   XmNactivateCallback,
7002                   Assign_Tactical_Call,
7003                   (XtPointer)p_station);
7004     if (p_station->flag & (ST_OBJECT | ST_ITEM))
7005     {
7006       // We don't allow setting tac-calls for objects/items,
7007       // so make the button insensitive.
7008       XtSetSensitive(button_tactical,FALSE);
7009     }
7010 
7011     //Add change_trail_color button at the top/right
7012     // "Trail Color"
7013     button_change_trail_color = XtVaCreateManagedWidget(langcode("WPUPSTI091"),
7014                                 xmPushButtonGadgetClass, form,
7015                                 XmNtopAttachment, XmATTACH_FORM,
7016                                 XmNtopOffset, 5,
7017                                 XmNbottomAttachment, XmATTACH_NONE,
7018                                 XmNleftAttachment, XmATTACH_WIDGET,
7019                                 XmNleftOffset, 10,
7020                                 XmNleftWidget, button_tactical,
7021                                 XmNrightAttachment, XmATTACH_NONE,
7022                                 XmNbackground, colors[0xff],
7023                                 XmNnavigationType, XmTAB_GROUP,
7024                                 XmNfontList, fontlist1,
7025                                 NULL);
7026     XtAddCallback(button_change_trail_color,
7027                   XmNactivateCallback,
7028                   Change_trail_color,
7029                   (XtPointer)p_station);
7030 
7031     n=0;
7032     XtSetArg(args[n], XmNrows, 15);
7033     n++;
7034     XtSetArg(args[n], XmNcolumns, 100);
7035     n++;
7036     XtSetArg(args[n], XmNeditable, FALSE);
7037     n++;
7038     XtSetArg(args[n], XmNtraversalOn, FALSE);
7039     n++;
7040     XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT);
7041     n++;
7042     XtSetArg(args[n], XmNwordWrap, TRUE);
7043     n++;
7044     XtSetArg(args[n], XmNbackground, colors[0xff]);
7045     n++;
7046     XtSetArg(args[n], XmNscrollHorizontal, FALSE);
7047     n++;
7048     XtSetArg(args[n], XmNcursorPositionVisible, FALSE);
7049     n++;
7050     XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET);
7051     n++;
7052     XtSetArg(args[n], XmNtopWidget, station_icon);
7053     n++;
7054     XtSetArg(args[n], XmNtopOffset, 5);
7055     n++;
7056     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET);
7057     n++;
7058     //        XtSetArg(args[n], XmNbottomWidget, button_store_track); n++;
7059     XtSetArg(args[n], XmNbottomWidget, button_track_station);
7060     n++;
7061     XtSetArg(args[n], XmNbottomOffset, 1);
7062     n++;
7063     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM);
7064     n++;
7065     XtSetArg(args[n], XmNleftOffset, 5);
7066     n++;
7067     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM);
7068     n++;
7069     XtSetArg(args[n], XmNrightOffset, 5);
7070     n++;
7071     XtSetArg(args[n], XmNfontList, fontlist1);
7072     n++;
7073 
7074     si_text = NULL;
7075     si_text = XmCreateScrolledText(form,"Station_data",args,n);
7076 
7077     end_critical_section(&db_station_info_lock, "db.c:Station_data" );
7078 
7079     // Fill in the si_text widget with real data
7080     station_data_fill_in( w, (XtPointer)db_station_info_callsign, NULL);
7081 
7082     begin_critical_section(&db_station_info_lock, "db.c:Station_data" );
7083 
7084     pos_dialog(db_station_info);
7085 
7086     delw = XmInternAtom(XtDisplay(db_station_info),"WM_DELETE_WINDOW", FALSE);
7087     XmAddWMProtocolCallback(db_station_info, delw, Station_data_destroy_shell, (XtPointer)db_station_info);
7088 
7089     XtManageChild(form);
7090     XtManageChild(si_text);
7091     XtVaSetValues(si_text, XmNbackground, colors[0x0f], NULL);
7092     XtManageChild(pane);
7093 
7094     resize_dialog(form, db_station_info);
7095 
7096     if (station_data_auto_update)
7097     {
7098       XmToggleButtonSetState(station_data_auto_update_w,TRUE,FALSE);
7099     }
7100 
7101     if (calldata == NULL)   // We're not going straight to the Modify dialog
7102     {
7103       // and will actually use the dialog we just drew
7104 
7105       XtPopup(db_station_info,XtGrabNone);
7106 
7107       XmTextShowPosition(si_text,0);
7108 
7109       // Move focus to the Cancel button.  This appears to highlight the
7110       // button fine, but we're not able to hit the <Enter> key to
7111       // have that default function happen.  Note:  We _can_ hit the
7112       // <SPACE> key, and that activates the option.
7113       XmProcessTraversal(button_cancel, XmTRAVERSE_CURRENT);
7114     }
7115   }
7116 
7117   end_critical_section(&db_station_info_lock, "db.c:Station_data" );
7118 
7119 }
7120 
7121 
7122 
7123 
7124 
7125 // Used for auto-refreshing the Station_info dialog.  Called from
7126 // main.c:UpdateTime() every 30 seconds.
7127 //
update_station_info(Widget w)7128 void update_station_info(Widget w)
7129 {
7130 
7131   begin_critical_section(&db_station_info_lock, "db.c:update_station_info" );
7132 
7133   // If we have a dialog to update and a callsign to pass to it
7134   if (( db_station_info != NULL)
7135       && (db_station_info_callsign != NULL)
7136       && (strlen(db_station_info_callsign) != 0) )
7137   {
7138 
7139     end_critical_section(&db_station_info_lock, "db.c:update_station_info" );
7140 
7141     // Fill in the si_text widget with real data
7142     station_data_fill_in( w, (XtPointer)db_station_info_callsign, NULL);
7143   }
7144   else
7145   {
7146 
7147     end_critical_section(&db_station_info_lock, "db.c:update_station_info" );
7148 
7149   }
7150 }
7151 
7152 
7153 
7154 
7155 
7156 /*
7157  *  Station Info Selection PopUp window: Canceled
7158  */
Station_info_destroy_shell(Widget UNUSED (widget),XtPointer clientData,XtPointer UNUSED (callData))7159 void Station_info_destroy_shell(Widget UNUSED(widget), XtPointer clientData, XtPointer UNUSED(callData) )
7160 {
7161   Widget shell = (Widget) clientData;
7162 
7163   // We used to close the detailed Station Info dialog here too, which
7164   // makes no sense.  Commenting this out so that we can close the
7165   // Station Chooser but leave the Station Info dialog open.
7166   //
7167   //    if (db_station_info!=NULL)
7168   //        Station_data_destroy_shell(db_station_info, db_station_info, NULL);
7169 
7170   XtPopdown(shell);
7171   (void)XFreePixmap(XtDisplay(appshell),SiS_icon0);
7172   (void)XFreePixmap(XtDisplay(appshell),SiS_icon);
7173 
7174   begin_critical_section(&db_station_popup_lock, "db.c:Station_info_destroy_shell" );
7175 
7176   XtDestroyWidget(shell);
7177   db_station_popup = (Widget)NULL;
7178 
7179   end_critical_section(&db_station_popup_lock, "db.c:Station_info_destroy_shell" );
7180 
7181 }
7182 
7183 
7184 
7185 
7186 
7187 // Global parameter so that we can pass another value to the below
7188 // function from the Station_info() function.  We need to be able to
7189 // pass this value off to the Station_data() function for special
7190 // operations like moves, where objects are on top of each other.
7191 //
7192 XtPointer station_info_select_global = NULL;
7193 
7194 
7195 
7196 
7197 
7198 /*
7199  *  Station Info Selection PopUp window: Quit with selected station
7200  */
Station_info_select_destroy_shell(Widget widget,XtPointer UNUSED (clientData),XtPointer UNUSED (callData))7201 void Station_info_select_destroy_shell(Widget widget, XtPointer UNUSED(clientData), XtPointer UNUSED(callData) )
7202 {
7203   int i,x;
7204   char *temp;
7205   char temp2[50];
7206   XmString *list;
7207   int found;
7208   //Widget shell = (Widget) clientData;
7209 
7210   found=0;
7211 
7212   begin_critical_section(&db_station_popup_lock, "db.c:Station_info_select_destroy_shell" );
7213 
7214   if (db_station_popup)
7215   {
7216     XtVaGetValues(station_list,XmNitemCount,&i,XmNitems,&list,NULL);
7217 
7218     for (x=1; x<=i; x++)
7219     {
7220       if (XmListPosSelected(station_list,x))
7221       {
7222         found=1;
7223         if (XmStringGetLtoR(list[(x-1)],XmFONTLIST_DEFAULT_TAG,&temp))
7224         {
7225           x=i+1;
7226         }
7227       }
7228     }
7229 
7230     // DK7IN ?? should we not first close the PopUp, then call Station_data ??
7231     if (found)
7232     {
7233       xastir_snprintf(temp2, sizeof(temp2), "%s", temp);
7234       // Only keep the station info, remove Tactical Call Sign
7235       temp2[strcspn(temp2, "(")] = '\0';
7236       remove_trailing_spaces(temp2);
7237 
7238       // Call it with the global parameter at the last, so we
7239       // can pass special parameters down that we couldn't
7240       // directly pass to Station_info_select_destroy_shell().
7241       Station_data(widget, temp2, station_info_select_global);
7242 
7243       // Clear the global variable so that nothing else calls
7244       // it with the wrong parameter
7245       station_info_select_global = NULL;
7246 
7247       XtFree(temp);
7248     }
7249     /*
7250     // This section of code gets rid of the Station Chooser.  Frank wanted to
7251     // be able to leave the Station Chooser up after selection so that other
7252     // stations could be selected, therefore I commented this out.
7253     XtPopdown(shell);                   // Get rid of the station chooser popup here
7254     (void)XFreePixmap(XtDisplay(appshell),SiS_icon0);
7255     (void)XFreePixmap(XtDisplay(appshell),SiS_icon);
7256     XtDestroyWidget(shell);             // and here
7257     db_station_popup = (Widget)NULL;    // and here
7258     */
7259   }
7260 
7261   end_critical_section(&db_station_popup_lock, "db.c:Station_info_select_destroy_shell" );
7262 
7263 }
7264 
7265 
7266 
7267 
7268 
7269 /*
7270  *  Station Info PopUp
7271  *  if only one station in view it shows the data with Station_data()
7272  *  otherwise we get a selection box
7273  *  clientData will be non-null if we wish to drop through to the object->modify
7274  *  or Assign Tactical Call dialogs.
7275  *
7276  * clientData: NULL = Station Info
7277  *             "1"  = Object -> Modify
7278  *             "2"  = Move Object
7279  *             "3"  = Assign Tactical Call
7280  *             "4"  = Send Message To
7281  */
Station_info(Widget w,XtPointer clientData,XtPointer UNUSED (calldata))7282 void Station_info(Widget w, XtPointer clientData, XtPointer UNUSED(calldata) )
7283 {
7284   DataRow *p_station;
7285   DataRow *p_found;
7286   int num_found = 0;
7287   unsigned long min_diff_x, diff_x, min_diff_y, diff_y;
7288   XmString str_ptr;
7289   unsigned int n;
7290   Atom delw;
7291   static Widget pane, form, button_ok, button_cancel;
7292   Arg al[50];                    /* Arg List */
7293   register unsigned int ac = 0;           /* Arg Count */
7294   char tactical_string[50];
7295 
7296 
7297   busy_cursor(appshell);
7298 
7299   min_diff_y = scale_y * 20;  // Pixels each way in y-direction.
7300   min_diff_x = scale_x * 20;  // Pixels each way in x-direction.
7301   p_found = NULL;
7302   p_station = n_first;
7303 
7304   // Here we just count them.  We go through the same type of code
7305   // again later if we find more than one station.
7306   while (p_station != NULL)      // search through database for nearby stations
7307   {
7308 
7309     if ( ( (p_station->flag & ST_INVIEW) != 0)
7310          && ok_to_draw_station(p_station) )   // only test stations in view
7311     {
7312 
7313       if (!altnet || is_altnet(p_station))
7314       {
7315 
7316         // Here we calculate diff in terms of XX pixels,
7317         // changed into lat/long values.  This keeps the
7318         // affected rectangle the same at any zoom level.
7319         // scale_y/scale_x is Xastir units/pixel.  Xastir
7320         // units are in 1/100 of a second.  If we want to go
7321         // 10 pixels in any direction (roughly, scale_x
7322         // varies by latitude), then we want (10 * scale_y),
7323         // and (10 * scale_x) if we want to make a very
7324         // accurate square.
7325 
7326         diff_y = (unsigned long)( labs((NW_corner_latitude+(menu_y*scale_y))
7327                                        - p_station->coord_lat));
7328 
7329         diff_x = (unsigned long)( labs((NW_corner_longitude+(menu_x*scale_x))
7330                                        - p_station->coord_lon));
7331 
7332         // If the station fits within our bounding box,
7333         // count it
7334         if ((diff_y < min_diff_y) && (diff_x < min_diff_x))
7335         {
7336           p_found = p_station;
7337           num_found++;
7338         }
7339       }
7340     }
7341     p_station = p_station->n_next;
7342   }
7343 
7344   if (p_found != NULL)    // We found at least one station
7345   {
7346 
7347     if (num_found == 1)
7348     {
7349       // We only found one station, so it's easy
7350       Station_data(w,p_found->call_sign,clientData);
7351     }
7352     else    // We found more: create dialog to choose a station
7353     {
7354 
7355       // Set up this global variable so that we can pass it
7356       // off to Station_data from the
7357       // Station_info_select_destroy_shell() function above.
7358       // Without this global variable we don't have enough
7359       // parameters passed to the final routine, so we can't
7360       // move an object that is on top of another.  With it,
7361       // we can.
7362       station_info_select_global = clientData;
7363 
7364       if (db_station_popup != NULL)
7365       {
7366         Station_info_destroy_shell(db_station_popup, db_station_popup, NULL);
7367       }
7368 
7369       begin_critical_section(&db_station_popup_lock, "db.c:Station_info" );
7370 
7371       if (db_station_popup == NULL)
7372       {
7373         // Set up a selection box:
7374         db_station_popup = XtVaCreatePopupShell(langcode("STCHO00001"),
7375                                                 xmDialogShellWidgetClass, appshell,
7376                                                 XmNdeleteResponse, XmDESTROY,
7377                                                 XmNdefaultPosition, FALSE,
7378                                                 XmNbackground, colors[0xff],
7379                                                 XmNfontList, fontlist1,
7380                                                 NULL);
7381 
7382         pane = XtVaCreateWidget("Station_info pane",xmPanedWindowWidgetClass, db_station_popup,
7383                                 XmNbackground, colors[0xff],
7384                                 NULL);
7385 
7386         form =  XtVaCreateWidget("Station_info form",xmFormWidgetClass, pane,
7387                                  XmNfractionBase, 5,
7388                                  XmNbackground, colors[0xff],
7389                                  XmNautoUnmanage, FALSE,
7390                                  XmNshadowThickness, 1,
7391                                  NULL);
7392 
7393         // Attach buttons first to the bottom of the form,
7394         // so that we'll be able to stretch this thing
7395         // vertically to see all the callsigns.
7396         //
7397         button_ok = XtVaCreateManagedWidget("Info",xmPushButtonGadgetClass, form,
7398                                             XmNtopAttachment, XmATTACH_NONE,
7399                                             XmNbottomAttachment, XmATTACH_FORM,
7400                                             XmNbottomOffset, 5,
7401                                             XmNleftAttachment, XmATTACH_POSITION,
7402                                             XmNleftPosition, 1,
7403                                             XmNrightAttachment, XmATTACH_POSITION,
7404                                             XmNrightPosition, 2,
7405                                             XmNnavigationType, XmTAB_GROUP,
7406                                             XmNfontList, fontlist1,
7407                                             NULL);
7408 
7409         button_cancel = XtVaCreateManagedWidget(langcode("UNIOP00003"),xmPushButtonGadgetClass, form,
7410                                                 XmNtopAttachment, XmATTACH_NONE,
7411                                                 XmNbottomAttachment, XmATTACH_FORM,
7412                                                 XmNbottomOffset, 5,
7413                                                 XmNleftAttachment, XmATTACH_POSITION,
7414                                                 XmNleftPosition, 3,
7415                                                 XmNrightAttachment, XmATTACH_POSITION,
7416                                                 XmNrightPosition, 4,
7417                                                 XmNnavigationType, XmTAB_GROUP,
7418                                                 XmNfontList, fontlist1,
7419                                                 NULL);
7420 
7421         XtAddCallback(button_cancel, XmNactivateCallback, Station_info_destroy_shell, db_station_popup);
7422         XtAddCallback(button_ok, XmNactivateCallback, Station_info_select_destroy_shell, db_station_popup);
7423 
7424 
7425         /*set args for color */
7426         ac = 0;
7427         XtSetArg(al[ac], XmNbackground, colors[0xff]);
7428         ac++;
7429         XtSetArg(al[ac], XmNvisibleItemCount, 6);
7430         ac++;
7431         XtSetArg(al[ac], XmNtraversalOn, TRUE);
7432         ac++;
7433         XtSetArg(al[ac], XmNshadowThickness, 3);
7434         ac++;
7435         XtSetArg(al[ac], XmNselectionPolicy, XmSINGLE_SELECT);
7436         ac++;
7437         XtSetArg(al[ac], XmNscrollBarPlacement, XmBOTTOM_RIGHT);
7438         ac++;
7439         XtSetArg(al[ac], XmNtopAttachment, XmATTACH_FORM);
7440         ac++;
7441         XtSetArg(al[ac], XmNtopOffset, 5);
7442         ac++;
7443         XtSetArg(al[ac], XmNbottomAttachment, XmATTACH_WIDGET);
7444         ac++;
7445         XtSetArg(al[ac], XmNbottomWidget, button_ok);
7446         ac++;
7447         XtSetArg(al[ac], XmNbottomOffset, 5);
7448         ac++;
7449         XtSetArg(al[ac], XmNrightAttachment, XmATTACH_FORM);
7450         ac++;
7451         XtSetArg(al[ac], XmNrightOffset, 5);
7452         ac++;
7453         XtSetArg(al[ac], XmNleftAttachment, XmATTACH_FORM);
7454         ac++;
7455         XtSetArg(al[ac], XmNleftOffset, 5);
7456         ac++;
7457         XtSetArg(al[ac], XmNfontList, fontlist1);
7458         ac++;
7459 
7460         station_list = XmCreateScrolledList(form,"Station_info list",al,ac);
7461 
7462 // DK7IN: I want to add the symbol in front of the call...
7463         // icon
7464         SiS_icon0 = XCreatePixmap(XtDisplay(appshell),RootWindowOfScreen(XtScreen(appshell)),20,20,
7465                                   DefaultDepthOfScreen(XtScreen(appshell)));
7466         SiS_icon  = XCreatePixmap(XtDisplay(appshell),RootWindowOfScreen(XtScreen(appshell)),20,20,
7467                                   DefaultDepthOfScreen(XtScreen(appshell)));
7468         /*      SiS_symb  = XtVaCreateManagedWidget("Station_info icon", xmLabelWidgetClass, ob_form1,
7469                                     XmNlabelType,               XmPIXMAP,
7470                                     XmNlabelPixmap,             SiS_icon,
7471                                     XmNbackground,              colors[0xff],
7472                                     XmNleftAttachment,          XmATTACH_FORM,
7473                                     XmNtopAttachment,           XmATTACH_FORM,
7474                                     XmNbottomAttachment,        XmATTACH_NONE,
7475                                     XmNrightAttachment,         XmATTACH_NONE,
7476                                     NULL);
7477         */
7478 
7479         /*fprintf(stderr,"What station\n");*/
7480         n = 1;
7481         p_station = n_first;
7482         while (p_station != NULL)      // search through database for nearby stations
7483         {
7484 
7485           if ( ( (p_station->flag & ST_INVIEW) != 0)
7486                && ok_to_draw_station(p_station) )   // only test stations in view
7487           {
7488 
7489             if (!altnet || is_altnet(p_station))
7490             {
7491 
7492               diff_y = (unsigned long)( labs((NW_corner_latitude+(menu_y*scale_y))
7493                                              - p_station->coord_lat));
7494 
7495               diff_x = (unsigned long)( labs((NW_corner_longitude+(menu_x*scale_x))
7496                                              - p_station->coord_lon));
7497 
7498               // If the station fits within our
7499               // bounding box, count it.
7500               if ((diff_y < min_diff_y) && (diff_x < min_diff_x))
7501               {
7502                 /*fprintf(stderr,"Station %s\n",p_station->call_sign);*/
7503                 if (p_station->tactical_call_sign)
7504                 {
7505                   xastir_snprintf(tactical_string, sizeof(tactical_string), "%s (%s)", p_station->call_sign,
7506                                   p_station->tactical_call_sign);
7507                   XmListAddItem(station_list, str_ptr = XmStringCreateLtoR(tactical_string,
7508                                                         XmFONTLIST_DEFAULT_TAG), (int)n++);
7509                 }
7510                 else
7511                 {
7512                   XmListAddItem(station_list, str_ptr = XmStringCreateLtoR(p_station->call_sign,
7513                                                         XmFONTLIST_DEFAULT_TAG), (int)n++);
7514                 }
7515                 XmStringFree(str_ptr);
7516               }
7517             }
7518           }
7519           p_station = p_station->n_next;
7520         }
7521 
7522 
7523         pos_dialog(db_station_popup);
7524 
7525         delw = XmInternAtom(XtDisplay(db_station_popup),"WM_DELETE_WINDOW", FALSE);
7526         XmAddWMProtocolCallback(db_station_popup, delw, Station_info_destroy_shell, (XtPointer)db_station_popup);
7527 
7528         XtManageChild(form);
7529         XtManageChild(station_list);
7530         XtVaSetValues(station_list, XmNbackground, colors[0x0f], NULL);
7531         XtManageChild(pane);
7532 
7533         XtPopup(db_station_popup,XtGrabNone);
7534 
7535         // Move focus to the Cancel button.  This appears to highlight the
7536         // button fine, but we're not able to hit the <Enter> key to
7537         // have that default function happen.  Note:  We _can_ hit the
7538         // <SPACE> key, and that activates the option.
7539         XmProcessTraversal(button_cancel, XmTRAVERSE_CURRENT);
7540 
7541       }
7542 
7543       end_critical_section(&db_station_popup_lock, "db.c:Station_info" );
7544 
7545     }
7546   }
7547 }
7548 
7549 
7550 
7551 
7552 
heard_via_tnc_in_past_hour(char * call)7553 int heard_via_tnc_in_past_hour(char *call)
7554 {
7555   DataRow *p_station;
7556   int in_hour;
7557 
7558   in_hour=0;
7559   if (search_station_name(&p_station,call,1))    // find call
7560   {
7561 
7562     // Check the heard_via_tnc_last_time timestamp.  This is a
7563     // timestamp that is saved each time a station is heard via
7564     // RF.  It is initially set to 0.  It does not get reset
7565     // when a packet comes in via a non-TNC interface.
7566     //
7567     if (p_station->heard_via_tnc_last_time)     // non-zero entry
7568     {
7569 
7570       // Should we check to see if the last packet was message
7571       // capable?
7572 
7573       // Decide whether it was heard on a TNC interface within
7574       // the hour
7575       in_hour = (int)((p_station->heard_via_tnc_last_time+3600l) > sec_now());
7576 
7577       if(debug_level & 2)
7578         fprintf(stderr, "Call %s: %ld %ld ok %d\n",
7579                 call,
7580                 (long)(p_station->heard_via_tnc_last_time),
7581                 (long)sec_now(),
7582                 in_hour);
7583 
7584     }
7585     else
7586     {
7587       if (debug_level & 2)
7588       {
7589         fprintf(stderr,"Call %s Not heard via tnc\n",call);
7590       }
7591     }
7592   }
7593   else
7594   {
7595     if (debug_level & 2)
7596     {
7597       fprintf(stderr,"IG:station not found\n");
7598     }
7599   }
7600   return(in_hour);
7601 }
7602 
7603 
7604 
7605 
7606 
7607 //////////////////////////////////// Weather Data //////////////////////////////////////////////////
7608 
7609 
7610 
7611 
7612 
7613 /* valid characters for APRS weather data fields */
is_aprs_chr(char ch)7614 int is_aprs_chr(char ch)
7615 {
7616 
7617   if (isdigit((int)ch) || ch==' ' || ch=='.' || ch=='-')
7618   {
7619     return(1);
7620   }
7621   else
7622   {
7623     return(0);
7624   }
7625 }
7626 
7627 
7628 
7629 
7630 
count_filler_chars(char ch)7631 int count_filler_chars(char ch)
7632 {
7633 
7634   if (isdigit((int)ch) || ch==' ' || ch=='.' || ch=='-')
7635   {
7636     return(1);
7637   }
7638   else
7639   {
7640     return(0);
7641   }
7642 }
7643 
7644 
7645 
7646 
7647 
7648 /* check data format    123 ___ ... */
7649 // We wish to count how many ' ' or '.' characters we find.  If it
7650 // equals zero or the field width, it might be a weather field.  If
7651 // not, then it might be part of a comment field or something else.
7652 //
is_weather_data(char * data,int len)7653 int is_weather_data(char *data, int len)
7654 {
7655   int ok = 1;
7656   int i;
7657   int count = 0;
7658 
7659   for (i=0; ok && i<len; i++)
7660     if (!is_aprs_chr(data[i]))
7661     {
7662       ok = 0;
7663     }
7664 
7665   // Count filler characters.  Must equal zero or field width to
7666   // be a weather field.  There doesn't appear to be a case where
7667   // a single period is allowed in any weather-related fields.
7668   //
7669   for (i=0; ok && i<len; i++)
7670   {
7671     if (data[i] == ' ' || data[i] == '.')
7672     {
7673       count++;
7674     }
7675   }
7676   if (count != 0 && count != len)
7677   {
7678     ok = 0;
7679   }
7680 
7681   return(ok);
7682 }
7683 
7684 
7685 
7686 
7687 
7688 // Extract single weather data item from "data".  Returns it in
7689 // "temp".  Modifies "data" to remove the found data from the
7690 // string.  Returns a 1 if found, 0 if not found.
7691 //
7692 // PE1DNN
7693 // If the item is contained in the string but does not contain a
7694 // value then regard the item as "not found" in the weather string.
7695 //
extract_weather_item(char * data,char type,int datalen,char * temp)7696 int extract_weather_item(char *data, char type, int datalen, char *temp)
7697 {
7698   int i,ofs,found,len;
7699 
7700 
7701   //fprintf(stderr,"%s\n",data);
7702 
7703   found=0;
7704   len = (int)strlen(data);
7705   for(ofs=0; !found && ofs<len-datalen; ofs++)      // search for start sequence
7706     if (data[ofs]==type)
7707     {
7708       found=1;
7709       if (!is_weather_data(data+ofs+1, datalen))
7710       {
7711         found=0;
7712       }
7713     }
7714   if (found)     // ofs now points after type character
7715   {
7716     substr(temp,data+ofs,datalen);
7717     for (i=ofs-1; i<len-datalen; i++)      // delete item from info field
7718     {
7719       data[i] = data[i+datalen+1];
7720     }
7721     if((temp[0] == ' ') || (temp[0] == '.'))
7722     {
7723       // found it, but it doesn't contain a value!
7724       // Clean up and report "not found" - PE1DNN
7725       temp[0] = '\0';
7726       found = 0;
7727     }
7728     else
7729     {
7730       if (debug_level & 2)
7731       {
7732         fprintf(stderr,"extract_weather_item: %s\n",temp);
7733       }
7734     }
7735   }
7736   else
7737   {
7738     temp[0] = '\0';
7739   }
7740   return(found);
7741 }
7742 
7743 
7744 
7745 
7746 
7747 // test-extract single weather data item from information field.  In
7748 // other words:  Does not change the input string, but does test
7749 // whether the data is present.  Returns a 1 if found, 0 if not
7750 // found.
7751 //
7752 // PE1DNN
7753 // If the item is contained in the string but does not contain a
7754 // value then regard the item as "not found" in the weather string.
7755 //
test_extract_weather_item(char * data,char type,int datalen)7756 int test_extract_weather_item(char *data, char type, int datalen)
7757 {
7758   int ofs,found,len;
7759 
7760   found=0;
7761   len = (int)strlen(data);
7762   for(ofs=0; !found && ofs<len-datalen; ofs++)      // search for start sequence
7763     if (data[ofs]==type)
7764     {
7765       found=1;
7766       if (!is_weather_data(data+ofs+1, datalen))
7767       {
7768         found=0;
7769       }
7770     }
7771 
7772   // We really should test for numbers here (with an optional
7773   // leading '-'), and test across the length of the substring.
7774   //
7775   if(found && ((data[ofs+1] == ' ') || (data[ofs+1] == '.')))
7776   {
7777     // found it, but it doesn't contain a value!
7778     // report "not found" - PE1DNN
7779     found = 0;
7780   }
7781 
7782   //fprintf(stderr,"test_extract: %c %d\n",type,found);
7783   return(found);
7784 }
7785 
7786 
7787 
7788 
7789 
7790 // DK7IN 77
7791 // raw weather report            in information field
7792 // positionless weather report   in information field
7793 // complete weather report       with lat/lon
7794 //  see APRS Reference page 62ff
7795 //
7796 // Added 'F' for Fuel Temp and 'f' for Fuel Moisture in order to
7797 // decode these two new parameters used for RAWS weather station
7798 // objects.
7799 //
7800 // By the time we call this function we've already extracted any
7801 // time/position info at the beginning of the string.
7802 //
extract_weather(DataRow * p_station,char * data,int compr)7803 int extract_weather(DataRow *p_station, char *data, int compr)
7804 {
7805   char time_data[MAX_TIME];
7806   char temp[5];
7807   int  ok = 1;
7808   WeatherRow *weather;
7809   char course[4];
7810   char speed[4];
7811   int in_knots = 0;
7812 
7813   //WE7U
7814   // Try copying the string to a temporary string, then do some
7815   // extractions to see if a few weather items are present?  This
7816   // would allow us to have the weather items in any order, and if
7817   // enough of them were present, we consider it to be a weather
7818   // packet?  We'd need to qualify all of the data to make sure we had
7819   // the proper number of digits for each.  The trick is to make sure
7820   // we don't decide it's a weather packet if it's not.  We don't know
7821   // what people might send in packets in the future.
7822 
7823   if (compr)          // compressed position report
7824   {
7825     // Look for weather data in fixed locations first
7826     if (strlen(data) >= 8
7827         && data[0] =='g' && is_weather_data(&data[1],3)
7828         && data[4] =='t' && is_weather_data(&data[5],3))
7829     {
7830 
7831       // Snag WX course/speed from compressed position data.
7832       // This speed is in knots.  This assumes that we've
7833       // already extracted speed/course from the compressed
7834       // packet.  extract_comp_position() extracts
7835       // course/speed as well.
7836       memcpy(speed, p_station->speed, sizeof(speed));
7837       speed[sizeof(speed)-1] = '\0';  // Terminate string
7838       memcpy(course, p_station->course, sizeof(course));
7839       course[sizeof(course)-1] = '\0';  // Terminate string
7840       in_knots = 1;
7841 
7842       //fprintf(stderr,"Found compressed wx\n");
7843     }
7844     // Look for weather data in non-fixed locations (RAWS WX
7845     // Stations?)
7846     else if ( strlen(data) >= 8
7847               && test_extract_weather_item(data,'g',3)
7848               && test_extract_weather_item(data,'t',3) )
7849     {
7850 
7851       // Snag WX course/speed from compressed position data.
7852       // This speed is in knots.  This assumes that we've
7853       // already extracted speed/course from the compressed
7854       // packet.  extract_comp_position() extracts
7855       // course/speed as well.
7856       memcpy(speed, p_station->speed, sizeof(speed));
7857       speed[sizeof(speed)-1] = '\0';  // Terminate string
7858       memcpy(course, p_station->course, sizeof(course));
7859       course[sizeof(course)-1] = '\0';  // Terminate string
7860       in_knots = 1;
7861 
7862       //fprintf(stderr,"Found compressed WX in non-fixed locations! %s:%s\n",
7863       //    p_station->call_sign,data);
7864 
7865     }
7866     else    // No weather data found
7867     {
7868       ok = 0;
7869 
7870       //fprintf(stderr,"No compressed wx\n");
7871     }
7872   }
7873   else      // Look for non-compressed weather data
7874   {
7875     // Look for weather data in defined locations first
7876     if (strlen(data)>=15 && data[3]=='/'
7877         && is_weather_data(data,3) && is_weather_data(&data[4],3)
7878         && data[7] =='g' && is_weather_data(&data[8], 3)
7879         && data[11]=='t' && is_weather_data(&data[12],3))      // Complete Weather Report
7880     {
7881 
7882       // Get speed/course.  Speed is in knots.
7883       (void)extract_speed_course(data,speed,course);
7884       in_knots = 1;
7885 
7886       // Either one not found?  Try again.
7887       if ( (speed[0] == '\0') || (course[0] == '\0') )
7888       {
7889 
7890         // Try to get speed/course from 's' and 'c' fields
7891         // (another wx format).  Speed is in mph.
7892         (void)extract_weather_item(data,'c',3,course); // wind direction (in degrees)
7893         (void)extract_weather_item(data,'s',3,speed);  // sustained one-minute wind speed (in mph)
7894         in_knots = 0;
7895       }
7896 
7897       //fprintf(stderr,"Found Complete Weather Report\n");
7898     }
7899     // Look for date/time and weather in fixed locations first
7900     else if (strlen(data)>=16
7901              && data[0] =='c' && is_weather_data(&data[1], 3)
7902              && data[4] =='s' && is_weather_data(&data[5], 3)
7903              && data[8] =='g' && is_weather_data(&data[9], 3)
7904              && data[12]=='t' && is_weather_data(&data[13],3))   // Positionless Weather Data
7905     {
7906       //fprintf(stderr,"Found positionless wx data\n");
7907       // Try to snag speed/course out of first 7 bytes.  Speed
7908       // is in knots.
7909       (void)extract_speed_course(data,speed,course);
7910       in_knots = 1;
7911 
7912       // Either one not found?  Try again.
7913       if ( (speed[0] == '\0') || (course[0] == '\0') )
7914       {
7915         //fprintf(stderr,"Trying again for course/speed\n");
7916         // Also try to get speed/course from 's' and 'c' fields
7917         // (another wx format)
7918         (void)extract_weather_item(data,'c',3,course); // wind direction (in degrees)
7919         (void)extract_weather_item(data,'s',3,speed);  // sustained one-minute wind speed (in mph)
7920         in_knots = 0;
7921       }
7922 
7923       //fprintf(stderr,"Found weather\n");
7924     }
7925     // Look for weather data in non-fixed locations (RAWS WX
7926     // Stations?)
7927     else if (strlen (data) >= 16
7928              && test_extract_weather_item(data,'h',2)
7929              && test_extract_weather_item(data,'g',3)
7930              && test_extract_weather_item(data,'t',3) )
7931     {
7932 
7933       // Try to snag speed/course out of first 7 bytes.  Speed
7934       // is in knots.
7935       (void)extract_speed_course(data,speed,course);
7936       in_knots = 1;
7937 
7938       // Either one not found?  Try again.
7939       if ( (speed[0] == '\0') || (course[0] == '\0') )
7940       {
7941 
7942         // Also try to get speed/course from 's' and 'c' fields
7943         // (another wx format)
7944         (void)extract_weather_item(data,'c',3,course); // wind direction (in degrees)
7945         (void)extract_weather_item(data,'s',3,speed);  // sustained one-minute wind speed (in mph)
7946         in_knots = 0;
7947       }
7948 
7949       //fprintf(stderr,"Found WX in non-fixed locations!  %s:%s\n",
7950       //    p_station->call_sign,data);
7951     }
7952     else    // No weather data found
7953     {
7954       ok = 0;
7955 
7956       //fprintf(stderr,"No wx found\n");
7957     }
7958   }
7959 
7960   if (ok)
7961   {
7962     ok = get_weather_record(p_station);     // get existing or create new weather record
7963   }
7964 
7965   if (ok)
7966   {
7967     weather = p_station->weather_data;
7968 
7969     // Copy into weather speed variable.  Convert knots to mph
7970     // if necessary.
7971     if (in_knots)
7972     {
7973       xastir_snprintf(weather->wx_speed,
7974                       sizeof(weather->wx_speed),
7975                       "%03.0f",
7976                       atoi(speed) * 1.1508);  // Convert knots to mph
7977     }
7978     else
7979     {
7980       // Already in mph.  Copy w/no conversion.
7981       xastir_snprintf(weather->wx_speed,
7982                       sizeof(weather->wx_speed),
7983                       "%s",
7984                       speed);
7985     }
7986 
7987     xastir_snprintf(weather->wx_course,
7988                     sizeof(weather->wx_course),
7989                     "%s",
7990                     course);
7991 
7992     if (compr)          // course/speed was taken from normal data, delete that
7993     {
7994       // fix me: we delete a potential real speed/course now
7995       // we should differentiate between normal and weather data in compressed position decoding...
7996       //            p_station->speed_time[0]     = '\0';
7997       p_station->speed[0]          = '\0';
7998       p_station->course[0]         = '\0';
7999     }
8000 
8001     (void)extract_weather_item(data,'g',3,weather->wx_gust);      // gust (peak wind speed in mph in the last 5 minutes)
8002 
8003     (void)extract_weather_item(data,'t',3,weather->wx_temp);      // temperature (in deg Fahrenheit), could be negative
8004 
8005     (void)extract_weather_item(data,'r',3,weather->wx_rain);      // rainfall (1/100 inch) in the last hour
8006 
8007     (void)extract_weather_item(data,'p',3,weather->wx_prec_24);   // rainfall (1/100 inch) in the last 24 hours
8008 
8009     (void)extract_weather_item(data,'P',3,weather->wx_prec_00);   // rainfall (1/100 inch) since midnight
8010 
8011     if (extract_weather_item(data,'h',2,weather->wx_hum))         // humidity (in %, 00 = 100%)
8012     {
8013       xastir_snprintf(weather->wx_hum, sizeof(weather->wx_hum), "%03d",(atoi(weather->wx_hum)+99)%100+1);
8014     }
8015 
8016     if (extract_weather_item(data,'b',5,weather->wx_baro))  // barometric pressure (1/10 mbar / 1/10 hPascal)
8017       xastir_snprintf(weather->wx_baro,
8018                       sizeof(weather->wx_baro),
8019                       "%0.1f",
8020                       (float)(atoi(weather->wx_baro)/10.0));
8021 
8022     // If we parsed a speed/course, a second 's' parameter means
8023     // snowfall.  Try to parse it, but only in the case where
8024     // we've parsed speed out of this packet already.
8025     if ( (speed[0] != '\0') && (course[0] != '\0') )
8026     {
8027       (void)extract_weather_item(data,'s',3,weather->wx_snow);      // snowfall, inches in the last 24 hours
8028     }
8029 
8030     (void)extract_weather_item(data,'L',3,temp);                  // luminosity (in watts per square meter) 999 and below
8031 
8032     (void)extract_weather_item(data,'l',3,temp);                  // luminosity (in watts per square meter) 1000 and above
8033 
8034     (void)extract_weather_item(data,'#',3,temp);                  // raw rain counter
8035 
8036     (void)extract_weather_item(data,'F',3,weather->wx_fuel_temp); // Fuel Temperature in °F (RAWS)
8037 
8038     if (extract_weather_item(data,'f',2,weather->wx_fuel_moisture))// Fuel Moisture (RAWS) (in %, 00 = 100%)
8039       xastir_snprintf(weather->wx_fuel_moisture,
8040                       sizeof(weather->wx_fuel_moisture),
8041                       "%03d",
8042                       (atoi(weather->wx_fuel_moisture)+99)%100+1);
8043 
8044     //    extract_weather_item(data,'w',3,temp);                          // ?? text wUII
8045 
8046     // now there should be the name of the weather station...
8047 
8048     // Create a timestamp from the current time
8049     xastir_snprintf(weather->wx_time,
8050                     sizeof(weather->wx_time),
8051                     "%s",
8052                     get_time(time_data));
8053 
8054     // Set the timestamp in the weather record so that we can
8055     // decide whether or not to "ghost" the weather data later.
8056     weather->wx_sec_time=sec_now();
8057 //        weather->wx_data=1;  // we don't need this
8058 
8059 //        case ('.'):/* skip */
8060 //            wx_strpos+=4;
8061 //            break;
8062 
8063 //        default:
8064 //            wx_done=1;
8065 //            weather->wx_type=data[wx_strpos];
8066 //            if(strlen(data)>wx_strpos+1)
8067 //                xastir_snprintf(weather->wx_station,
8068 //                    sizeof(weather->wx_station),
8069 //                    "%s",
8070 //                    data+wx_strpos+1);
8071 //            break;
8072   }
8073   return(ok);
8074 }
8075 
8076 
8077 
8078 
8079 
8080 // Initial attempt at decoding tropical storm, tropical depression,
8081 // and hurricane data.
8082 //
8083 // This data can be in an Object report, but can also be in an Item
8084 // or position report.
8085 // "/TS" = Tropical Storm
8086 // "/HC" = Hurricane
8087 // "/TD" = Tropical Depression
8088 // "/TY" = Typhoon
8089 // "/ST" = Super Typhoon
8090 // "/SC" = Severe Cyclone
8091 
8092 // The symbol will be either "\@" for current position, or "/@" for
8093 // predicted position.
8094 //
extract_storm(DataRow * p_station,char * data,int UNUSED (compr))8095 int extract_storm(DataRow *p_station, char *data, int UNUSED(compr) )
8096 {
8097   char time_data[MAX_TIME];
8098   int  ok = 1;
8099   WeatherRow *weather;
8100   char course[4];
8101   char speed[4];  // Speed in knots
8102   char *p, *p2;
8103 
8104 
8105   // Should probably encode the storm type in the weather object and
8106   // print it out in plain text in the Station Info dialog.
8107 
8108   if ((p = strstr(data, "/TS")) != NULL)
8109   {
8110     // We have a Tropical Storm
8111     //fprintf(stderr,"Tropical Storm! %s\n",data);
8112   }
8113   else if ((p = strstr(data, "/TD")) != NULL)
8114   {
8115     // We have a Tropical Depression
8116     //fprintf(stderr,"Tropical Depression! %s\n",data);
8117   }
8118   else if ((p = strstr(data, "/HC")) != NULL)
8119   {
8120     // We have a Hurricane
8121     //fprintf(stderr,"Hurricane! %s\n",data);
8122   }
8123   else if ((p = strstr(data, "/TY")) != NULL)
8124   {
8125     // We have a Typhoon
8126     //fprintf(stderr,"Hurricane! %s\n",data);
8127   }
8128   else if ((p = strstr(data, "/ST")) != NULL)
8129   {
8130     // We have a Super Typhoon
8131     //fprintf(stderr,"Hurricane! %s\n",data);
8132   }
8133   else if ((p = strstr(data, "/SC")) != NULL)
8134   {
8135     // We have a Severe Cyclone
8136     //fprintf(stderr,"Hurricane! %s\n",data);
8137   }
8138   else    // Not one of the three we're trying to decode
8139   {
8140     ok = 0;
8141     return(ok);
8142   }
8143 
8144   //fprintf(stderr,"\n%s\n",data);
8145 
8146   // Back up 7 spots to try to extract the next items
8147   p2 = p - 7;
8148   if (p2 >= data)
8149   {
8150     // Attempt to extract course/speed.  Speed in knots.
8151     if (!extract_speed_course(p2,speed,course))
8152     {
8153       // No speed/course to extract
8154       //fprintf(stderr,"No speed/course found\n");
8155       ok = 0;
8156       return(ok);
8157     }
8158   }
8159   else    // Not enough characters for speed/course.  Must have
8160   {
8161     // guessed wrong on what type of data it is.
8162     //fprintf(stderr,"No speed/course found 2\n");
8163     ok = 0;
8164     return(ok);
8165   }
8166 
8167 
8168   //fprintf(stderr,"%s\n",data);
8169 
8170   if (ok)
8171   {
8172 
8173     // If we got this far, we have speed/course and know what type
8174     // of storm it is.
8175     //fprintf(stderr,"Speed: %s, Course: %s\n",speed,course);
8176 
8177     ok = get_weather_record(p_station);     // get existing or create new weather record
8178   }
8179 
8180   if (ok)
8181   {
8182     //        p_station->speed_time[0]     = '\0';
8183 
8184     p_station->weather_data->wx_storm = 1;  // We found a storm
8185 
8186     // Note that speed is in knots.  If we were stuffing it into
8187     // "wx_speed" we'd have to convert it to MPH.
8188     if (strcmp(speed,"   ") != 0 && strcmp(speed,"...") != 0)
8189     {
8190       xastir_snprintf(p_station->speed,
8191                       sizeof(p_station->speed),
8192                       "%s",
8193                       speed);
8194     }
8195     else
8196     {
8197       p_station->speed[0] = '\0';
8198     }
8199 
8200     if (strcmp(course,"   ") != 0 && strcmp(course,"...") != 0)
8201       xastir_snprintf(p_station->course,
8202                       sizeof(p_station->course),
8203                       "%s",
8204                       course);
8205     else
8206     {
8207       p_station->course[0] = '\0';
8208     }
8209 
8210     weather = p_station->weather_data;
8211 
8212     p2++;   // Skip the description text, "/TS", "/HC", "/TD", "/TY", "/ST", or "/SC"
8213 
8214     // Extract the sustained wind speed in knots
8215     if(extract_weather_item(p2,'/',3,weather->wx_speed))
8216       // Convert from knots to MPH
8217       xastir_snprintf(weather->wx_speed,
8218                       sizeof(weather->wx_speed),
8219                       "%0.1f",
8220                       atof(weather->wx_speed) * 1.1508);
8221 
8222     //fprintf(stderr,"%s\n",data);
8223 
8224     // Extract gust speed in knots
8225     if (extract_weather_item(p2,'^',3,weather->wx_gust)) // gust (peak wind speed in knots)
8226       // Convert from knots to MPH
8227       xastir_snprintf(weather->wx_gust,
8228                       sizeof(weather->wx_gust),
8229                       "%0.1f",
8230                       atof(weather->wx_gust) * 1.1508);
8231 
8232     //fprintf(stderr,"%s\n",data);
8233 
8234     // Pressure is already in millibars/hPa.  No conversion
8235     // needed.
8236     if (extract_weather_item(p2,'/',4,weather->wx_baro))  // barometric pressure (1/10 mbar / 1/10 hPascal)
8237       xastir_snprintf(weather->wx_baro,
8238                       sizeof(weather->wx_baro),
8239                       "%0.1f",
8240                       atof(weather->wx_baro));
8241 
8242     //fprintf(stderr,"%s\n",data);
8243 
8244     (void)extract_weather_item(p2,'>',3,weather->wx_hurricane_radius); // Nautical miles
8245 
8246     //fprintf(stderr,"%s\n",data);
8247 
8248     (void)extract_weather_item(p2,'&',3,weather->wx_trop_storm_radius); // Nautical miles
8249 
8250     //fprintf(stderr,"%s\n",data);
8251 
8252     (void)extract_weather_item(p2,'%',3,weather->wx_whole_gale_radius); // Nautical miles
8253 
8254     //fprintf(stderr,"%s\n",data);
8255 
8256     // Create a timestamp from the current time
8257     xastir_snprintf(weather->wx_time,
8258                     sizeof(weather->wx_time),
8259                     "%s",
8260                     get_time(time_data));
8261 
8262     // Set the timestamp in the weather record so that we can
8263     // decide whether or not to "ghost" the weather data later.
8264     weather->wx_sec_time=sec_now();
8265   }
8266   return(ok);
8267 }
8268 
8269 
8270 
8271 
8272 
8273 /*
8274  * Look for information about other points associated with this station.
8275  * If found, compute the coordinates and save the information in the
8276  * station structure.
8277  * KG4NBB
8278  */
8279 // If remove_string == 0, don't remove the string from the comment
8280 // field.  Useful for objects/items where we need to retransmit the
8281 // string unchanged.
8282 
8283 #define MULTI_DEBUG 2048
8284 #define LBRACE '{'
8285 #define RBRACE '}'
8286 #define START_STR " }"
8287 
extract_multipoints(DataRow * p_station,char * data,int UNUSED (type),int remove_string)8288 static void extract_multipoints(DataRow *p_station,
8289                                 char *data,
8290                                 int UNUSED(type),
8291                                 int remove_string)
8292 {
8293   // If they're in there, the multipoints start with the
8294   // sequence <space><rbrace><lower><digit> and end with a <lbrace>.
8295   // In addition, there must be no spaces in there, and there
8296   // must be an even number of characters (after the lead-in).
8297 
8298   char *p, *p2;
8299   int found = 0;
8300   char *end;
8301   int data_size;
8302 
8303 
8304   if (debug_level & MULTI_DEBUG)
8305     fprintf(stderr,"extract_multipoints: start processing %s\n",
8306             p_station->call_sign);
8307 
8308   if (data == NULL)
8309   {
8310     if (debug_level & MULTI_DEBUG)
8311     {
8312       fprintf(stderr,"extract_multipoints: No Data, returning\n");
8313     }
8314     return;
8315   }
8316 
8317   if (debug_level & MULTI_DEBUG)
8318   {
8319     fprintf(stderr,"Data: %s\t\t\n", data);
8320   }
8321 
8322   data_size = strlen(data);
8323 
8324   end = data + (strlen(data) - 7);  // 7 == 3 lead-in chars, plus 2 points
8325 
8326   p_station->num_multipoints = 0;
8327 
8328   /*
8329   for (p = data; !found && p <= end; ++p) {
8330       if (*p == ' ' && *(p+1) == RBRACE && islower((int)*(p+2)) && isdigit((int)*(p+3)) &&
8331                           (p2 = strchr(p+4, LBRACE)) != NULL && ((p2 - p) % 2) == 1) {
8332           found = 1;
8333       }
8334   }
8335   */
8336 
8337   // Start looking at the beginning of the data.
8338 
8339   p = data;
8340 
8341   if (debug_level & MULTI_DEBUG)
8342   {
8343     if (strstr(p,START_STR) == NULL)
8344     {
8345       fprintf(stderr," Data does not start with space-brace, it starts with %c%c\n", p[0],p[1]);
8346     }
8347     else
8348     {
8349       fprintf(stderr," Data starts with space-brace\n");
8350     }
8351   }
8352 
8353   // Look for the opening string.
8354 
8355   while (!found && p < end && (p = strstr(p, START_STR)) != NULL)
8356   {
8357     // The opening string was found. Check the following information.
8358     if (debug_level & MULTI_DEBUG)
8359       fprintf(stderr,"  Found opening brace, next chars are %c %c %c\n",
8360               *(p+2),*(p+3),*(p+4));
8361 
8362     if (islower((int)*(p+2)) && isdigit((int)*(p+3)) && (p2 = strchr(p+4, LBRACE)) != NULL && ((p2 - p) % 2) == 1)
8363     {
8364       // It all looks good!
8365 
8366       found = 1;
8367     }
8368     else
8369     {
8370       // The following characters are not right. Advance and
8371       // look again.
8372       if (debug_level & MULTI_DEBUG)
8373       {
8374         fprintf(stderr,"  Found opening string (}) but next characters are not right: %c %c %c\n",*(p+2),*(p+3),*(p+4));
8375       }
8376       ++p;
8377     }
8378   }
8379 
8380   if (found)
8381   {
8382     long multiplier;
8383     double d;
8384     char *m_start = p;    // Start of multipoint string
8385     char ok = 1;
8386 
8387     if (debug_level & MULTI_DEBUG)
8388     {
8389       fprintf(stderr,"station %s contains \"%s\"\n", p_station->call_sign, p);
8390     }
8391 
8392     // The second character (the lowercase) indicates additional style information,
8393     // such as color, line type, etc.
8394 
8395     p_station->style = *(p+2);
8396 
8397     // The third character (the digit) indicates the way the points should be
8398     // used. They may be used to draw a closed polygon, a series of line segments,
8399     // etc.
8400 
8401     p_station->type = *(p+3);
8402 
8403     // The fourth character indicates the scale of the coordinates that
8404     // follow. It may range from '!' to '|' (124). The value represents the
8405     // unit of measure (1, 0.1, 0.001, etc., in degrees) used in the offsets.
8406     //
8407     // Use the following formula to convert the char to the value:
8408     // (10 ^ ((c - 33) / 20)) / 10000 degrees
8409     //
8410     // Finally we have to convert to Xastir units. Xastir stores coordinates
8411     // as hudredths of seconds. There are 360,000 of those per degree, so we
8412     // need to multiply by that factor so our numbers will be converted to
8413     // Xastir units.
8414 
8415     p = p + 4;
8416 
8417     if (*p < '!' || *p > '|')
8418     {
8419       fprintf(stderr,"extract_multipoints: invalid scale character %d\n", *p);
8420       ok = 0; // Failure
8421     }
8422     else
8423     {
8424 
8425       d = (double)(*p);
8426       d = pow(10.0, ((d - 33) / 20)) / 10000.0 * 360000.0;
8427       multiplier = (long)d;
8428       if (debug_level & MULTI_DEBUG)
8429       {
8430         fprintf(stderr,"    multiplier factor is: %c %d %f (%ld)\n", *p, *p, d, multiplier);
8431       }
8432 
8433       ++p;
8434 
8435       // The remaining characters are in pairs. Each pair is the
8436       // offset lat and lon for one of the points. (The offset is
8437       // from the actual location of the object.) Convert each
8438       // character to its numeric value and save it.
8439 
8440       while (*p != LBRACE && p_station->num_multipoints < MAX_MULTIPOINTS)
8441       {
8442         // The characters are in the range '"' (34 decimal) to 'z' (122). They
8443         // encode values in the range -44 to +44. To convert to the correct
8444         // value 78 is subtracted from the character's value.
8445 
8446         int lat_val = *p - 78;
8447         int lon_val = *(p+1) - 78;
8448 
8449         // Check for correct values.
8450 
8451         if (lon_val < -44 || lon_val > 44 || lat_val < -44 || lat_val > 44)
8452         {
8453           char temp[MAX_LINE_SIZE+1];
8454           int i;
8455 
8456           // Filter the string so we don't send strange
8457           // chars to the xterm
8458           for (i = 0; i < (int)strlen(data); i++)
8459           {
8460             temp[i] = data[i] & 0x7f;
8461             if ( (temp[i] < 0x20) || (temp[i] > 0x7e) )
8462             {
8463               temp[i] = ' ';
8464             }
8465           }
8466           temp[strlen(data)] = '\0';
8467 
8468           fprintf(stderr,"extract_multipoints: invalid value in (filtered) \"%s\": %d,%d\n",
8469                   temp,
8470                   lat_val,
8471                   lon_val);
8472 
8473           p_station->num_multipoints = 0;     // forget any points we already set
8474           ok = 0; // Failure to decode
8475           break;
8476         }
8477 
8478         // Malloc the storage area for this if we don't have
8479         // it yet.
8480         if (p_station->multipoint_data == NULL)
8481         {
8482           //fprintf(stderr, "Malloc'ing MultipointRow record, %s\n", p_station->call_sign);
8483           p_station->multipoint_data = malloc(sizeof(MultipointRow));
8484           if (p_station->multipoint_data == NULL)
8485           {
8486             p_station->num_multipoints = 0;
8487             fprintf(stderr,"Couldn't malloc MultipointRow'\n");
8488             if (debug_level & MULTI_DEBUG)
8489             {
8490               fprintf(stderr,"extract_multipoints: Malloc failure, returning\n");
8491             }
8492             return;
8493           }
8494         }
8495 
8496         if (debug_level & MULTI_DEBUG)
8497         {
8498           fprintf(stderr,"computed offset %d,%d\n", lat_val, lon_val);
8499         }
8500 
8501         // Add the offset to the object's position to obtain the position of the point.
8502         // Note that we're working in Xastir coordinates, and in North America they
8503         // are exactly opposite to lat/lon (larger numbers are farther east and south).
8504         // An offset with a positive value means that the point should be north and/or
8505         // west of the object, so we have to *subtract* the offset to get the correct
8506         // placement in Xastir coordinates.
8507         // TODO: Consider what we should do in the other geographic quadrants. Should we
8508         // check here for the correct sign of the offset? Or should the program that
8509         // creates the offsets take that into account?
8510 
8511         p_station->multipoint_data->multipoints[p_station->num_multipoints][0]
8512           = p_station->coord_lon - (lon_val * multiplier);
8513         p_station->multipoint_data->multipoints[p_station->num_multipoints][1]
8514           = p_station->coord_lat - (lat_val * multiplier);
8515 
8516         if (debug_level & MULTI_DEBUG)
8517           fprintf(stderr,
8518                   "computed point %ld, %ld\n",
8519                   p_station->multipoint_data->multipoints[p_station->num_multipoints][0],
8520                   p_station->multipoint_data->multipoints[p_station->num_multipoints][1]);
8521         p += 2;
8522         ++p_station->num_multipoints;
8523       }   // End of while loop
8524     }
8525 
8526     if (ok && remove_string)
8527     {
8528       // We've successfully decoded a multipoint object?
8529       // Remove the multipoint strings (and the sequence
8530       // number at the end if present) from the data string.
8531       // m_start points to the first character (a space).  'p'
8532       // should be pointing at the LBRACE character.
8533 
8534       // Make 'p' point to just after the end of the chars
8535       while ( (p < data+strlen(data)) && (*p != ' ') )
8536       {
8537         p++;
8538       }
8539       // The string that 'p' points to now may be empty
8540 
8541       // Truncate "data" at the starting brace - 1
8542       *m_start = '\0';
8543 
8544       // Now we have two strings inside "data".  Copy the 2nd
8545       // string directly onto the end of the first.
8546       strncat(data, p, data_size+1);
8547 
8548       // The multipoint string and sequence number should be
8549       // erased now from "data".
8550       //fprintf(stderr,"New Data: %s\n", data);
8551     }
8552 
8553     if (debug_level & MULTI_DEBUG)
8554     {
8555       fprintf(stderr,"    station has %d points\n", p_station->num_multipoints);
8556     }
8557   }
8558 
8559   if (debug_level & MULTI_DEBUG)
8560   {
8561     fprintf(stderr,"extract_multipoints: Normal Return\n");
8562   }
8563 }
8564 
8565 #undef MULTI_DEBUG
8566 
8567 
8568 
8569 ////////////////////////////////////////////////////////////////////////////////////////////////////
8570 
8571 
8572 
init_weather(WeatherRow * weather)8573 void init_weather(WeatherRow *weather)      // clear weather data
8574 {
8575 
8576   weather->wx_sec_time             = (time_t)0;
8577   weather->wx_storm                = 0;
8578   weather->wx_time[0]              = '\0';
8579   weather->wx_course[0]            = '\0';
8580   weather->wx_speed[0]             = '\0';
8581   weather->wx_speed_sec_time       = 0; // ??
8582   weather->wx_gust[0]              = '\0';
8583   weather->wx_hurricane_radius[0]  = '\0';
8584   weather->wx_trop_storm_radius[0] = '\0';
8585   weather->wx_whole_gale_radius[0] = '\0';
8586   weather->wx_temp[0]              = '\0';
8587   weather->wx_rain[0]              = '\0';
8588   weather->wx_rain_total[0]        = '\0';
8589   weather->wx_snow[0]              = '\0';
8590   weather->wx_prec_24[0]           = '\0';
8591   weather->wx_prec_00[0]           = '\0';
8592   weather->wx_hum[0]               = '\0';
8593   weather->wx_baro[0]              = '\0';
8594   weather->wx_fuel_temp[0]         = '\0';
8595   weather->wx_fuel_moisture[0]     = '\0';
8596   weather->wx_type                 = '\0';
8597   weather->wx_station[0]           = '\0';
8598 }
8599 
8600 
8601 
8602 
8603 
get_weather_record(DataRow * fill)8604 int get_weather_record(DataRow *fill)      // get or create weather storage
8605 {
8606   int ok=1;
8607 
8608   if (fill->weather_data == NULL)        // new weather data, allocate storage and init
8609   {
8610     fill->weather_data = malloc(sizeof(WeatherRow));
8611     if (fill->weather_data == NULL)
8612     {
8613       fprintf(stderr,"Couldn't allocate memory in get_weather_record()\n");
8614       ok = 0;
8615     }
8616     else
8617     {
8618       init_weather(fill->weather_data);
8619     }
8620   }
8621   return(ok);
8622 }
8623 
8624 
8625 
8626 
8627 
delete_weather(DataRow * fill)8628 int delete_weather(DataRow *fill)      // delete weather storage, if allocated
8629 {
8630 
8631   if (fill->weather_data != NULL)
8632   {
8633     free(fill->weather_data);
8634     fill->weather_data = NULL;
8635     return(1);
8636   }
8637   return(0);
8638 }
8639 
8640 
8641 
8642 
8643 
delete_multipoints(DataRow * fill)8644 int delete_multipoints(DataRow *fill)   // delete multipoint storage, if allocated
8645 {
8646 
8647   if (fill->multipoint_data != NULL)
8648   {
8649     //fprintf(stderr,"Removing multipoint data, %s\n", fill->call_sign);
8650     free(fill->multipoint_data);
8651     fill->multipoint_data = NULL;
8652     fill->num_multipoints = 0;
8653     return(1);
8654   }
8655   return(0);
8656 }
8657 
8658 
8659 
8660 ////////////////////////////////////////// Trails //////////////////////////////////////////////////
8661 
8662 
8663 
8664 /*
8665  *  See if current color is defined as active trail color
8666  */
trail_color_active(int UNUSED (color_index))8667 int trail_color_active(int UNUSED(color_index))
8668 {
8669 
8670   // this should be made configurable...
8671   // to select trail colors to use
8672 
8673   return(1);          // accept this color
8674 }
8675 
8676 
8677 
8678 
8679 
8680 /*
8681  *  Get new trail color for a call
8682  */
new_trail_color(char * call)8683 int new_trail_color(char *call)
8684 {
8685   int color, found, i;
8686 
8687   // If my_trail_diff_color is set a 0, then we'll
8688   // assign one color to every SSID from our callsign.  If
8689   // 1, they get the next color available (round-robin style) just
8690   // like all the other stations.
8691   //
8692   // 0 for last parameter in is_my_call() means skip SSID in
8693   // callsign check.  Non-zero means the callsign + SSID must be
8694   // an exact match.
8695   if (is_my_call(call,my_trail_diff_color))
8696   {
8697     color = MY_TRAIL_COLOR;    // It's my call, so use special color
8698   }
8699   else
8700   {
8701     // all other callsigns get some other color out of the color table
8702     color = current_trail_color;
8703     for(i=0,found=0; !found && i<MAX_TRAIL_COLORS; i++)
8704     {
8705       color = (color + 1) % MAX_TRAIL_COLORS; // try next color in list
8706       // skip special and or inactive colors.
8707       if (color != MY_TRAIL_COLOR && trail_color_active(color))
8708       {
8709         found = 1;
8710       }
8711     }
8712     if (found)
8713     {
8714       current_trail_color = color;  // save it for next time
8715     }
8716     else
8717     {
8718       color = current_trail_color;  // keep old color
8719     }
8720   }
8721   return(color);
8722 }
8723 
8724 
8725 
8726 
8727 
8728 //
8729 // Store one trail point.  Allocate storage for the new data.
8730 //
8731 // We now store track data in a doubly-linked list.  Each record has a
8732 // pointer to the previous and the next record in the list.  The main
8733 // station record has a pointer to the oldest and the newest end of the
8734 // chain, and the chain can be traversed in either order.
8735 //
store_trail_point(DataRow * p_station,long lon,long lat,time_t sec,char * alt,char * speed,char * course,short stn_flag)8736 int store_trail_point(DataRow *p_station,
8737                       long lon,
8738                       long lat,
8739                       time_t sec,
8740                       char *alt,
8741                       char *speed,
8742                       char *course,
8743                       short stn_flag)
8744 {
8745 
8746   char flag;
8747   TrackRow *ptr;
8748 
8749   //fprintf(stderr,"store_trail_point: %s\n",p_station->call_sign);
8750 
8751   if (debug_level & 256)
8752   {
8753     fprintf(stderr,"store_trail_point: for %s\n", p_station->call_sign);
8754   }
8755 
8756   // Allocate storage for the new track point
8757   ptr = malloc(sizeof(TrackRow));
8758   if (ptr == NULL)
8759   {
8760     if (debug_level & 256)
8761     {
8762       fprintf(stderr,"store_trail_point: MALLOC failed for trail.\n");
8763     }
8764     return(0); // Failed due to malloc
8765   }
8766 
8767   // Check whether we have any track data saved
8768   if (p_station->newest_trackpoint == NULL)
8769   {
8770     // new trail, do initialization
8771 
8772     if (debug_level & 256)
8773     {
8774       fprintf(stderr,"Creating new trail.\n");
8775     }
8776     tracked_stations++;
8777 
8778     // Assign a new trail color 'cuz it's a new trail
8779     p_station->trail_color = new_trail_color(p_station->call_sign);
8780   }
8781 
8782   // Start linking the record to the new end of the chain
8783   ptr->prev = p_station->newest_trackpoint;   // Link to record or NULL
8784   ptr->next = NULL;   // Newest end of chain
8785 
8786   // Have an older record already?
8787   if (p_station->newest_trackpoint != NULL)   // Yes
8788   {
8789     p_station->newest_trackpoint->next = ptr;
8790   }
8791   else    // No, this is our first record
8792   {
8793     p_station->oldest_trackpoint = ptr;
8794   }
8795 
8796   // Link it in as our newest record
8797   p_station->newest_trackpoint = ptr;
8798 
8799   if (debug_level & 256)
8800   {
8801     fprintf(stderr,"store_trail_point: Storing data for %s\n", p_station->call_sign);
8802   }
8803 
8804   ptr->trail_long_pos = lon;
8805   ptr->trail_lat_pos  = lat;
8806   ptr->sec            = sec;
8807 
8808   if (alt[0] != '\0')
8809   {
8810     ptr->altitude = atoi(alt)*10;
8811   }
8812   else
8813   {
8814     ptr->altitude = -99999l;
8815   }
8816 
8817   if (speed[0] != '\0')
8818   {
8819     ptr->speed  = (long)(atof(speed)*18.52);
8820   }
8821   else
8822   {
8823     ptr->speed  = -1;
8824   }
8825 
8826   if (course[0] != '\0')
8827   {
8828     ptr->course = (int)(atof(course) + 0.5);  // Poor man's rounding
8829   }
8830   else
8831   {
8832     ptr->course = -1;
8833   }
8834 
8835   flag = '\0';                    // init flags
8836 
8837   if ((stn_flag & ST_DIRECT) != 0)
8838   {
8839     flag |= TR_LOCAL;  // set "local" flag
8840   }
8841 
8842   if (ptr->prev != NULL)      // we have at least two points...
8843   {
8844     // Check whether distance between points is too far.  We
8845     // must convert from degrees to the Xastir coordinate system
8846     // units, which are 100th of a second.
8847     if (    labs(lon - ptr->prev->trail_long_pos) > (trail_segment_distance * 60*60*100) ||
8848             labs(lat - ptr->prev->trail_lat_pos)  > (trail_segment_distance * 60*60*100) )
8849     {
8850 
8851       // Set "new track" flag if there's
8852       // "trail_segment_distance" degrees or more between
8853       // points.  Originally was hard-coded to one degree, now
8854       // set by a slider in the timing dialog.
8855       flag |= TR_NEWTRK;
8856     }
8857     else
8858     {
8859       // Check whether trail went above our maximum time
8860       // between points.  If so, don't draw segment.
8861       if (labs(sec - ptr->prev->sec) > (trail_segment_time *60))
8862       {
8863 
8864         // Set "new track" flag if long delay between
8865         // reception of two points.  Time is set by a slider
8866         // in the timing dialog.
8867         flag |= TR_NEWTRK;
8868       }
8869     }
8870   }
8871   else
8872   {
8873     // Set "new track" flag for first point received.
8874     flag |= TR_NEWTRK;
8875   }
8876   ptr->flag = flag;
8877 
8878   return(1);  // We succeeded
8879 }
8880 
8881 
8882 
8883 
8884 
8885 /*
8886  *  Check if current packet is a delayed echo
8887  */
is_trailpoint_echo(DataRow * p_station)8888 int is_trailpoint_echo(DataRow *p_station)
8889 {
8890   int packets = 1;
8891   time_t checktime;
8892   char temp[50];
8893   TrackRow *ptr;
8894 
8895 
8896   // Check whether we're to skip checking for dupes (reading in
8897   // objects/items from file is one such case).
8898   //
8899   if (skip_dupe_checking)
8900   {
8901     return(0);  // Say that it isn't an echo
8902   }
8903 
8904   // Start at newest end of linked list and compare.  Return if we're
8905   // beyond the checktime.
8906   ptr = p_station->newest_trackpoint;
8907 
8908   if (ptr == NULL)
8909   {
8910     return(0);  // first point couldn't be an echo
8911   }
8912 
8913   checktime = p_station->sec_heard - TRAIL_ECHO_TIME*60;
8914 
8915   while (ptr != NULL)
8916   {
8917 
8918     if (ptr->sec < checktime)
8919     {
8920       return(0);  // outside time frame, no echo found
8921     }
8922 
8923     if ((p_station->coord_lon == ptr->trail_long_pos)
8924         && (p_station->coord_lat == ptr->trail_lat_pos)
8925         && (p_station->speed[0] == '\0' || ptr->speed < 0
8926             || (long)(atof(p_station->speed)*18.52) == ptr->speed)
8927         // current: char knots, trail: long 0.1m (-1 is undef)
8928         && (p_station->course[0] == '\0' || ptr->course <= 0
8929             || atoi(p_station->course) == ptr->course)
8930         // current: char, trail: int (-1 is undef)
8931         && (p_station->altitude[0] == '\0' || ptr->altitude <= -99999l
8932             || atoi(p_station->altitude)*10 == ptr->altitude))
8933     {
8934       // current: char, trail: int (-99999l is undef)
8935       if (debug_level & 1)
8936       {
8937         fprintf(stderr,"delayed echo for %s",p_station->call_sign);
8938         convert_lat_l2s(p_station->coord_lat, temp, sizeof(temp), CONVERT_HP_NORMAL);
8939         fprintf(stderr," at %s",temp);
8940         convert_lon_l2s(p_station->coord_lon, temp, sizeof(temp), CONVERT_HP_NORMAL);
8941         fprintf(stderr," %s, already heard %d packets ago\n",temp,packets);
8942       }
8943       return(1);              // we found a delayed echo
8944     }
8945     ptr = ptr->prev;
8946     packets++;
8947   }
8948   return(0);                      // no echo found
8949 }
8950 
8951 
8952 
8953 
8954 
8955 //
8956 //  Expire trail points.
8957 //
8958 // We now store track data in a doubly-linked list.  Each record has a
8959 // pointer to the previous and the next record in the list.  The main
8960 // station record has a pointer to the oldest and the newest end of the
8961 // chain, and the chain can be traversed in either order.  We use
8962 // this to advantage by adding records at one end of the list and
8963 // expiring them at the other.
8964 //
expire_trail_points(DataRow * p_station,time_t sec)8965 void expire_trail_points(DataRow *p_station, time_t sec)
8966 {
8967   int ii = 0;
8968   int done = 0;
8969   TrackRow *ptr;
8970 
8971 
8972   //fprintf(stderr,"expire_trail_points: %s\n",p_station->call_sign);
8973 
8974   if (debug_level & 256)
8975   {
8976     fprintf(stderr,"expire_trail_points: %s\n",p_station->call_sign);
8977   }
8978 
8979   // Check whether we have any track data saved
8980   if (p_station->oldest_trackpoint == NULL)
8981   {
8982     return;     // Nothing to expire
8983   }
8984 
8985   // Iterate from oldest->newest trackpoints
8986   while (!done && p_station->oldest_trackpoint != NULL)
8987   {
8988     ptr = p_station->oldest_trackpoint;
8989     if ( (ptr->sec + sec) >= sec_now() )
8990     {
8991       // New trackpoint, within expire time.  Quit checking
8992       // the rest of the trackpoints for this station.
8993       done++;
8994     }
8995     else
8996     {
8997       //fprintf(stderr,"Found old trackpoint\n");
8998 
8999       // Track too old.  Unlink this trackpoint and free it.
9000       p_station->oldest_trackpoint = ptr->next;
9001 
9002       // End of chain in this direction
9003       if (p_station->oldest_trackpoint != NULL)
9004       {
9005         p_station->oldest_trackpoint->prev = NULL;
9006       }
9007       else
9008       {
9009         p_station->newest_trackpoint = NULL;
9010       }
9011 
9012       // Free up the space used by the expired trackpoint
9013       free(ptr);
9014 
9015       //fprintf(stderr,"Free'ing a trackpoint\n");
9016 
9017       ii++;
9018 
9019       // Reduce our count of mobile stations if the size of
9020       // the track just went to zero.
9021       if (p_station->oldest_trackpoint == NULL)
9022       {
9023         tracked_stations--;
9024       }
9025     }
9026   }
9027 
9028   if ( (debug_level & 256) && ii )
9029   {
9030     fprintf(stderr,"expire_trail_points: %d trackpoints free'd for %s\n",
9031             ii,
9032             p_station->call_sign);
9033   }
9034 }
9035 
9036 
9037 
9038 
9039 
9040 /*
9041  *  Delete comment records and free memory
9042  */
delete_comments_and_status(DataRow * fill)9043 int delete_comments_and_status(DataRow *fill)
9044 {
9045 
9046   // If the pointers are empty, we're done
9047   if (       (fill->comment_data == NULL)
9048              && (fill->status_data  == NULL) )
9049   {
9050     return(0);
9051   }
9052 
9053   if (fill->comment_data != NULL)     // We have comment records
9054   {
9055     CommentRow *ptr;
9056     CommentRow *ptr_next;
9057 
9058     ptr = fill->comment_data;
9059     ptr_next = ptr->next;
9060     while (ptr != NULL)
9061     {
9062       // Free the actual text string that we malloc'ed
9063       if (ptr->text_ptr != NULL)
9064       {
9065         free(ptr->text_ptr);
9066       }
9067       free(ptr);
9068       ptr = ptr_next; // Advance to next record
9069       if (ptr != NULL)
9070       {
9071         ptr_next = ptr->next;
9072       }
9073       else
9074       {
9075         ptr_next = NULL;
9076       }
9077     }
9078   }
9079   if (fill->status_data != NULL)     // We have status records
9080   {
9081     CommentRow *ptr;
9082     CommentRow *ptr_next;
9083 
9084     ptr = fill->status_data;
9085     ptr_next = ptr->next;
9086     while (ptr != NULL)
9087     {
9088       // Free the actual text string that we malloc'ed
9089       if (ptr->text_ptr != NULL)
9090       {
9091         free(ptr->text_ptr);
9092       }
9093       free(ptr);
9094       ptr = ptr_next; // Advance to next record
9095       if (ptr != NULL)
9096       {
9097         ptr_next = ptr->next;
9098       }
9099       else
9100       {
9101         ptr_next = NULL;
9102       }
9103     }
9104   }
9105   return(1);
9106 }
9107 
9108 
9109 
9110 
9111 
9112 /*
9113  *  Delete trail and free memory
9114  */
delete_trail(DataRow * fill)9115 int delete_trail(DataRow *fill)
9116 {
9117 
9118   if (fill->newest_trackpoint != NULL)
9119   {
9120     TrackRow *current;
9121     TrackRow *next;
9122 
9123     // Free the TrackRow records
9124     current = fill->oldest_trackpoint;
9125     while (current != NULL)
9126     {
9127       next = current->next;
9128       free(current);
9129       current = next;
9130     }
9131 
9132     fill->oldest_trackpoint = NULL;
9133     fill->newest_trackpoint = NULL;
9134     tracked_stations--;
9135     return(1);
9136   }
9137   return(0);
9138 }
9139 
9140 
9141 
9142 
9143 
9144 /*
9145  *  Draw trail on screen.  If solid=1, draw type LineSolid, else
9146  *  draw type LineOnOffDash.
9147  *
9148  *  If label_all_trackpoints=1, add the callsign next to each
9149  *  trackpoint.  We may modify this and just add the callsign at the
9150  *  start/end of each new track segment.
9151  *
9152  */
draw_trail(Widget w,DataRow * fill,int solid)9153 void draw_trail(Widget w, DataRow *fill, int solid)
9154 {
9155   char short_dashed[2]  = {(char)1,(char)5};
9156   char medium_dashed[2] = {(char)5,(char)5};
9157   unsigned long lat0, lon0, lat1, lon1;        // trail segment points
9158   int col_trail, col_dot;
9159   XColor rgb;
9160   long brightness;
9161   char flag1;
9162   TrackRow *ptr;
9163 
9164 
9165   if (!ok_to_draw_station(fill))
9166   {
9167     return;
9168   }
9169 
9170   // Expire old trackpoints first.  We use the
9171   // remove-station-from-display time as the expire time for
9172   // trackpoints.  This can be set from the Configure->Defaults
9173   // dialog.
9174   expire_trail_points(fill, sec_clear);
9175 
9176   ptr = fill->newest_trackpoint;
9177 
9178   // Trail should have at least two points
9179   if ( (ptr != NULL) && (ptr->prev != NULL) )
9180   {
9181     int skip_dupes = 0; // Don't skip points first time through
9182 
9183     if (debug_level & 256)
9184     {
9185       fprintf(stderr,"draw_trail called for %s with %s.\n",
9186               fill->call_sign, (solid? "Solid" : "Non-Solid"));
9187     }
9188 
9189     col_trail = trail_colors[fill->trail_color];
9190 
9191     // define color of position dots in trail
9192     rgb.pixel = col_trail;
9193     XQueryColor(XtDisplay(w),cmap,&rgb);
9194 
9195     brightness = (long)(0.3*rgb.red + 0.55*rgb.green + 0.15*rgb.blue);
9196     if (brightness > 32000l)
9197     {
9198       col_dot = trail_colors[0x05];   // black dot on light trails
9199     }
9200     else
9201     {
9202       col_dot = trail_colors[0x06];   // white dot on dark trail
9203     }
9204 
9205     if (solid)
9206       // Used to be "JoinMiter" and "CapButt" below
9207     {
9208       (void)XSetLineAttributes(XtDisplay(w), gc, 3, LineSolid, CapRound, JoinRound);
9209     }
9210     else
9211     {
9212       // Another choice is LineDoubleDash
9213       (void)XSetLineAttributes(XtDisplay(w), gc, 3, LineOnOffDash, CapRound, JoinRound);
9214       (void)XSetDashes(XtDisplay(w), gc, 0, short_dashed, 2);
9215     }
9216 
9217     // Traverse linked list of trail points from newest to
9218     // oldest
9219     while ( (ptr != NULL) && (ptr->prev != NULL) )
9220     {
9221       lon0 = ptr->trail_long_pos;         // Trail segment start
9222       lat0 = ptr->trail_lat_pos;
9223       lon1 = ptr->prev->trail_long_pos;   // Trail segment end
9224       lat1 = ptr->prev->trail_lat_pos;
9225       flag1 = ptr->flag; // Are we at the start of a new trail?
9226 
9227       if ((flag1 & TR_NEWTRK) == '\0')
9228       {
9229         int lon0_screen, lat0_screen, lon1_screen, lat1_screen;
9230 
9231         // draw trail segment
9232         //
9233         (void)XSetForeground(XtDisplay(w),gc,col_trail);
9234         draw_vector(da,
9235                     lon0,
9236                     lat0,
9237                     lon1,
9238                     lat1,
9239                     gc,
9240                     pixmap_final,
9241                     skip_dupes);
9242 
9243         // draw position point itself
9244         //
9245         (void)XSetForeground(XtDisplay(w),gc,col_dot);
9246         draw_point(w,
9247                    lon0,
9248                    lat0,
9249                    gc,
9250                    pixmap_final,
9251                    skip_dupes);
9252 
9253         // Draw the callsign to go with the point if
9254         // label_all_trackpoints=1
9255         //
9256         if (Display_.callsign && Display_.label_all_trackpoints)
9257         {
9258 
9259           // Convert to screen coordinates
9260           lon0_screen = (lon0 - NW_corner_longitude) / scale_x;
9261           lat0_screen = (lat0 - NW_corner_latitude) / scale_y;
9262 
9263           // Convert to screen coordinates.
9264           lon1_screen = (lon1 - NW_corner_longitude) / scale_x;
9265           lat1_screen = (lat1 - NW_corner_latitude)  / scale_y;
9266 
9267           // The last position already gets its callsign
9268           // string drawn, plus that gets shifted based on
9269           // other parameters.  Draw both points of all
9270           // line segments except that one.  This will
9271           // result in strings getting drawn twice at
9272           // times, but they overlay on top of each other
9273           // so no big deal.
9274           //
9275           if (ptr != fill->newest_trackpoint)
9276           {
9277 
9278             draw_nice_string(da,
9279                              pixmap_final,
9280                              letter_style,
9281                              lon0_screen+10,
9282                              lat0_screen,
9283                              fill->call_sign,
9284                              0x08,
9285                              0x0f,
9286                              strlen(fill->call_sign));
9287 
9288             // If not same screen position as last drawn
9289             if (lon0_screen != lon1_screen
9290                 && lat0_screen != lat1_screen)
9291             {
9292 
9293               draw_nice_string(da,
9294                                pixmap_final,
9295                                letter_style,
9296                                lon1_screen+10,
9297                                lat1_screen,
9298                                fill->call_sign,
9299                                0x08,
9300                                0x0f,
9301                                strlen(fill->call_sign));
9302             }
9303           }
9304         }
9305       }
9306       ptr = ptr->prev;
9307       skip_dupes = 1;
9308     }
9309     (void)XSetDashes(XtDisplay(w), gc, 0, medium_dashed, 2);
9310   }
9311   else if (debug_level & 256)
9312   {
9313     fprintf(stderr,"Trail for %s does not contain 2 or more points.\n",
9314             fill->call_sign);
9315   }
9316 }
9317 
9318 
9319 
9320 
9321 
9322 // DK7IN: there should be some library functions for the next two,
9323 //        but I don't have any documentation while being in holidays...
month2str(int month,char * str,int str_size)9324 void month2str(int month, char *str, int str_size)
9325 {
9326 
9327   switch (month)
9328   {
9329     case  0:
9330       xastir_snprintf(str,str_size,"Jan");
9331       break;
9332     case  1:
9333       xastir_snprintf(str,str_size,"Feb");
9334       break;
9335     case  2:
9336       xastir_snprintf(str,str_size,"Mar");
9337       break;
9338     case  3:
9339       xastir_snprintf(str,str_size,"Apr");
9340       break;
9341     case  4:
9342       xastir_snprintf(str,str_size,"May");
9343       break;
9344     case  5:
9345       xastir_snprintf(str,str_size,"Jun");
9346       break;
9347     case  6:
9348       xastir_snprintf(str,str_size,"Jul");
9349       break;
9350     case  7:
9351       xastir_snprintf(str,str_size,"Aug");
9352       break;
9353     case  8:
9354       xastir_snprintf(str,str_size,"Sep");
9355       break;
9356     case  9:
9357       xastir_snprintf(str,str_size,"Oct");
9358       break;
9359     case 10:
9360       xastir_snprintf(str,str_size,"Nov");
9361       break;
9362     case 11:
9363       xastir_snprintf(str,str_size,"Dec");
9364       break;
9365     default:
9366       xastir_snprintf(str,str_size,"   ");
9367       break;
9368   }
9369 }
9370 
9371 
9372 
9373 
9374 
wday2str(int wday,char * str,int str_size)9375 void wday2str(int wday, char *str, int str_size)
9376 {
9377 
9378   switch (wday)
9379   {
9380     case  0:
9381       xastir_snprintf(str,str_size,"Sun");
9382       break;
9383     case  1:
9384       xastir_snprintf(str,str_size,"Mon");
9385       break;
9386     case  2:
9387       xastir_snprintf(str,str_size,"Tue");
9388       break;
9389     case  3:
9390       xastir_snprintf(str,str_size,"Wed");
9391       break;
9392     case  4:
9393       xastir_snprintf(str,str_size,"Thu");
9394       break;
9395     case  5:
9396       xastir_snprintf(str,str_size,"Fri");
9397       break;
9398     case  6:
9399       xastir_snprintf(str,str_size,"Sat");
9400       break;
9401     default:
9402       xastir_snprintf(str,str_size,"   ");
9403       break;
9404   }
9405 }
9406 
9407 
9408 
9409 
9410 
9411 /*
9412  *  Export trail point to file
9413  *
9414  *  Don't call directly, call export_trail() or export_trail_as_kml() instead
9415  *  as they need to open the file, set appropriate headers, and call export_trailstation()
9416  *  to set the context for the position.
9417  */
exp_trailpos(FILE * f,long lat,long lon,time_t sec,long speed,int course,long alt,int newtrk,int export_format)9418 void exp_trailpos(FILE *f,long lat,long lon,time_t sec,long speed,int course,long alt,int newtrk, int export_format)
9419 {
9420   struct tm *time;
9421   char lat_string[12+1];
9422   char lon_string[12+1];
9423   char month[3+1];
9424   char wday[3+1];
9425   float deg;
9426 
9427   time  = gmtime(&sec);
9428   month2str(time->tm_mon, month, sizeof(month));
9429   wday2str(time->tm_wday, wday, sizeof(wday));
9430   switch (export_format)
9431   {
9432     case EXPORT_KML_TRACK:
9433       // kml format is longitude,latitude,altitude triplets with
9434       // a comma and no spaces separating elements of the triplet
9435       // and a single space seperating sets of triplets in a
9436       // coordinates element.  Latitude and longitude are
9437       // both in decimal degrees.
9438       deg = (float)(lon - 64800000l) / 360000.0;
9439       fprintf(f,"%09.5f,",deg);
9440       deg = -(float)(lat - 32400000l) / 360000.0;
9441       fprintf(f,"%08.5f,",deg);
9442       if (alt > -99999l)
9443       {
9444         fprintf(f,"%05.0f ",(float)(alt/10.0));
9445       }
9446       else        // undefined
9447       {
9448         fprintf(f,"0 ");
9449       }
9450       break;
9451     case EXPORT_XASTIR_TRACK:
9452     default:
9453       if (newtrk)
9454       {
9455         fprintf(f,"\nN  New Track Start\n");
9456       }
9457       // DK7IN: The format may change in the near future !
9458       //        Are there any standards? I want to be able to be compatible to
9459       //        GPS data formats (e.g. G7TO) for easy interchange from/to GPS
9460       //        How should we present undefined data? (speed/course/altitude)
9461       convert_lat_l2s(lat, lat_string, sizeof(lat_string), CONVERT_UP_TRK);
9462       convert_lon_l2s(lon, lon_string, sizeof(lon_string), CONVERT_UP_TRK);
9463       fprintf(f,"T  %s",lat_string);
9464       fprintf(f," %s",lon_string);
9465       fprintf(f," %s %s %02d %02d:%02d:%02d %04d",wday,month,time->tm_mday,time->tm_hour,time->tm_min,time->tm_sec,time->tm_year+1900);
9466 
9467       if (alt > -99999l)
9468       {
9469         fprintf(f,"  %5.0fm",(float)(alt/10.0));
9470       }
9471       else        // undefined
9472       {
9473         fprintf(f,"        ");
9474       }
9475 
9476       if (speed >= 0)
9477       {
9478         fprintf(f," %4.0fkm/h",(float)(speed/10.0));
9479       }
9480       else        // undefined
9481       {
9482         fprintf(f,"          ");
9483       }
9484 
9485       if (course >= 0)                    // DK7IN: is 0 undefined ?? 1..360 ?
9486       {
9487         fprintf(f," %3d\xB0\n",course);
9488       }
9489       else        // undefined
9490       {
9491         fprintf(f,"     \n");
9492       }
9493   }
9494 }
9495 
9496 
9497 
9498 
9499 
9500 /*
9501  *  Export trail for one station to file.
9502  *  Don't call directly, call export_trail() or export_trail_as_kml() instead
9503  *  as they need to open the file and set appropriate headers.
9504  *
9505  *  @param f handle of file to write to
9506  *  @param p_station pointer to station to write
9507  *  @param export_format file format to use (xastir tracklog or kml).
9508  */
exp_trailstation(FILE * f,DataRow * p_station,int export_format)9509 void exp_trailstation(FILE *f, DataRow *p_station, int export_format)
9510 {
9511   char timestring[101];  // string representation of the time heard or the current time
9512   long lat0, lon0;
9513   int newtrk;
9514   time_t sec;
9515   long speed;         // 0.1km/h
9516   int  course;        // degrees
9517   long alt;           // 0.1m
9518   TrackRow *current;
9519 
9520   newtrk = 1;
9521 
9522   current = p_station->oldest_trackpoint;
9523 
9524   switch (export_format)
9525   {
9526 
9527     case EXPORT_KML_TRACK:
9528       // This placemark is for a single position
9529       // or for the most recent position of a trail
9530       // in either case represented as a <Point/>
9531       // and will show up as a labeled pushpin point.
9532       fprintf(f,"<Placemark>");
9533       get_iso_datetime(p_station->sec_heard,timestring,True,True);
9534 
9535       if (p_station->origin[0] == '\0')
9536       {
9537         fprintf(f,"<name>%s</name>\n",p_station->call_sign);
9538         fprintf(f,"<description>");
9539       }
9540       else
9541       {
9542         fprintf(f,"<name>%s</name>\n<description>Object from %s. \n",p_station->call_sign,p_station->origin);
9543       }
9544       // packets recieved %d last heard %s
9545       fprintf(f,langcode("WPUPSTI005"),p_station->num_packets, timestring);
9546       if (p_station->tactical_call_sign && p_station->tactical_call_sign[0] != '\0')
9547       {
9548         // tactical call %s
9549         fprintf(f, langcode("WPUPSTI065"), p_station->tactical_call_sign);
9550       }
9551       fprintf(f,"</description>\n");
9552 
9553       // kml specifies w3c's date time format for timestamps
9554       if (get_w3cdtf_datetime(p_station->sec_heard, timestring, False, False))
9555         if (strlen(timestring) > 0)
9556         {
9557           fprintf(f,"<TimeStamp><when>%s</when></TimeStamp>",timestring);
9558         }
9559 
9560       if (current != NULL)
9561       {
9562         // We have trail points, create both a set of time stamp labled point placemarks
9563         // and a linestring placemark to draw the trail.
9564         fprintf(f,"<Point>\n<coordinates>");
9565         if (p_station->altitude[0] != '\0')
9566         {
9567           alt = atoi(p_station->altitude)*10;
9568         }
9569         else
9570         {
9571           alt = -99999l;
9572         }
9573         if (p_station->speed[0] != '\0')
9574         {
9575           speed = (long)(atof(p_station->speed)*18.52);
9576         }
9577         else
9578         {
9579           speed = -1;
9580         }
9581         if (p_station->course[0] != '\0')
9582         {
9583           course = atoi(p_station->course);
9584         }
9585         else
9586         {
9587           course = -1;
9588         }
9589         exp_trailpos(f,p_station->coord_lat,p_station->coord_lon,p_station->sec_heard,speed,course,alt,newtrk, export_format);
9590         fprintf(f,"</coordinates></Point>");
9591 
9592         fprintf(f,"</Placemark>\n");
9593 
9594         // folow with a set of timestamped placemarks for each point on trail
9595         while (current != NULL)
9596         {
9597           lon0   = current->trail_long_pos;                   // Trail segment start
9598           lat0   = current->trail_lat_pos;
9599           sec    = current->sec;
9600           speed  = current->speed;
9601           course = current->course;
9602           alt    = current->altitude;
9603           // kml specifies w3c's date time format for timestamps
9604           if (get_w3cdtf_datetime(sec,timestring,False,False) && (int)sec>0)
9605           {
9606             // point has valid timestamp, write it
9607             fprintf(f,"<Placemark>");
9608             fprintf(f,"<name>%s at %s</name>\n",p_station->call_sign, timestring);
9609             fprintf(f,"<TimeStamp><when>%s</when></TimeStamp>",timestring);
9610             fprintf(f,"<Point><coordinates>");
9611             exp_trailpos(f,lat0,lon0,sec,speed,course,alt,newtrk, export_format);
9612             fprintf(f,"</coordinates></Point>");
9613             fprintf(f,"</Placemark>\n");
9614           }
9615           // Advance to the next point
9616           current = current->next;
9617         }
9618         // Prepare to follow with  a trail (as a <LineString/>).
9619         fprintf(f,"<Placemark>");
9620         if (p_station->origin[0] == '\0')
9621         {
9622           fprintf(f,"<name>%s (trail)</name>\n",p_station->call_sign);
9623         }
9624         else
9625         {
9626           fprintf(f,"<name>%s (trail)</name>\n<description>Object from %s</description>\n",p_station->call_sign,p_station->origin);
9627         }
9628       }
9629       break;
9630 
9631     case EXPORT_XASTIR_TRACK:
9632     default:
9633       if (p_station->origin[0] == '\0')
9634       {
9635         fprintf(f,"\n#C %s\n",p_station->call_sign);
9636       }
9637       else
9638       {
9639         fprintf(f,"\n#O %s %s\n",p_station->call_sign,p_station->origin);
9640       }
9641   }
9642 
9643   // A trail must have at least two points:  One in the struct,
9644   // and one in the tracklog.  If the station only has one point,
9645   // there won't be a tracklog.  If the station has moved, then
9646   // it'll have both.
9647 
9648   // reset current, as we may have moved it past the last trackpoint
9649   // while generating kml above.
9650   current = p_station->oldest_trackpoint;
9651 
9652   if (current != NULL)    // We have trail points, loop through
9653   {
9654     // them.  Skip the most current position
9655     // because it is included in the
9656     // tracklog (if we have a tracklog!).
9657 
9658     switch (export_format)
9659     {
9660       case EXPORT_KML_TRACK:
9661         fprintf(f,"<LineString>\n<coordinates>");
9662         break;
9663         //default:
9664         // no heading for set of points
9665     }
9666     while (current != NULL)
9667     {
9668       lon0   = current->trail_long_pos;                   // Trail segment start
9669       lat0   = current->trail_lat_pos;
9670       sec    = current->sec;
9671       speed  = current->speed;
9672       course = current->course;
9673       alt    = current->altitude;
9674       if ((current->flag & TR_NEWTRK) != '\0')
9675       {
9676         newtrk = 1;
9677       }
9678 
9679       // identical for kml and xastir tracks, but could be different for other formats
9680       switch (export_format)
9681       {
9682         case EXPORT_KML_TRACK:
9683           exp_trailpos(f,lat0,lon0,sec,speed,course,alt,newtrk, export_format);
9684           break;
9685         case EXPORT_XASTIR_TRACK:
9686         default:
9687           exp_trailpos(f,lat0,lon0,sec,speed,course,alt,newtrk, export_format);
9688       }
9689 
9690       newtrk = 0;
9691 
9692       // Advance to the next point
9693       current = current->next;
9694     }
9695     switch (export_format)
9696     {
9697       case EXPORT_KML_TRACK:
9698         fprintf(f,"</coordinates>\n</LineString>\n");
9699         break;
9700         //default:
9701         // no close for set of points
9702     }
9703   }
9704   else    // We don't have any tracklog, so write out the most
9705   {
9706     // current position only.
9707 
9708     if (p_station->altitude[0] != '\0')
9709     {
9710       alt = atoi(p_station->altitude)*10;
9711     }
9712     else
9713     {
9714       alt = -99999l;
9715     }
9716 
9717     if (p_station->speed[0] != '\0')
9718     {
9719       speed = (long)(atof(p_station->speed)*18.52);
9720     }
9721     else
9722     {
9723       speed = -1;
9724     }
9725 
9726     if (p_station->course[0] != '\0')
9727     {
9728       course = atoi(p_station->course);
9729     }
9730     else
9731     {
9732       course = -1;
9733     }
9734 
9735     switch (export_format)
9736     {
9737       case EXPORT_KML_TRACK:
9738         fprintf(f,"<Point>\n\t<coordinates>");
9739         exp_trailpos(f,p_station->coord_lat,p_station->coord_lon,p_station->sec_heard,speed,course,alt,newtrk, export_format);
9740         fprintf(f,"</coordinates>\n\t</Point>\n");
9741         break;
9742       case EXPORT_XASTIR_TRACK:
9743       default:
9744         exp_trailpos(f,p_station->coord_lat,p_station->coord_lon,p_station->sec_heard,speed,course,alt,newtrk, export_format);
9745     }
9746   }
9747 
9748 
9749   switch (export_format)
9750   {
9751     case (EXPORT_KML_TRACK):
9752       fprintf(f,"</Placemark>\n");
9753       break;
9754     case (EXPORT_XASTIR_TRACK):
9755     default:
9756       fprintf(f,"\n");
9757   }
9758 }
9759 
9760 
9761 
9762 
9763 //
9764 // Export trail data for one or all stations to file
9765 //
9766 // If p_station == NULL, store all stations, else store only one
9767 // station.
9768 //
export_trail(DataRow * p_station)9769 void export_trail(DataRow *p_station)
9770 {
9771   char file[420];
9772   FILE *f;
9773   time_t sec;
9774   struct tm *time;
9775   int storeall;
9776   char user_base_dir[MAX_VALUE];
9777 
9778   sec = sec_now();
9779   time  = gmtime(&sec);
9780 
9781   if (p_station == NULL)
9782   {
9783     storeall = 1;
9784   }
9785   else
9786   {
9787     storeall = 0;
9788   }
9789 
9790   if (storeall)
9791   {
9792     // define filename for storing all station
9793     xastir_snprintf(file, sizeof(file),
9794                     "%s/%04d%02d%02d-%02d%02d%02d.trk",
9795                     get_user_base_dir("tracklogs", user_base_dir,
9796                                       sizeof(user_base_dir)),
9797                     time->tm_year+1900,
9798                     time->tm_mon+1,
9799                     time->tm_mday,
9800                     time->tm_hour,
9801                     time->tm_min,
9802                     time->tm_sec);
9803   }
9804   else
9805   {
9806     // define filename for current station
9807     xastir_snprintf(file, sizeof(file), "%s/%s.trk",
9808                     get_user_base_dir("tracklogs", user_base_dir,
9809                                       sizeof(user_base_dir)),
9810                     p_station->call_sign);
9811   }
9812 
9813   // create or open file
9814   (void)filecreate(file);     // create empty file if it doesn't exist
9815   // DK7IN: owner should better be set to user, it is now root with kernel AX.25!
9816 
9817   f=fopen(file,"a");          // open file for append
9818   if (f != NULL)
9819   {
9820 
9821     fprintf(f,
9822             "# WGS-84 tracklog created by Xastir %04d/%02d/%02d %02d:%02d\n",
9823             time->tm_year+1900,
9824             time->tm_mon+1,
9825             time->tm_mday,
9826             time->tm_hour,
9827             time->tm_min);
9828 
9829     if (storeall)
9830     {
9831       p_station = n_first;
9832       while (p_station != NULL)
9833       {
9834         exp_trailstation(f,p_station, EXPORT_XASTIR_TRACK);
9835         p_station = p_station->n_next;
9836       }
9837     }
9838     else
9839     {
9840       exp_trailstation(f,p_station, EXPORT_XASTIR_TRACK);
9841     }
9842     (void)fclose(f);
9843   }
9844   else
9845   {
9846     fprintf(stderr,"Couldn't create or open tracklog file %s\n",file);
9847   }
9848 }
9849 
9850 
9851 
9852 
9853 
9854 //
9855 // Export trail data for one or all stations to a klm file suitable for
9856 // loading into google earth/google maps/NASA worldwind etc.
9857 // For documentation of the KML (Keyhole Markup Language) format,
9858 // see: http://
9859 //
9860 // @param p_station pointer to datarow containing station to export
9861 // If p_station == NULL, store all stations, else store only one
9862 // station.
9863 //
export_trail_as_kml(DataRow * p_station)9864 void export_trail_as_kml(DataRow *p_station)
9865 {
9866   char file[420];
9867   FILE *f;
9868   time_t sec;
9869   struct tm *time;
9870   int storeall;
9871   char user_base_dir[MAX_VALUE];
9872 
9873   sec = sec_now();
9874   time  = gmtime(&sec);
9875 
9876   if (p_station == NULL)
9877   {
9878     storeall = 1;
9879   }
9880   else
9881   {
9882     storeall = 0;
9883   }
9884 
9885   if (storeall)
9886   {
9887     // define filename for storing all station
9888     xastir_snprintf(file, sizeof(file),
9889                     "%s/%04d%02d%02d-%02d%02d%02d.kml",
9890                     get_user_base_dir("tracklogs", user_base_dir,
9891                                       sizeof(user_base_dir)),
9892                     time->tm_year+1900,
9893                     time->tm_mon+1,
9894                     time->tm_mday,
9895                     time->tm_hour,
9896                     time->tm_min,
9897                     time->tm_sec);
9898   }
9899   else
9900   {
9901     // define filename for current station, call + current time.
9902     xastir_snprintf(file, sizeof(file),
9903                     "%s/%s_%04d%02d%02d-%02d%02d%02d.kml",
9904                     get_user_base_dir("tracklogs", user_base_dir,
9905                                       sizeof(user_base_dir)),
9906                     p_station->call_sign,
9907                     time->tm_year+1900,
9908                     time->tm_mon+1,
9909                     time->tm_mday,
9910                     time->tm_hour,
9911                     time->tm_min,
9912                     time->tm_sec);
9913   }
9914 
9915   // create or open file
9916   (void)filecreate(file);     // create empty file if it doesn't exist
9917   // DK7IN: owner should better be set to user, it is now root with kernel AX.25!
9918 
9919   f=fopen(file,"w+");          // open file for writing
9920   if (f != NULL)
9921   {
9922 
9923     fprintf(f,"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://earth.google.com/kml/2.2\">\n<Document>\n<name>APRS Data</name>\n<open>1</open>\n");
9924 
9925     fprintf(f,
9926             "<description>WGS-84 tracklog created by Xastir %04d/%02d/%02d %02d:%02d</description>\n",
9927             time->tm_year+1900,
9928             time->tm_mon+1,
9929             time->tm_mday,
9930             time->tm_hour,
9931             time->tm_min);
9932 
9933     if (storeall)
9934     {
9935       p_station = n_first;
9936       while (p_station != NULL)
9937       {
9938         exp_trailstation(f,p_station,EXPORT_KML_TRACK);
9939         p_station = p_station->n_next;
9940       }
9941     }
9942     else
9943     {
9944       exp_trailstation(f,p_station,EXPORT_KML_TRACK);
9945     }
9946 
9947     fprintf(f,"</Document>\n</kml>");
9948 
9949     (void)fclose(f);
9950   }
9951   else
9952   {
9953     fprintf(stderr,"Couldn't create or open tracklog file %s\n",file);
9954   }
9955 }
9956 
9957 
9958 
9959 //////////////////////////////////////  Station storage  ///////////////////////////////////////////
9960 
9961 // Station storage is done in a double-linked list. In fact there are two such
9962 // pointer structures, one for sorting by name and one for sorting by time.
9963 // We store both the pointers to the next and to the previous elements.  DK7IN
9964 
9965 /*
9966  *  Setup station storage structure
9967  */
init_station_data(void)9968 void init_station_data(void)
9969 {
9970 
9971   station_count = 0;                  // empty station list
9972   n_first = NULL;                     // pointer to next element in name sorted list
9973   n_last  = NULL;                     // pointer to previous element in name sorted list
9974   t_oldest = NULL;                     // pointer to oldest element in time sorted list
9975   t_newest  = NULL;                     // pointer to newest element in time sorted list
9976   last_sec = sec_now();               // check value for detecting changed seconds in time
9977   next_time_sn = 0;                   // serial number for unique time index
9978   current_trail_color = 0x00;         // first trail color used will be 0x01
9979   last_station_remove = sec_now();    // last time we checked for stations to remove
9980 }
9981 
9982 
9983 
9984 
9985 
9986 /*
9987  *  Initialize station data
9988  */
init_station(DataRow * p_station)9989 void init_station(DataRow *p_station)
9990 {
9991   // the list pointers should already be set
9992   p_station->oldest_trackpoint  = NULL;         // no trail
9993   p_station->newest_trackpoint  = NULL;         // no trail
9994   p_station->trail_color        = 0;
9995   p_station->weather_data       = NULL;         // no weather
9996   p_station->coord_lat          = 0l;           //  90°N  \ undefined
9997   p_station->coord_lon          = 0l;           // 180°W  / position
9998   p_station->pos_amb            = 0;            // No ambiguity
9999   p_station->error_ellipse_radius = 600;        // In cm, default 6 meters
10000   p_station->lat_precision      = 60;           // In 100ths of seconds latitude (60 = 0.01 minutes)
10001   p_station->lon_precision      = 60;           // In 100ths of seconds longitude (60 = 0.01 minutes)
10002   p_station->call_sign[0]       = '\0';         // ?????
10003   p_station->tactical_call_sign = NULL;
10004   p_station->sec_heard          = 0;
10005   p_station->time_sn            = 0;
10006   p_station->flag               = 0;            // set all flags to inactive
10007   p_station->object_retransmit  = -1;           // transmit forever
10008   p_station->last_transmit_time = sec_now();    // Used for object/item decaying algorithm
10009   p_station->transmit_time_increment = 0;       // Used in data_add()
10010   //    p_station->last_modified_time = 0;            // Used for object/item dead-reckoning
10011   p_station->record_type        = '\0';
10012   p_station->data_via           = '\0';         // L local, T TNC, I internet, F file
10013   p_station->heard_via_tnc_port = 0;
10014   p_station->heard_via_tnc_last_time = 0;
10015   p_station->last_port_heard    = 0;
10016   p_station->num_packets        = 0;
10017   p_station->aprs_symbol.aprs_type = '\0';
10018   p_station->aprs_symbol.aprs_symbol = '\0';
10019   p_station->aprs_symbol.special_overlay = '\0';
10020   p_station->aprs_symbol.area_object.type           = AREA_NONE;
10021   p_station->aprs_symbol.area_object.color          = AREA_GRAY_LO;
10022   p_station->aprs_symbol.area_object.sqrt_lat_off   = 0;
10023   p_station->aprs_symbol.area_object.sqrt_lon_off   = 0;
10024   p_station->aprs_symbol.area_object.corridor_width = 0;
10025   //    p_station->station_time_type  = '\0';
10026   p_station->origin[0]          = '\0';        // no object
10027   p_station->packet_time[0]     = '\0';
10028   p_station->node_path_ptr      = NULL;
10029   p_station->pos_time[0]        = '\0';
10030   //    p_station->altitude_time[0]   = '\0';
10031   p_station->altitude[0]        = '\0';
10032   //    p_station->speed_time[0]      = '\0';
10033   p_station->speed[0]           = '\0';
10034   p_station->course[0]          = '\0';
10035   p_station->bearing[0]         = '\0';
10036   p_station->NRQ[0]             = '\0';
10037   p_station->power_gain[0]      = '\0';
10038   p_station->signal_gain[0]     = '\0';
10039   p_station->signpost[0]        = '\0';
10040   p_station->probability_min[0] = '\0';
10041   p_station->probability_max[0] = '\0';
10042   //    p_station->station_time[0]    = '\0';
10043   p_station->sats_visible[0]    = '\0';
10044   p_station->status_data        = NULL;
10045   p_station->comment_data       = NULL;
10046   p_station->df_color           = -1;
10047 
10048   // Show that there are no other points associated with this
10049   // station. We could also zero all the entries of the
10050   // multipoints[][] array, but nobody should be looking there
10051   // unless this is non-zero.
10052   // KG4NBB
10053 
10054   p_station->num_multipoints = 0;
10055   p_station->multipoint_data = NULL;
10056 }
10057 
10058 
10059 
10060 
10061 
10062 /*
10063  *  Remove element from name ordered list
10064  */
remove_name(DataRow * p_rem)10065 void remove_name(DataRow *p_rem)        // todo: return pointer to next element
10066 {
10067   int update_shortcuts = 0;
10068   int hash_key;   // We use a 14-bit hash key
10069 
10070 
10071   // Do a quick check to see if we're removing a station record
10072   // that is pointed to by our pointer shortcuts array.
10073   // If so, update our pointer shortcuts after we're done.
10074   //
10075   // We create the hash key out of the lower 7 bits of the first
10076   // two characters, creating a 14-bit key (1 of 16384)
10077   //
10078   hash_key = (int)((p_rem->call_sign[0] & 0x7f) << 7);
10079   hash_key = hash_key | (int)(p_rem->call_sign[1] & 0x7f);
10080 
10081   if (station_shortcuts[hash_key] == p_rem)
10082   {
10083     // Yes, we're trying to remove a record that a hash key
10084     // directly points to.  We'll need to redo that hash key
10085     // after we remove the record.
10086     update_shortcuts++;
10087   }
10088 
10089 
10090   // Proceed to the station record removal
10091   //
10092   if (p_rem->n_prev == NULL)   // Appears to be first element in list
10093   {
10094 
10095     if (n_first == p_rem)    // Yes, head of list
10096     {
10097 
10098       // Make list head point to 2nd element in list (or NULL)
10099       // so that we can delete the current record.
10100       n_first = p_rem->n_next;
10101     }
10102     else    // No, not first element in list.  Problem!  The
10103     {
10104       // list pointers are inconsistent for some reason.
10105       // The chain has been broken and we have dangling
10106       // pointers.
10107 
10108       fprintf(stderr,
10109               "remove_name(): ERROR: p->n_prev == NULL but p != n_first\n");
10110 
10111       abort();    // Cause a core dump at this point
10112       // Perhaps we could do some repair to the list pointers here?  Start
10113       // at the other end of the chain and navigate back to this end, then
10114       // fix up n_first to point to it?  This is at the risk of a memory
10115       // leak, but at least Xastir might continue to run.
10116 
10117     }
10118   }
10119   else    // Not the first element in the list.  Fix up pointers
10120   {
10121     // to skip the current record.
10122     p_rem->n_prev->n_next = p_rem->n_next;
10123   }
10124 
10125 
10126   if (p_rem->n_next == NULL)   // Appears to be last element in list
10127   {
10128 
10129     if (n_last == p_rem)     // Yes, tail of list
10130     {
10131 
10132       // Make list tail point to previous element in list (or
10133       // NULL) so that we can delete the current record.
10134       n_last = p_rem->n_prev;
10135     }
10136     else    // No, not last element in list.  Problem!  The list
10137     {
10138       // pointers are inconsistent for some reason.  The
10139       // chain has been broken and we have dangling
10140       // pointers.
10141 
10142       fprintf(stderr,
10143               "remove_name(): ERROR: p->n_next == NULL but p != n_last\n");
10144 
10145       abort();    // Cause a core dump at this point
10146       // Perhaps we could do some repair to the list pointers here?  Start
10147       // at the other end of the chain and navigate back to this end, then
10148       // fix up n_last to point to it?  This is at the risk of a memory
10149       // leak, but at least Xastir might continue to run.
10150 
10151     }
10152   }
10153   else    // Not the last element in the list.  Fix up pointers to
10154   {
10155     // skip the current record.
10156     p_rem->n_next->n_prev = p_rem->n_prev;
10157   }
10158 
10159 
10160   // Update our pointer shortcuts.  Pass the removed hash_key to
10161   // the function so that we can try to redo just that hash_key
10162   // pointer.
10163   if (update_shortcuts)
10164   {
10165     //fprintf(stderr,"\t\t\t\t\t\tRemoval of hash key: %i\n", hash_key);
10166 
10167     // The -1 tells the function to redo all of the hash table
10168     // pointers because we deleted one of them.  Later we could
10169     // optimize this so that only the specific pointer is fixed
10170     // up.
10171     station_shortcuts_update_function(-1, NULL);
10172   }
10173 }
10174 
10175 
10176 
10177 
10178 
10179 /*
10180  *  Remove element from time ordered list
10181  */
remove_time(DataRow * p_rem)10182 void remove_time(DataRow *p_rem)        // todo: return pointer to next element
10183 {
10184 
10185   if (p_rem->t_older == NULL)   // Appears to be first element in list
10186   {
10187 
10188     if (t_oldest == p_rem)    // Yes, head of list (oldest)
10189     {
10190 
10191       // Make oldest list head point to 2nd element in list (or NULL)
10192       // so that we can delete the current record.
10193       t_oldest = p_rem->t_newer;
10194     }
10195     else    // No, not first (oldest) element in list.  Problem!
10196     {
10197       // The list pointers are inconsistent for some
10198       // reason.  The chain has been broken and we have
10199       // dangling pointers.
10200 
10201       fprintf(stderr,
10202               "remove_time(): ERROR: p->t_older == NULL but p != t_oldest\n");
10203 
10204       abort();    // Cause a core dump at this point
10205       // Perhaps we could do some repair to the list pointers here?  Start
10206       // at the other end of the chain and navigate back to this end, then
10207       // fix up t_oldest to point to it?  This is at the risk of a memory
10208       // leak, but at least Xastir might continue to run.
10209 
10210     }
10211   }
10212   else    // Not the first (oldest) element in the list.  Fix up
10213   {
10214     // pointers to skip the current record.
10215     p_rem->t_older->t_newer = p_rem->t_newer;
10216   }
10217 
10218 
10219   if (p_rem->t_newer == NULL)   // Appears to be last (newest) element in list
10220   {
10221 
10222     if (t_newest == p_rem)     // Yes, head of list (newest)
10223     {
10224 
10225       // Make newest list head point to previous element in
10226       // list (or NULL) so that we can delete the current
10227       // record.
10228       t_newest = p_rem->t_older;
10229     }
10230     else    // No, not newest element in list.  Problem!  The
10231     {
10232       // list pointers are inconsistent for some reason.
10233       // The chain has been broken and we have dangling
10234       // pointers.
10235 
10236       fprintf(stderr,
10237               "remove_time(): ERROR: p->t_newer == NULL but p != t_newest\n");
10238 
10239       abort();    // Cause a core dump at this point
10240       // Perhaps we could do some repair to the list pointers here?  Start
10241       // at the other end of the chain and navigate back to this end, then
10242       // fix up t_newest to point to it?  This is at the risk of a memory
10243       // leak, but at least Xastir might continue to run.
10244 
10245     }
10246   }
10247   else    // Not the newest element in the list.  Fix up pointers
10248   {
10249     // to skip the current record.
10250     p_rem->t_newer->t_older = p_rem->t_older;
10251   }
10252 }
10253 
10254 
10255 
10256 
10257 
10258 /*
10259  *  Insert existing element into name ordered list before p_name.
10260  *  If p_name is NULL then we add it to the end instead.
10261  */
insert_name(DataRow * p_new,DataRow * p_name)10262 void insert_name(DataRow *p_new, DataRow *p_name)
10263 {
10264 
10265   // Set up pointer to next record (or NULL), sorted by name
10266   p_new->n_next = p_name;
10267 
10268   if (p_name == NULL)         // Add to end of list
10269   {
10270 
10271     p_new->n_prev = n_last;
10272 
10273     if (n_last == NULL)     // If we have an empty list
10274     {
10275       n_first = p_new;  // Add it to the head of the list
10276     }
10277 
10278     else    // List wasn't empty, add to the end of the list.
10279     {
10280       n_last->n_next = p_new;
10281     }
10282 
10283     n_last = p_new;
10284   }
10285 
10286   else    // Insert new record ahead of p_name record
10287   {
10288 
10289     p_new->n_prev = p_name->n_prev;
10290 
10291     if (p_name->n_prev == NULL)     // add to begin of list
10292     {
10293       n_first = p_new;
10294     }
10295     else
10296     {
10297       p_name->n_prev->n_next = p_new;
10298     }
10299 
10300     p_name->n_prev = p_new;
10301   }
10302 }
10303 
10304 
10305 
10306 
10307 
10308 /*
10309  *  Insert existing element into time ordered list before p_time
10310  *  The p_new record ends up being on the "older" side of p_time when
10311  *  all done inserting (closer in the list to the t_oldest pointer).
10312  *  If p_time == NULL, insert at newest end of list.
10313  */
insert_time(DataRow * p_new,DataRow * p_time)10314 void insert_time(DataRow *p_new, DataRow *p_time)
10315 {
10316 
10317   // Set up pointer to next record (or NULL), sorted by time
10318   p_new->t_newer = p_time;
10319 
10320   if (p_time == NULL)                 // add to end of list (becomes newest station)
10321   {
10322 
10323     p_new->t_older = t_newest;         // connect to previous end of list
10324 
10325     if (t_newest == NULL)             // if list empty, create list
10326     {
10327       t_oldest = p_new;  // it's now our only station on the list
10328     }
10329     else
10330     {
10331       t_newest->t_newer = p_new;  // list not empty, link original last record to our new one
10332     }
10333 
10334     t_newest = p_new;                 // end of list (newest record pointer) points to our new record
10335   }
10336 
10337   else                              // Else we're inserting into the middle of the list somewhere
10338   {
10339 
10340     p_new->t_older = p_time->t_older;
10341 
10342     if (p_time->t_older == NULL)     // add to end of list (new record becomes oldest station)
10343     {
10344       t_oldest = p_new;
10345     }
10346     else
10347     {
10348       p_time->t_older->t_newer = p_new;  // else
10349     }
10350 
10351     p_time->t_older = p_new;
10352   }
10353 }
10354 
10355 
10356 
10357 
10358 
10359 /*
10360  *  Free station memory for one entry
10361  */
delete_station_memory(DataRow * p_del)10362 void delete_station_memory(DataRow *p_del)
10363 {
10364   if (p_del == NULL)
10365   {
10366     return;
10367   }
10368   remove_name(p_del);
10369   remove_time(p_del);
10370   free(p_del);
10371   station_count--;
10372 }
10373 
10374 
10375 
10376 
10377 
10378 /*
10379  *  Create new uninitialized element in station list
10380  *  and insert it before p_name after p_time entries.
10381  *
10382  *  Returns NULL if malloc error.
10383  */
insert_new_station(DataRow * p_name,DataRow * p_time)10384 /*@null@*/ DataRow *insert_new_station(DataRow *p_name, DataRow *p_time)
10385 {
10386   DataRow *p_new;
10387 
10388   p_new = (DataRow *)calloc(1, sizeof(DataRow));
10389 
10390   if (p_new != NULL)                  // we really got the memory
10391   {
10392     insert_name(p_new,p_name);      // insert element into name ordered list
10393     insert_time(p_new,p_time);      // insert element into time ordered list
10394   }
10395   else    // p_new == NULL
10396   {
10397     fprintf(stderr,"ERROR: we got no memory for station storage\n");
10398   }
10399 
10400   return(p_new);                      // return pointer to new element
10401 }
10402 
10403 
10404 
10405 
10406 
10407 /*
10408  *  Create new initialized element for call in station list
10409  *  and insert it before p_name after p_time entries.
10410  *
10411  *  Returns NULL if mallc error.
10412  */
add_new_station(DataRow * p_name,DataRow * p_time,char * call)10413 /*@null@*/ DataRow *add_new_station(DataRow *p_name, DataRow *p_time, char *call)
10414 {
10415   DataRow *p_new;
10416   int hash_key;   // We use a 14-bit hash key
10417   char *tactical_call;
10418 
10419 
10420   if (call[0] == '\0')
10421   {
10422     // Do nothing.  No update needed.  Callsign is empty.
10423     return(NULL);
10424   }
10425 
10426   //fprintf(stderr,"Adding new station: %s\n",call);
10427 
10428   p_new = insert_new_station(p_name,p_time);  // allocate memory
10429 
10430   if (p_new == NULL)
10431   {
10432     // Couldn't allocate space for the station
10433     return(NULL);
10434   }
10435 
10436   init_station(p_new);                    // initialize new station record
10437   xastir_snprintf(p_new->call_sign,
10438                   sizeof(p_new->call_sign),
10439                   "%s",
10440                   call);
10441   station_count++;
10442 
10443   // Do some quick checks to see if we just inserted a new hash
10444   // key or inserted at the beginning of a hash key (making the
10445   // old pointer incorrect).  If so, update our pointers to match.
10446 
10447   // We create the hash key out of the lower 7 bits of the first
10448   // two characters, creating a 14-bit key (1 of 16384)
10449   //
10450   hash_key = (int)((call[0] & 0x7f) << 7);
10451   hash_key = hash_key | (int)(call[1] & 0x7f);
10452 
10453   if (station_shortcuts[hash_key] == NULL)
10454   {
10455     // New hash key entry point found.  Fill in the pointer.
10456     //fprintf(stderr,"New hash key: %i, call: %s\n",
10457     //    hash_key,
10458     //    call);
10459 
10460     station_shortcuts_update_function(hash_key, p_new);
10461   }
10462   else if (p_new->n_prev == NULL)
10463   {
10464     // We just inserted at the beginning of the list.  Assume
10465     // that we inserted at the beginning of our hash_key
10466     // segment.
10467     //fprintf(stderr,"Start of list hash_key: %i, call: %s\n",
10468     //    hash_key,
10469     //    call);
10470 
10471     station_shortcuts_update_function(hash_key, p_new);
10472   }
10473   else
10474   {
10475     // Check whether either of the first two chars of the new
10476     // callsign and the previous callsign are different.  If so,
10477     // we need to update the hash table entry for our new record
10478     // 'cuz we're at the start of a new hash table entry.
10479     if (p_new->n_prev->call_sign[0] != call[0]
10480         || p_new->n_prev->call_sign[1] != call[1])
10481     {
10482       //fprintf(stderr,"Hash segment start: %i, call: %s\n",
10483       //    hash_key,
10484       //    call);
10485 
10486       station_shortcuts_update_function(hash_key, p_new);
10487     }
10488   }
10489 
10490   //if (p_new->n_prev != NULL) {
10491   //    fprintf(stderr,"\tprev: %s",
10492   //        p_new->n_prev->call_sign);
10493   //}
10494 
10495   //if (p_new->n_next != NULL) {
10496   //    fprintf(stderr,"\t\tnext: %s",
10497   //        p_new->n_next->call_sign);
10498   //}
10499   //
10500   //fprintf(stderr,"\n");
10501 
10502   // Check whether we have a tactical call to assign to this
10503   // station in our tactical hash table.
10504   //fprintf(stderr,"Call:'%s'\n", call);
10505   tactical_call = get_tactical_from_hash(call);
10506 
10507   // If tactical call found and not blank
10508   if (tactical_call && tactical_call[0] != '\0')
10509   {
10510 
10511     // Malloc some memory to hold it in the station record.
10512     p_new->tactical_call_sign = (char *)malloc(MAX_TACTICAL_CALL+1);
10513     CHECKMALLOC(p_new->tactical_call_sign);
10514 
10515     //fprintf(stderr,"***Assigning tactical call to new record***\n");
10516     xastir_snprintf(p_new->tactical_call_sign,
10517                     MAX_TACTICAL_CALL+1,
10518                     "%s",
10519                     tactical_call);
10520 
10521     //if (tactical_call[0] == '\0')
10522     //    fprintf(stderr,"Blank tactical call\n");
10523   }
10524   else
10525   {
10526     //fprintf(stderr,".");
10527   }
10528 
10529   return(p_new);                      // return pointer to new element
10530 }
10531 
10532 
10533 
10534 
10535 #ifdef HAVE_DB
10536 /* function add_simple_station()
10537  * adds an xastir DataRow using station and additional data from a simpleStation
10538  * record in a SQL database.
10539  * @param p_new_station Pointer to a DataRow for the new station, probably initalized as DataRow p_new_station = NULL
10540  * @param station  String pointer for the callsign or object name
10541  * @param origin   String pointer for the callsign for an object
10542  * @param symbol   String pointer to an aprs symbol, will take the first character
10543  * @param overlay  String pointer to an aprs overlay, will take the first character
10544  * @param aprs_type String pointer to an aprs type, will take the first character
10545  * @param latitude  in decimal degrees
10546  * @param longitude in decimal degrees
10547  * @param record_type
10548  * @param node_path
10549  * @param transmit_time Time at which the station position was transmitted in a string pointer with format described by timeformat
10550  * @param timeformat Format for the transmit_time, e.g. "%Y-%M-%D %h:%d:%m" see documentation for strptime
10551  *
10552  * @returns 0 if unable to add new station (p_new_station should be null)
10553  * otherwise returns 1 (and p_new_station should be a pointer to the DataRow
10554  * for the new station record.
10555  */
add_simple_station(DataRow * p_new_station,char * station,char * origin,char * symbol,char * overlay,char * aprs_type,char * latitude,char * longitude,char * record_type,char * node_path,char * transmit_time,char * timeformat)10556 int add_simple_station(DataRow *p_new_station,char *station, char *origin, char *symbol, char *overlay, char *aprs_type, char *latitude, char *longitude, char *record_type, char *node_path, char *transmit_time, char *timeformat)
10557 {
10558   int returnvalue = 0;
10559   unsigned long x;  // xastir coordinate for longitude
10560   unsigned long y;  // xastir coordinate for latitide
10561   float lat;  // latitude converted from retrieved string
10562   float lon;  // longitude converted from retrieved string
10563   DataRow *p_time;  // pointer to new station record
10564   //DataRow *p_new_station_unused;
10565   struct tm time;
10566   time_t sec;
10567   char timestring[100+1];
10568   char empty[MAX_ALTITUDE];  // for storing trailpoint data (altitude, course, speed) we don't know here.
10569   empty[0]='\0';
10570 
10571   // Add a datarow using the retrieved station record from the postgis/mysql database.
10572   p_time = NULL;
10573   p_new_station = NULL;
10574 
10575   if (debug_level & 4096)
10576   {
10577     fprintf(stderr,"add_simple_station(%s)\n",station);
10578   }
10579 
10580   if (search_station_name(&p_new_station,station,1))
10581   {
10582     // A datarow for this station exists, find out if the new record
10583     // is older or younger than the existing DataRow for this station
10584     strptime(transmit_time,timeformat,&time);
10585     p_new_station->sec_heard = mktime(&time);
10586     if(p_new_station->sec_heard > mktime(&time))
10587     {
10588       // Add the new record as a trailpoint.
10589       if (strlen(transmit_time) > 0)
10590       {
10591         strptime(transmit_time, timeformat, &time);
10592         sec = mktime(&time);
10593         lat = strtof(latitude,NULL);
10594         lon = strtof(longitude,NULL);
10595         if (convert_to_xastir_coordinates (&x, &y, lon, lat))
10596         {
10597           (void)store_trail_point(p_new_station, x, y, sec, empty, empty, empty, 0);
10598         }
10599       }
10600 
10601       // all done
10602       returnvalue = 1;
10603     }
10604     else
10605     {
10606       // Append the position of the existing record as a trailpoint
10607       // and set the station DataRow to the new values.
10608       (void)store_trail_point(p_new_station, p_new_station->coord_lon, p_new_station->coord_lat, p_new_station->sec_heard, empty, empty, empty, 0);
10609     }
10610   }
10611   else
10612   {
10613     // add a new station
10614     p_new_station = add_new_station(p_new_station,p_time,station);
10615   }
10616   if(returnvalue==0)
10617   {
10618     // Set the values for the p_new_station DataRow based on the
10619     // supplied parameters.  At this point p_new_station might
10620     // be either a brand new station record, or an existing
10621     // station record for the callsign that we were passed.
10622     if (!(p_new_station==NULL))
10623     {
10624       // set values for new station based on the database row
10625       xastir_snprintf(p_new_station->origin,58,"%s",origin);
10626       p_new_station->aprs_symbol.aprs_symbol = symbol[0];
10627       p_new_station->aprs_symbol.special_overlay = overlay[0];
10628       p_new_station->aprs_symbol.aprs_type = aprs_type[0];
10629       lat = strtof(latitude,NULL);
10630       lon = strtof(longitude,NULL);
10631       if (convert_to_xastir_coordinates (&x, &y, lon, lat))
10632       {
10633         p_new_station->coord_lon = x;
10634         p_new_station->coord_lat = y;
10635       }
10636       p_new_station->record_type = record_type[0];
10637       // free node path, Malloc, and store the new path
10638       if (p_new_station->node_path_ptr != NULL)
10639       {
10640         free(p_new_station->node_path_ptr);
10641       }
10642       p_new_station->node_path_ptr = (char *)malloc(strlen(node_path) + 1);
10643       CHECKMALLOC(p_new_station->node_path_ptr);
10644       substr(p_new_station->node_path_ptr,node_path,strlen(node_path));
10645 
10646       // also set flags for the station
10647       p_new_station->flag |= ST_ACTIVE;
10648       if (position_on_extd_screen(p_new_station->coord_lat,p_new_station->coord_lon))
10649       {
10650         p_new_station->flag |= (ST_INVIEW);   // set   "In View" flag
10651       }
10652       else
10653       {
10654         p_new_station->flag &= (~ST_INVIEW);  // clear "In View" flag
10655       }
10656       p_new_station->data_via = DATA_VIA_DATABASE;  // treat as data from a file.
10657       if (strlen(transmit_time) > 0)
10658       {
10659         //strptime(transmit_time,"%Y-%m-%d %H:%M:%S",&time);
10660         strptime(transmit_time,timeformat,&time);
10661         p_new_station->sec_heard = mktime(&time);
10662         if (debug_level & 4096)
10663         {
10664           get_iso_datetime(p_new_station->sec_heard,timestring,False,False);
10665           fprintf(stderr,"time %s to [%s] using [%s]\n",transmit_time,timestring,timeformat);
10666         }
10667         if (p_new_station->sec_heard > sec_now())
10668         {
10669           p_new_station->sec_heard = sec_now();
10670         }
10671         (void)strftime(timestring,MAX_TIME,"%m%d%Y%H%M%S",&time);
10672         xastir_snprintf(p_new_station->pos_time,
10673                         sizeof(p_new_station->pos_time),
10674                         "%s",
10675                         timestring);
10676       }
10677       returnvalue = 1;
10678     }
10679   }
10680   return returnvalue;
10681 }
10682 #endif /* HAVE_DB */
10683 
10684 
10685 
10686 
10687 
10688 /*
10689  *  Move station record before p_time in time ordered list
10690  */
move_station_time(DataRow * p_curr,DataRow * p_time)10691 void move_station_time(DataRow *p_curr, DataRow *p_time)
10692 {
10693 
10694   if (p_curr != NULL)                 // need a valid record
10695   {
10696     remove_time(p_curr);
10697     insert_time(p_curr,p_time);
10698   }
10699 }
10700 
10701 
10702 
10703 
10704 
10705 /*
10706  *  Move station record before p_name in name ordered list
10707  */
move_station_name(DataRow * p_curr,DataRow * p_name)10708 void move_station_name(DataRow *p_curr, DataRow *p_name)
10709 {
10710 
10711   if (p_curr != NULL)                 // need a valid record
10712   {
10713     remove_name(p_curr);
10714     insert_name(p_curr,p_name);
10715   }
10716 }
10717 
10718 
10719 
10720 
10721 
10722 // Update all of the pointers so that they accurately reflect the
10723 // current state of the station database.
10724 //
10725 // NOTE:  This part of the code could be made smarter so that the
10726 // pointers are updated whenever they are found to be out of whack,
10727 // instead of zeroing all of them and starting from scratch each
10728 // time.  Alternate:  Follow the current pointer if non-NULL then go
10729 // up/down the list to find the current switchover point between
10730 // letters.
10731 //
10732 // Better:  Tie into the station insert function.  If a new letter
10733 // is inserted, or a new station at the beginning of a letter group,
10734 // run this function to keep things up to date.  That way we won't
10735 // have to traverse in both directions to find a callsign in the
10736 // search_station_name() function.
10737 //
10738 // If hash_key_in is -1, we need to redo all of the hash keys.  If
10739 // it is between 0 and 16383, then we need to redo just that one
10740 // hash key.  The 2nd parameter is either NULL for a removed record,
10741 // or a pointer to a new station record in the case of an addition.
10742 //
station_shortcuts_update_function(int hash_key_in,DataRow * p_rem)10743 void station_shortcuts_update_function(int hash_key_in, DataRow *p_rem)
10744 {
10745   int ii;
10746   DataRow *ptr;
10747   int prev_hash_key = 0x0000;
10748   int hash_key;
10749 
10750 
10751   // I just changed the function so that we can pass in the hash_key
10752   // that we wish to update:  We should be able to speed things up by
10753   // updating one hash key instead of all 16384 pointers.
10754 
10755   if ( (hash_key_in != -1)
10756        && (hash_key_in >= 0)
10757        && (hash_key_in < 16384) )
10758   {
10759 
10760     // We're adding/changing a hash key entry
10761     station_shortcuts[hash_key_in] = p_rem;
10762     //fprintf(stderr,"%i ",hash_key_in);
10763   }
10764   else    // We're removing a hash key entry.
10765   {
10766 
10767     // Clear and rebuild the entire hash table.
10768 
10769     //??????????????????????????????????????????????????
10770     // Clear all of the pointers before we begin????
10771     //??????????????????????????????????????????????????
10772     for (ii = 0; ii < 16384; ii++)
10773     {
10774       station_shortcuts[ii] = NULL;
10775     }
10776 
10777     ptr = n_first;  // Start of list
10778 
10779 
10780     // Loop through entire list, writing the pointer into the
10781     // station_shortcuts array whenever a new character is
10782     // encountered.  Do this until the end of the array or the end
10783     // of the list.
10784     //
10785     while ( (ptr != NULL) && (prev_hash_key < 16384) )
10786     {
10787 
10788       // We create the hash key out of the lower 7 bits of the
10789       // first two characters, creating a 14-bit key (1 of 16384)
10790       //
10791       hash_key = (int)((ptr->call_sign[0] & 0x7f) << 7);
10792       hash_key = hash_key | (int)(ptr->call_sign[1] & 0x7f);
10793 
10794       if (hash_key > prev_hash_key)
10795       {
10796 
10797         // We found the next hash_key.  Store the pointer at the
10798         // correct location.
10799         if (hash_key < 16384)
10800         {
10801           station_shortcuts[hash_key] = ptr;
10802           //fprintf(stderr,"%i ", hash_key);
10803         }
10804         prev_hash_key = hash_key;
10805       }
10806       ptr = ptr->n_next;
10807     }
10808     //fprintf(stderr,"\n");
10809 
10810   }
10811 
10812 }
10813 
10814 
10815 
10816 
10817 
10818 //
10819 // Search station record by callsign
10820 // Returns a station with a call equal or after the searched one
10821 //
10822 // We use a doubly-linked list for the stations, so we can traverse
10823 // in either direction.  We also use a 14-bit hash table created
10824 // from the first two letters of the call to dump us into the
10825 // beginning of the correct area that may hold the callsign, which
10826 // reduces search time quite a bit.  We end up doing a linear search
10827 // only through a small area of the linked list.
10828 //
10829 // DK7IN:  I don't look at case, objects and internet names could
10830 // have lower case.
10831 //
search_station_name(DataRow ** p_name,char * call,int exact)10832 int search_station_name(DataRow **p_name, char *call, int exact)
10833 {
10834   int kk;
10835   int hash_key;
10836   int result;
10837   int ok = 1;
10838 
10839 
10840   (*p_name) = n_first;                                // start of alphabet
10841 
10842   if (call[0] == '\0')
10843   {
10844     // If call we're searching for is empty, return n_first as
10845     // the pointer.
10846     return(0);
10847   }
10848 
10849   // We create the hash key out of the lower 7 bits of the first
10850   // two characters, creating a 14-bit key (1 of 16384)
10851   //
10852   hash_key = (int)((call[0] & 0x7f) << 7);
10853   hash_key = hash_key | (int)(call[1] & 0x7f);
10854 
10855   // Look for a match using hash table lookup
10856   //
10857   (*p_name) = station_shortcuts[hash_key];
10858 
10859   if ((*p_name) == NULL)      // No hash-table entry found.
10860   {
10861     int mm;
10862 
10863     //fprintf(stderr,"No hash-table entry found: call:%s\n",call);
10864 
10865 
10866     // No index found for that letter.  Walk the array until
10867     // we find an entry that is filled.  That'll be our
10868     // potential insertion point (insertion into the list will
10869     // occur just ahead of the hash entry).
10870     for (mm = hash_key; mm < 16384; mm++)
10871     {
10872       if (station_shortcuts[mm] != NULL)
10873       {
10874         (*p_name) = station_shortcuts[mm];
10875         break;
10876       }
10877     }
10878   }
10879 //    else {
10880 //fprintf(stderr,"Hash key %d=%s, searching for call: %s\n",
10881 //    hash_key,
10882 //    (*p_name)->call_sign,
10883 //    call);
10884 //    }
10885 
10886   // If we got to this point, we either have a NULL pointer or a
10887   // real hash-table pointer entry.  A non-NULL pointer means that
10888   // we have a match for the lower seven bits of the first two
10889   // characters of the callsign.  Check the rest of the callsign,
10890   // and jump out of the loop if we get outside the linear search
10891   // area (if first two chars are different).
10892 
10893   kk = (int)strlen(call);
10894 
10895   // Search linearly through list.  Stop at end of list or break.
10896   while ( (*p_name) != NULL)
10897   {
10898 
10899     if (exact)
10900     {
10901       // Check entire string for exact match
10902       result = strcmp( call, (*p_name)->call_sign );
10903     }
10904     else
10905     {
10906       // Check first part of string for match
10907       result = strncmp( call, (*p_name)->call_sign, kk );
10908     }
10909 
10910     if (result < 0)     // We went past the right location.
10911     {
10912       // We're done.
10913       ok = 0;
10914       //fprintf(stderr,"Went past possible entry point, searching for call: %s\n",call);
10915       break;
10916     }
10917     else if (result == 0)   // Found a possible match
10918     {
10919       //fprintf(stderr,"Found possible match: list:%s call:%s\n",
10920       //    (*p_name)->call_sign,
10921       //    call);
10922       break;
10923     }
10924     else    // Result > 0.  We haven't found it yet.
10925     {
10926       (*p_name) = (*p_name)->n_next;  // Next element in list
10927     }
10928   }
10929 
10930   // Did we find anything?
10931   if ( (*p_name) == NULL)
10932   {
10933     ok = 0;
10934     //fprintf(stderr,"End of list reached, call: %s\n",call);
10935     return(ok); // Nope.  No match found.
10936   }
10937 
10938   // If "exact" is set, check that the string lengths match as
10939   // well.  If not, we didn't find it.
10940   if (exact && ok && strlen((*p_name)->call_sign) != strlen(call))
10941   {
10942     ok = 0;
10943   }
10944 
10945   return(ok);         // if not ok: p_name points to correct insert position in name list
10946 }
10947 
10948 
10949 
10950 
10951 
10952 /*
10953  *  Search station record by time and time serial number, serial ignored if -1
10954  *  Returns a station that is equal or older than the search criterium
10955  */
search_station_time(DataRow ** p_time,time_t heard,int serial)10956 int search_station_time(DataRow **p_time, time_t heard, int serial)
10957 {
10958   int ok = 1;
10959 
10960   (*p_time) = t_newest;                                 // newest station
10961   if (heard == 0)                                     // we want the newest station
10962   {
10963     if (t_newest == NULL)
10964     {
10965       ok = 0;  // empty list
10966     }
10967   }
10968   else
10969   {
10970     while((*p_time) != NULL)                        // check time
10971     {
10972       if ((*p_time)->sec_heard <= heard)          // compare
10973       {
10974         break;  // found time or earlier
10975       }
10976       (*p_time) = (*p_time)->t_older;              // next element
10977     }
10978     // we now probably have found the entry
10979     if ((*p_time) != NULL && (*p_time)->sec_heard == heard)
10980     {
10981       // we got a match, but there may be more of them
10982       if (serial >= 0)                            // check serial number, ignored if -1
10983       {
10984         while((*p_time) != NULL)                // for unique time index
10985         {
10986           if ((*p_time)->sec_heard == heard && (*p_time)->time_sn <= serial)  // compare
10987           {
10988             break;  // found it (same time, maybe earlier SN)
10989           }
10990           if ((*p_time)->sec_heard < heard)   // compare
10991           {
10992             break;  // found it (earlier time)
10993           }
10994           (*p_time) = (*p_time)->t_older;      // consider next element
10995         }
10996         if ((*p_time) == NULL || (*p_time)->sec_heard != heard || (*p_time)->time_sn != serial)
10997         {
10998           ok = 0;  // no perfect match
10999         }
11000       }
11001     }
11002     else
11003     {
11004       ok = 0;  // no perfect match
11005     }
11006   }
11007   return(ok);
11008 }
11009 
11010 
11011 
11012 
11013 
11014 /*
11015  *  Get pointer to next station in name sorted list
11016  */
next_station_name(DataRow ** p_curr)11017 int next_station_name(DataRow **p_curr)
11018 {
11019 
11020   if ((*p_curr) == NULL)
11021   {
11022     (*p_curr) = n_first;
11023   }
11024   else
11025   {
11026     (*p_curr) = (*p_curr)->n_next;
11027   }
11028   if ((*p_curr) != NULL)
11029   {
11030     return(1);
11031   }
11032   else
11033   {
11034     return(0);
11035   }
11036 }
11037 
11038 
11039 
11040 
11041 
11042 /*
11043  *  Get pointer to previous station in name sorted list
11044  */
prev_station_name(DataRow ** p_curr)11045 int prev_station_name(DataRow **p_curr)
11046 {
11047 
11048   if ((*p_curr) == NULL)
11049   {
11050     (*p_curr) = n_last;
11051   }
11052   else
11053   {
11054     (*p_curr) = (*p_curr)->n_prev;
11055   }
11056   if ((*p_curr) != NULL)
11057   {
11058     return(1);
11059   }
11060   else
11061   {
11062     return(0);
11063   }
11064 }
11065 
11066 
11067 
11068 
11069 
11070 /*
11071  *  Get pointer to newer station in time sorted list
11072  */
next_station_time(DataRow ** p_curr)11073 int next_station_time(DataRow **p_curr)
11074 {
11075 
11076   if ((*p_curr) == NULL)
11077   {
11078     (*p_curr) = t_oldest;  // Grab oldest station if NULL passed to us???
11079   }
11080   else
11081   {
11082     (*p_curr) = (*p_curr)->t_newer;  // Else grab newer station
11083   }
11084   if ((*p_curr) != NULL)
11085   {
11086     return(1);
11087   }
11088   else
11089   {
11090     return(0);
11091   }
11092 }
11093 
11094 
11095 
11096 
11097 
11098 /*
11099  *  Get pointer to older station in time sorted list
11100  */
prev_station_time(DataRow ** p_curr)11101 int prev_station_time(DataRow **p_curr)
11102 {
11103 
11104   if ((*p_curr) == NULL)
11105   {
11106     (*p_curr) = t_newest;  // Grab newest station if NULL passed to us???
11107   }
11108   else
11109   {
11110     (*p_curr) = (*p_curr)->t_older;
11111   }
11112   if ((*p_curr) != NULL)
11113   {
11114     return(1);
11115   }
11116   else
11117   {
11118     return(0);
11119   }
11120 }
11121 
11122 
11123 
11124 
11125 
11126 /*
11127  *  Set flag for all stations in current view area or a margin area around it
11128  *  That are the stations we look at if we want to draw symbols or trails
11129  */
setup_in_view(void)11130 void setup_in_view(void)
11131 {
11132   DataRow *p_station;
11133   long min_lat, max_lat;                      // screen borders plus space
11134   long min_lon, max_lon;                      // for trails from off-screen stations
11135   long marg_lat, marg_lon;                    // margin around screen
11136 
11137   marg_lat = (long)(3 * screen_height * scale_y/2);
11138   marg_lon = (long)(3 * screen_width  * scale_x/2);
11139   if (marg_lat < IN_VIEW_MIN*60*100)          // allow a minimum area,
11140   {
11141     marg_lat = IN_VIEW_MIN*60*100;  // there could be outside stations
11142   }
11143   if (marg_lon < IN_VIEW_MIN*60*100)          // with trail parts on screen
11144   {
11145     marg_lon = IN_VIEW_MIN*60*100;
11146   }
11147 
11148   // Only screen view
11149   // min_lat = SE_corner_latitude
11150   // max_lat = NW_corner_latitude;
11151   // min_lon = NW_corner_longitude;
11152   // max_lon = SE_corner_longitude;
11153 
11154   // Screen view plus one screen wide margin
11155   // There could be stations off screen with on screen trails
11156   // See also the use of position_on_extd_screen()
11157   min_lat = center_latitude  - marg_lat;
11158   max_lat = center_latitude  + marg_lat;
11159   min_lon = center_longitude - marg_lon;
11160   max_lon = center_longitude + marg_lon;
11161 
11162   p_station = n_first;
11163   while (p_station != NULL)
11164   {
11165     if ((p_station->flag & ST_ACTIVE) == 0        // ignore deleted objects
11166         || p_station->coord_lon < min_lon || p_station->coord_lon > max_lon
11167         || p_station->coord_lat < min_lat || p_station->coord_lat > max_lat
11168         || (p_station->coord_lat == 0 && p_station->coord_lon == 0))
11169     {
11170       // outside view and undefined stations:
11171       p_station->flag &= (~ST_INVIEW);        // clear "In View" flag
11172     }
11173     else
11174     {
11175       p_station->flag |= ST_INVIEW;  // set "In View" flag
11176     }
11177     p_station = p_station->n_next;
11178   }
11179 }
11180 
11181 
11182 
11183 
11184 
11185 /*
11186  *  Check if position is inside screen borders
11187  */
position_on_screen(long lat,long lon)11188 int position_on_screen(long lat, long lon)
11189 {
11190 
11191   if (   lon > NW_corner_longitude && lon < SE_corner_longitude
11192          && lat > NW_corner_latitude && lat < SE_corner_latitude
11193          && !(lat == 0 && lon == 0))     // discard undef positions from screen
11194   {
11195     return(1);  // position is inside the screen
11196   }
11197   else
11198   {
11199     return(0);
11200   }
11201 }
11202 
11203 
11204 
11205 
11206 
11207 /*
11208  *  Check if position is inside extended screen borders
11209  *  (real screen + one screen margin for trails)
11210  *  used for station "In View" flag
11211  */
position_on_extd_screen(long lat,long lon)11212 int position_on_extd_screen(long lat, long lon)
11213 {
11214   long marg_lat, marg_lon;                    // margin around screen
11215 
11216   marg_lat = (long)(3 * screen_height * scale_y/2);
11217   marg_lon = (long)(3 * screen_width  * scale_x/2);
11218   if (marg_lat < IN_VIEW_MIN*60*100)          // allow a minimum area,
11219   {
11220     marg_lat = IN_VIEW_MIN*60*100;  // there could be outside stations
11221   }
11222   if (marg_lon < IN_VIEW_MIN*60*100)          // with trail parts on screen
11223   {
11224     marg_lon = IN_VIEW_MIN*60*100;
11225   }
11226 
11227   if (    labs(lon - center_longitude) < marg_lon
11228           && labs(lat - center_latitude)  < marg_lat
11229           && !(lat == 0 && lon == 0))    // discard undef positions from screen
11230   {
11231     return(1);  // position is inside the area
11232   }
11233   else
11234   {
11235     return(0);
11236   }
11237 }
11238 
11239 
11240 
11241 
11242 
11243 /*
11244  *  Check if position is inside inner screen area
11245  *  (real screen + minus 1/6 screen margin)
11246  *  used for station tracking
11247  */
position_on_inner_screen(long lat,long lon)11248 int position_on_inner_screen(long lat, long lon)
11249 {
11250 
11251   if (    lon > center_longitude-(long)(screen_width *scale_x/3)
11252           && lon < center_longitude+(long)(screen_width *scale_x/3)
11253           && lat > center_latitude -(long)(screen_height*scale_y/3)
11254           && lat < center_latitude +(long)(screen_height*scale_y/3)
11255           && !(lat == 0 && lon == 0))    // discard undef positions from screen
11256   {
11257     return(1);  // position is inside the area
11258   }
11259   else
11260   {
11261     return(0);
11262   }
11263 }
11264 
11265 
11266 
11267 
11268 
11269 /*
11270  *  Delete single station with all its data    ?? delete messages ??
11271  *  This function is called with a callsign parameter.  Only used for
11272  *  my callsign, not for any other.
11273  */
station_del(char * call)11274 void station_del(char *call)
11275 {
11276   DataRow *p_name;                      // DK7IN: do it with move... ?
11277 
11278   if (search_station_name(&p_name, call, 1))
11279   {
11280     (void)delete_trail(p_name);       // Free track storage if it exists.
11281     (void)delete_weather(p_name);     // Free weather memory, if allocated
11282     (void)delete_multipoints(p_name); // Free multipoint memory, if allocated
11283     (void)delete_comments_and_status(p_name);  // Free comment storage if it exists
11284     if (p_name->node_path_ptr != NULL)// Free malloc'ed path
11285     {
11286       free(p_name->node_path_ptr);
11287     }
11288     if (p_name->tactical_call_sign != NULL)
11289     {
11290       free(p_name->tactical_call_sign);
11291     }
11292     delete_station_memory(p_name);    // Free memory
11293   }
11294 }
11295 
11296 
11297 
11298 
11299 
11300 /*
11301  *  Delete single station with all its data    ?? delete messages ??
11302  *  This function is called with a pointer instead of a callsign.
11303  */
station_del_ptr(DataRow * p_name)11304 void station_del_ptr(DataRow *p_name)
11305 {
11306 
11307   //fprintf(stderr,"db.c:station_del_ptr(): %s\n",p_name->call_sign);
11308 
11309   if (p_name != NULL)
11310   {
11311 
11312     // A bit of debug code:  Attempting to find out if we're
11313     // deleting our own objects from time to time.  Leave this
11314     // in until we're sure the problem has been fixed.
11315     ////        if (is_my_call(p_name->origin,1)) { // Check SSID as well
11316     //        if ( is_my_object_item(p_name) ) { // Check SSID as well
11317     //            fprintf(stderr,"station_del_ptr: Removing my own object: %s\n",
11318     //                p_name->call_sign);
11319     //        }
11320 
11321 #ifdef EXPIRE_DEBUG
11322     fprintf(stderr,"Removing: %s heard %d seconds ago\n",p_name->call_sign, (int)(sec_now() - p_name->sec_heard));
11323 #endif
11324 
11325     (void)delete_trail(p_name);     // Free track storage if it exists.
11326     (void)delete_weather(p_name);   // free weather memory, if allocated
11327     (void)delete_multipoints(p_name); // Free multipoint memory, if allocated
11328     (void)delete_comments_and_status(p_name);  // Free comment storage if it exists
11329     if (p_name->node_path_ptr != NULL)  // Free malloc'ed path
11330     {
11331       free(p_name->node_path_ptr);
11332     }
11333     if (p_name->tactical_call_sign != NULL)
11334     {
11335       free(p_name->tactical_call_sign);
11336     }
11337     delete_station_memory(p_name);  // free memory, update
11338     // linked lists, update
11339     // station_count
11340 
11341     //fprintf(stderr,"db.c:station_del_ptr(): Deleted station\n");
11342 
11343   }
11344 }
11345 
11346 
11347 
11348 
11349 
11350 /*
11351  *  Delete all stations             ?? delete messages ??
11352  */
delete_all_stations(void)11353 void delete_all_stations(void)
11354 {
11355   DataRow *p_name;
11356   DataRow *p_curr;
11357   int ii;
11358 
11359 
11360   // Clear all of the pointers before we begin
11361   for (ii = 0; ii < 16384; ii++)
11362   {
11363     station_shortcuts[ii] = NULL;
11364   }
11365 
11366   p_name = n_first;
11367   while (p_name != NULL)
11368   {
11369     p_curr = p_name;
11370     p_name = p_name->n_next;
11371     station_del_ptr(p_curr);
11372     //(void)delete_trail(p_curr);     // free trail memory, if allocated
11373     //(void)delete_weather(p_curr);   // free weather memory, if allocated
11374     //(void)delete_multipoints(p_curr);// Free multipoint memory, if allocated
11375     //delete_station_memory(p_curr);  // free station memory
11376   }
11377   if (station_count != 0)
11378   {
11379     fprintf(stderr,
11380             "ERROR: station_count should be 0 after stations delete, is %d\n",
11381             station_count);
11382     station_count = 0;
11383   }
11384 }
11385 
11386 
11387 
11388 
11389 
11390 /*
11391  *  Check if we have to delete old stations.
11392  *
11393  *  Called from main.c:UpdateTime() on a periodic basis.
11394  *
11395  */
check_station_remove(time_t curr_sec)11396 void check_station_remove(time_t curr_sec)
11397 {
11398   DataRow *p_station, *p_station_t_newer;
11399   time_t t_rem;
11400   int done;
11401 
11402 
11403   // Run through this routine every STATION_REMOVE_CYCLE
11404   // seconds (currently every five minutes)
11405 #ifdef EXPIRE_DEBUG
11406   // Check every 15 seconds, useful for debug only.
11407   if (last_station_remove < (curr_sec - DEBUG_STATION_REMOVE_CYCLE))  // DEBUG
11408 #else
11409   if (last_station_remove < (curr_sec - STATION_REMOVE_CYCLE))
11410 #endif
11411   {
11412 
11413     //fprintf(stderr,"db.c:check_station_remove() is running\n");
11414 
11415     // Compute the cutoff time.  Any stations older than t_rem
11416     // will be removed, unless they have a tactical call or
11417     // belong to us.
11418     t_rem = curr_sec - sec_remove;
11419 
11420 #ifdef EXPIRE_DEBUG
11421     // Expire every 15 seconds, useful for debug only.
11422     t_rem = curr_sec - (1 * DEBUG_STATION_REMOVE);
11423 #endif
11424 
11425     for (done = 0, p_station = t_oldest; p_station != NULL && !done; p_station = p_station_t_newer)
11426     {
11427 
11428       // Save a pointer to the next record in time-order
11429       // before we delete a record and lose it.
11430       p_station_t_newer = p_station->t_newer;
11431 
11432       if (p_station->sec_heard < t_rem)
11433       {
11434 
11435         //                if ( (is_my_call(p_station->call_sign,1)) // It's my station (including SSID) or
11436         //                        || ( (is_my_call(p_station->origin,1)) // Station is owned by me (including SSID)
11437         //                          && ( ((p_station->flag & ST_OBJECT) != 0) // and it's an object
11438         //                            || ((p_station->flag & ST_ITEM  ) != 0) ) ) ) { // or an item
11439         if ( is_my_station(p_station) || is_my_object_item(p_station))
11440         {
11441 
11442           // It's one of mine, leave it alone!
11443 
11444 #ifdef EXPIRE_DEBUG
11445           fprintf(stderr,"found old station: %s\t\t",p_station->call_sign);
11446           fprintf(stderr,"mine\n");
11447 #endif
11448 
11449         }
11450 
11451         /*
11452           else if (p_station->tactical_call_sign) {
11453           // Station has a tactical callsign assigned,
11454           // don't delete it.
11455 
11456           #ifdef EXPIRE_DEBUG
11457           fprintf(stderr,"found old station: %s\t\t",p_station->call_sign);
11458           fprintf(stderr,"tactical\n");
11459           #endif
11460 
11461           }
11462         */
11463 
11464         else    // Not one of mine, doesn't have a tactical
11465         {
11466           // callsign assigned, so start deleting
11467 
11468           //The debug output needs to be before the delete, as
11469           // we're freeing the data pointed to by p_station!
11470 #ifdef EXPIRE_DEBUG
11471           fprintf(stderr,"found old station: %s\t\t",p_station->call_sign);
11472           fprintf(stderr,"deleting\n");
11473           fprintf(stderr,"Last heard time: %ld\n",p_station->sec_heard);
11474           fprintf(stderr," t_rem: %ld\n",t_rem);
11475           fprintf(stderr," next older record has time %ld\n",p_station_t_newer->sec_heard);
11476 #endif
11477 
11478           mdelete_messages(p_station->call_sign); // Delete messages
11479           station_del_ptr(p_station);
11480           //(void)delete_trail(p_station);        // Free track storage if it exists.
11481           //(void)delete_weather(p_station);      // Free weather memory, if allocated
11482           //(void)delete_multipoints(p_station);  // Free multipoint memory, if allocated
11483           //delete_station_memory(p_station);     // Free memory
11484 
11485         }
11486       }
11487       else
11488       {
11489 #ifdef EXPIRE_DEBUG
11490         DataRow *testPtr = sanity_check_time_list(t_rem);
11491         if (testPtr)
11492         {
11493           fprintf(stderr,"TIME-SORTED LIST SANITY CHECK FAILED!\n");
11494           fprintf(stderr," At least one station left after expire with time older than %ld\n",t_rem);
11495           fprintf(stderr,"   Station name: %s\n", testPtr->call_sign);
11496           fprintf(stderr,"   Last heard time %ld\n",testPtr->sec_heard);
11497           fprintf(stderr,"   Seconds ago: %ld\n",curr_sec-testPtr->sec_heard);
11498           fprintf(stderr,"   Seconds older than expire time: %ld\n",t_rem-testPtr->sec_heard);
11499           fprintf(stderr,"--------\n");
11500           dump_time_sorted_list();
11501         }
11502 #endif
11503         done++;                                         // all other stations are newer...
11504       }
11505     }
11506     last_station_remove = curr_sec;
11507   }
11508 }
11509 
11510 
11511 
11512 
11513 
11514 /*
11515  *  Delete an object (mark it as deleted)
11516  */
delete_object(char * name)11517 void delete_object(char *name)
11518 {
11519   DataRow *p_station;
11520 
11521   //fprintf(stderr,"delete_object\n");
11522 
11523   p_station = NULL;
11524   if (search_station_name(&p_station,name,1))         // find object name
11525   {
11526     p_station->flag &= (~ST_ACTIVE);                // clear flag
11527     p_station->flag &= (~ST_INVIEW);                // clear "In View" flag
11528     if (position_on_screen(p_station->coord_lat,p_station->coord_lon))
11529     {
11530       redraw_on_new_data = 2;  // redraw now
11531     }
11532     // there is some problem...  it is not redrawn immediately! ????
11533     // but deleted from list immediatetly
11534     redo_list = (int)TRUE;                          // and update lists
11535   }
11536 }
11537 
11538 
11539 
11540 
11541 
11542 ///////////////////////////////////////  APRS Decoding  ////////////////////////////////////////////
11543 
11544 /*
11545  * Try to find a !DAO! format datum and extra precision string from the
11546  * comment field of an APRS location packet (incl. objects and items).
11547  * If !DAO! is found, it is removed from the comment.
11548  * See http://web.ew.usna.edu/~bruninga/aprs/datum.txt
11549  *
11550  * lat and lon will contain the thousandth and ten thousandth
11551  * minute digits of the location, if valid (see below).
11552  * For example, if the final location is 70 deg 12.3456 minutes,
11553  * lat or lon will contain 56. If the final location is
11554  * 50 deg 56.2104 minutes, lat or lon will contain 4. So remember
11555  * to zero pad! The range for lat/lon, when valid, is 0-99.
11556  * datumch will contain the datum character, if found.
11557  *
11558  * daocomment must be null-terminated and must contain the comment field
11559  *
11560  * returns 3 if dao was found and contained a base-91 position
11561  *   (= datumch, lat, and lon contents are all valid)
11562  * returns 2 if dao was found and contained a human readable position
11563  *   (= datumch, lat, and lon contents are all valid)
11564  * returns 1 if dao was found but only included datum information
11565  *   (= only datumch is valid)
11566  * returns 0 if no valid dao was found
11567  *   (= datumch, lat, and lon contents are all invalid, daocomment is unmodified)
11568  *
11569  * Tapio Sokura OH2KKU <tapio.sokura@iki.fi> 2007-11-15
11570  */
decode_dao(int * lat,int * lon,char * datumch,char * daocomment)11571 int decode_dao (int *lat, int *lon, char *datumch, char *daocomment)
11572 {
11573   char *searchval, *rval;
11574   size_t slen;
11575 
11576   // Loop around searching for !DAO!, return the first valid match.
11577   // The first '!' is found using strchr, the rest of the
11578   // string is validated more manually.
11579   searchval = daocomment;
11580   rval = strchr(searchval, '!');
11581   while (rval != NULL)
11582   {
11583 
11584     // Check the remaining string length so we don't
11585     // run past string end
11586     slen = strlen(rval);
11587     if (slen < 5)
11588     {
11589       break;
11590     }
11591 
11592     if (rval[4] == '!' && rval[1] >= '!' && rval[1] <= '{')
11593     {
11594       // found the !DAO! terminator and datum char is
11595       // within the allowable range
11596 
11597       if (rval[1] >= 'A' && rval[1] <= 'Z')
11598       {
11599         // looks like human readable format
11600 
11601         if (rval[2] == ' ' && rval[3] == ' ')
11602         {
11603           // only datum information present
11604           *datumch = rval[1];
11605           memmove(rval, rval + 5, slen - 4);
11606           return 1;
11607 
11608         }
11609         else if (rval[2] >= '0' && rval[2] <= '9' &&
11610                  rval[3] >= '0' && rval[3] <= '9')
11611         {
11612           // human readable format 0-9 lat/lon ok
11613 
11614           // ASCII - 48 = the integer digit we want.
11615           // Multiply by 10, because we only get
11616           // thousandths of a minute with human
11617           // readable format.
11618           *lat = ((int)rval[2] - 48) * 10;
11619           *lon = ((int)rval[3] - 48) * 10;
11620           *datumch = rval[1];
11621           memmove(rval, rval + 5, slen - 4);
11622           return 2;
11623         }
11624         // not ok for human readable format, continue searching
11625 
11626       }
11627       else if (rval[1] >= 'a' && rval[1] <= 'z')
11628       {
11629         // looks like base-91 format
11630 
11631         if (rval[2] == ' ' && rval[3] == ' ')
11632         {
11633           // only datum information present
11634           *datumch = rval[1];
11635           memmove(rval, rval + 5, slen - 4);
11636           return 1;
11637 
11638         }
11639         else if (rval[2] >= '!' && rval[2] <= '{' &&
11640                  rval[3] >= '!' && rval[3] <= '{')
11641         {
11642           // base-91 lat/lon ok
11643           unsigned int lats, lons;
11644           float latval, lonval;
11645           lats = rval[2] - 33; // get base91 values
11646           lons = rval[3] - 33;
11647           latval = lats / 91.0 * 100; // do proper scaling
11648           lonval = lons / 91.0 * 100;
11649           *lat = (int)(latval + 0.5); // round and store
11650           *lon = (int)(lonval + 0.5);
11651           *datumch = rval[1];
11652           memmove(rval, rval + 5, slen - 4);
11653           return 3;
11654         }
11655         // not ok for base91 format, continue searching
11656       }
11657       // Datum chars outside A-Z and a-z are not
11658       // handled (here at least).
11659     }
11660 
11661     // If we end up here, we didn't find a match.
11662     // Search for the next '!' char.
11663     searchval = rval + 1;
11664     rval = strchr(searchval, '!');
11665   }
11666 
11667   // No more string left to search and no match.
11668   return 0;
11669 }
11670 
11671 
11672 
11673 /*
11674  *  Extract Uncompressed Position Report from begin of line
11675  *
11676  * If a position is found, it is deleted from the data.
11677  */
extract_position(DataRow * p_station,char ** info,int type)11678 int extract_position(DataRow *p_station, char **info, int type)
11679 {
11680   int ok, dao_lat, dao_lon, dao_rval;
11681   char temp_lat[10+1];
11682   char temp_lon[11+1];
11683   char temp_grid[8+1];
11684   char *my_data;
11685   char dao_datumch;
11686   float gridlat;
11687   float gridlon;
11688   my_data = (*info);
11689 
11690   if (type != APRS_GRID)  // Not a grid
11691   {
11692     ok = (int)(strlen(my_data) >= 19);
11693     ok = (int)(ok && my_data[4]=='.' && my_data[14]=='.'
11694                && (toupper(my_data[7]) =='N' || toupper(my_data[7]) =='S')
11695                && (toupper(my_data[17])=='E' || toupper(my_data[17])=='W'));
11696     // errors found:  [4]: X   [7]: n s   [17]: w e
11697     if (ok)
11698     {
11699       ok =             is_num_chr(my_data[0]);           // 5230.31N/01316.88E>
11700       ok = (int)(ok && is_num_chr(my_data[1]));          // 0123456789012345678
11701       ok = (int)(ok && is_num_or_sp(my_data[2]));
11702       ok = (int)(ok && is_num_or_sp(my_data[3]));
11703       ok = (int)(ok && is_num_or_sp(my_data[5]));
11704       ok = (int)(ok && is_num_or_sp(my_data[6]));
11705       ok = (int)(ok && is_num_chr(my_data[9]));
11706       ok = (int)(ok && is_num_chr(my_data[10]));
11707       ok = (int)(ok && is_num_chr(my_data[11]));
11708       ok = (int)(ok && is_num_or_sp(my_data[12]));
11709       ok = (int)(ok && is_num_or_sp(my_data[13]));
11710       ok = (int)(ok && is_num_or_sp(my_data[15]));
11711       ok = (int)(ok && is_num_or_sp(my_data[16]));
11712     }
11713 
11714     if (ok)
11715     {
11716       overlay_symbol(my_data[18], my_data[8], p_station);
11717       p_station->pos_amb = 0;
11718       // spaces in latitude set position ambiguity, spaces in longitude do not matter
11719       // we will adjust the lat/long to the center of the rectangle of ambiguity
11720       if (my_data[2] == ' ')        // nearest degree
11721       {
11722         p_station->pos_amb = 4;
11723         my_data[2]  = my_data[12] = '3';
11724         my_data[3]  = my_data[5]  = my_data[6]  = '0';
11725         my_data[13] = my_data[15] = my_data[16] = '0';
11726       }
11727       else if (my_data[3] == ' ')   // nearest 10 minutes
11728       {
11729         p_station->pos_amb = 3;
11730         my_data[3]  = my_data[13] = '5';
11731         my_data[5]  = my_data[6]  = '0';
11732         my_data[15] = my_data[16] = '0';
11733       }
11734       else if (my_data[5] == ' ')   // nearest minute
11735       {
11736         p_station->pos_amb = 2;
11737         my_data[5]  = my_data[15] = '5';
11738         my_data[6]  = '0';
11739         my_data[16] = '0';
11740       }
11741       else if (my_data[6] == ' ')   // nearest 1/10th minute
11742       {
11743         p_station->pos_amb = 1;
11744         my_data[6]  = my_data[16] = '5';
11745       }
11746 
11747       xastir_snprintf(temp_lat,
11748                       sizeof(temp_lat),
11749                       "%s",
11750                       my_data);
11751       temp_lat[9] = toupper(my_data[7]);
11752       temp_lat[10] = '\0';
11753 
11754       xastir_snprintf(temp_lon,
11755                       sizeof(temp_lon),
11756                       "%s",
11757                       my_data+9);
11758       temp_lon[10] = toupper(my_data[17]);
11759       temp_lon[11] = '\0';
11760 
11761       // Check for !DAO!, beginning from the comment field.
11762       // Datum is not used for the time being.
11763       // Note: error/precision information (the white box on the map) is
11764       // not updated here, because changes to p_station->lat/lon_precision
11765       // are overridden in the calling function.
11766       dao_rval = decode_dao(&dao_lat, &dao_lon, &dao_datumch, my_data + 19);
11767       if (dao_rval == 2 || dao_rval == 3)
11768       {
11769         // 48 is the magic number to add to a single digit integer to
11770         // get the same digit in ASCII.
11771         temp_lat[7] = (char)(dao_lat / 10 + 48);
11772         temp_lat[8] = (char)(dao_lat % 10 + 48);
11773         temp_lon[8] = (char)(dao_lon / 10 + 48);
11774         temp_lon[9] = (char)(dao_lon % 10 + 48);
11775         // Signal that this is an accuracy-enhanced !DAO! position,
11776         // so the calling function can set the error boxes accordingly
11777         // (once somebody implements it).
11778         ok = dao_rval;
11779       }
11780       else
11781       {
11782         // no valid !DAO! _location_ found, pad with zeroes instead
11783         temp_lat[7] = '0';
11784         temp_lat[8] = '0';
11785         temp_lon[8] = '0';
11786         temp_lon[9] = '0';
11787       }
11788 
11789       // Callsign check here also checks SSID for an exact
11790       // match
11791       //            if (!is_my_call(p_station->call_sign,1)) {      // don't change my position, I know it better...
11792       if ( !(is_my_station(p_station)) )        // don't change my position, I know it better...
11793       {
11794 
11795         p_station->coord_lat = convert_lat_s2l(temp_lat);   // ...in case of position ambiguity
11796         p_station->coord_lon = convert_lon_s2l(temp_lon);
11797       }
11798 
11799       (*info) += 19;                  // delete position from comment
11800     }
11801   }
11802   else   // It is a grid
11803   {
11804     // first sanity checks, need more
11805     ok = (int)(is_num_chr(my_data[2]));
11806     ok = (int)(ok && is_num_chr(my_data[3]));
11807     ok = (int)(ok && ((my_data[0]>='A')&&(my_data[0]<='R')));
11808     ok = (int)(ok && ((my_data[1]>='A')&&(my_data[1]<='R')));
11809     if (ok)
11810     {
11811       xastir_snprintf(temp_grid,
11812                       sizeof(temp_grid),
11813                       "%s",
11814                       my_data);
11815       // this test treats >6 digit grids as 4 digit grids; >6 are uncommon.
11816       // the spec mentioned 4 or 6, I'm not sure >6 is even allowed.
11817       if ( (temp_grid[6] != ']') || (temp_grid[4] == 0) || (temp_grid[5] == 0))
11818       {
11819         p_station->pos_amb = 6; // 1deg lat x 2deg lon
11820         temp_grid[4] = 'L';
11821         temp_grid[5] = 'L';
11822       }
11823       else
11824       {
11825         p_station->pos_amb = 5; // 2.5min lat x 5min lon
11826         temp_grid[4] = toupper(temp_grid[4]);
11827         temp_grid[5] = toupper(temp_grid[5]);
11828       }
11829       // These equations came from what I read in the qgrid source code and
11830       // various mailing list archives.
11831       gridlon= (20.*((float)temp_grid[0]-65.) + 2.*((float)temp_grid[2]-48.) + 5.*((float)temp_grid[4]-65.)/60.) - 180.;
11832       gridlat= (10.*((float)temp_grid[1]-65.) + ((float)temp_grid[3]-48.) + 5.*(temp_grid[5]-65.)/120.) - 90.;
11833       // could check for my callsign here, and avoid changing it...
11834       p_station->coord_lat = (unsigned long)(32400000l + (360000.0 * (-gridlat)));
11835       p_station->coord_lon = (unsigned long)(64800000l + (360000.0 * gridlon));
11836       p_station->aprs_symbol.aprs_type = '/';
11837       p_station->aprs_symbol.aprs_symbol = 'G';
11838     }        // is it valid grid or not - "ok"
11839     // could cut off the grid square from the comment here, but why bother?
11840   } // is it grid or not
11841   return(ok);
11842 }
11843 
11844 
11845 
11846 
11847 
11848 // DK7IN 99
11849 /*
11850  *  Extract Compressed Position Report Data Formats from begin of line
11851  *    [APRS Reference, chapter 9]
11852  *
11853  * If a position is found, it is deleted from the data.  If a
11854  * compressed position is found, delete the three csT bytes as well,
11855  * even if all spaces.
11856  * Returns 0 if the packet is NOT a properly compressed position
11857  * packet, returns 1 if ok.
11858  */
extract_comp_position(DataRow * p_station,char ** info,int UNUSED (type))11859 int extract_comp_position(DataRow *p_station, char **info, int UNUSED(type) )
11860 {
11861   int ok;
11862   int x1, x2, x3, x4, y1, y2, y3, y4;
11863   int c = 0;
11864   int s = 0;
11865   int T = 0;
11866   int len;
11867   char *my_data;
11868   float lon = 0;
11869   float lat = 0;
11870   // We were extracting  the range from the posit, but never using it.
11871   // GCC 6.x whines
11872   //    float range;
11873   int skip = 0;
11874   char L;
11875 
11876 
11877   if (debug_level & 1)
11878   {
11879     fprintf(stderr,"extract_comp_position: Start\n");
11880   }
11881 
11882   //fprintf(stderr,"extract_comp_position start: %s\n",*info);
11883 
11884   // compressed data format  /YYYYXXXX$csT  is a fixed 13-character field
11885   // used for ! / @ = data IDs
11886   //   /     Symbol Table ID or overlay: '/' '\' A-Z a-j
11887   //   YYYY  compressed latitude
11888   //   XXXX  compressed longitude
11889   //   $     Symbol Code
11890   //   cs    compressed
11891   //            course/speed
11892   //            radio range
11893   //            altitude
11894   //   T     compression type ID
11895 
11896   my_data = (*info);
11897 
11898   // Check leading char.  Must be one of these:
11899   // '/'
11900   // '\'
11901   // A-Z
11902   // a-j
11903   //
11904   L = my_data[0];
11905   if (   L == '/'
11906          || L == '\\'
11907          || ( L >= 'A' && L <= 'Z' )
11908          || ( L >= 'a' && L <= 'j' ) )
11909   {
11910     // We're good so far
11911   }
11912   else
11913   {
11914     // Note one of the symbol table or overlay characters, so
11915     // there's something funky about this packet.  It's not a
11916     // properly formatted compressed position.
11917     return(0);
11918   }
11919 
11920   //fprintf(stderr,"my_data: %s\n",my_data);
11921 
11922   // If c = space, csT bytes are ignored.  Minimum length:  8
11923   // bytes for lat/lon, 2 for symbol, 3 for csT for a total of 13.
11924   len = strlen(my_data);
11925   ok = (int)(len >= 13);
11926 
11927   if (ok)
11928   {
11929     y1 = (int)my_data[1] - '!';
11930     y2 = (int)my_data[2] - '!';
11931     y3 = (int)my_data[3] - '!';
11932     y4 = (int)my_data[4] - '!';
11933     x1 = (int)my_data[5] - '!';
11934     x2 = (int)my_data[6] - '!';
11935     x3 = (int)my_data[7] - '!';
11936     x4 = (int)my_data[8] - '!';
11937 
11938     // csT bytes
11939     if (my_data[10] == ' ') // Space
11940     {
11941       c = -1;  // This causes us to ignore csT
11942     }
11943     else
11944     {
11945       c = (int)my_data[10] - '!';
11946       s = (int)my_data[11] - '!';
11947       T = (int)my_data[12] - '!';
11948     }
11949     skip = 13;
11950 
11951     // Convert ' ' to '0'.  Not specified in APRS Reference!  Do
11952     // we need it?
11953     if (x1 == -1)
11954     {
11955       x1 = '\0';
11956     }
11957     if (x2 == -1)
11958     {
11959       x2 = '\0';
11960     }
11961     if (x3 == -1)
11962     {
11963       x3 = '\0';
11964     }
11965     if (x4 == -1)
11966     {
11967       x4 = '\0';
11968     }
11969     if (y1 == -1)
11970     {
11971       y1 = '\0';
11972     }
11973     if (y2 == -1)
11974     {
11975       y2 = '\0';
11976     }
11977     if (y3 == -1)
11978     {
11979       y3 = '\0';
11980     }
11981     if (y4 == -1)
11982     {
11983       y4 = '\0';
11984     }
11985 
11986     ok = (int)(ok && (x1 >= '\0' && x1 < 91));  //  /YYYYXXXX$csT
11987     ok = (int)(ok && (x2 >= '\0' && x2 < 91));  //  0123456789012
11988     ok = (int)(ok && (x3 >= '\0' && x3 < 91));
11989     ok = (int)(ok && (x4 >= '\0' && x4 < 91));
11990     ok = (int)(ok && (y1 >= '\0' && y1 < 91));
11991     ok = (int)(ok && (y2 >= '\0' && y2 < 91));
11992     ok = (int)(ok && (y3 >= '\0' && y3 < 91));
11993     ok = (int)(ok && (y4 >= '\0' && y4 < 91));
11994 
11995     T &= 0x3F;      // DK7IN: force Compression Byte to valid format
11996     // mask off upper two unused bits, they should be zero!?
11997 
11998     ok = (int)(ok && (c == -1 || ((c >=0 && c < 91) && (s >= 0 && s < 91) && (T >= 0 && T < 64))));
11999 
12000     if (ok)
12001     {
12002       lat = (((y1 * 91 + y2) * 91 + y3) * 91 + y4 ) / 380926.0; // in deg, 0:  90°N
12003       lon = (((x1 * 91 + x2) * 91 + x3) * 91 + x4 ) / 190463.0; // in deg, 0: 180°W
12004       lat *= 60 * 60 * 100;                       // in 1/100 sec
12005       lon *= 60 * 60 * 100;                       // in 1/100 sec
12006 
12007       // The below check should _not_ be done.  Compressed
12008       // format can resolve down to about 1 foot worldwide
12009       // (0.3 meters).
12010       //if ((((long)(lat+4) % 60) > 8) || (((long)(lon+4) % 60) > 8))
12011       //    ok = 0;   // check max resolution 0.01 min to
12012       // catch even more errors
12013     }
12014   }
12015 
12016   if (ok)
12017   {
12018     overlay_symbol(my_data[9], my_data[0], p_station);      // Symbol / Table
12019 
12020     // Callsign check here includes checking SSID for an exact
12021     // match
12022     //        if (!is_my_call(p_station->call_sign,1)) {  // don't change my position, I know it better...
12023     if ( !(is_my_station(p_station)) )    // don't change my position, I know it better...
12024     {
12025 
12026       // Record the uncompressed lat/long that we just
12027       // computed.
12028       p_station->coord_lat = (long)((lat));               // in 1/100 sec
12029       p_station->coord_lon = (long)((lon));               // in 1/100 sec
12030     }
12031 
12032     if (c >= 0)                                     // ignore csT if c = ' '
12033     {
12034       if (c < 90)     // Found course/speed or altitude bytes
12035       {
12036         if ((T & 0x18) == 0x10)     // check for GGA (with altitude)
12037         {
12038           xastir_snprintf(p_station->altitude, sizeof(p_station->altitude), "%06.0f",pow(1.002,(double)(c*91+s))*0.3048);
12039         }
12040         else   // Found compressed course/speed bytes
12041         {
12042 
12043           // Convert 0 degrees to 360 degrees so that
12044           // Xastir will see it as a valid course and do
12045           // dead-reckoning properly on this station
12046           if (c == 0)
12047           {
12048             c = 90;
12049           }
12050 
12051           // Compute course in degrees
12052           xastir_snprintf(p_station->course,
12053                           sizeof(p_station->course),
12054                           "%03d",
12055                           c*4);
12056 
12057           // Compute speed in knots
12058           xastir_snprintf(p_station->speed,
12059                           sizeof(p_station->speed),
12060                           "%03.0f",
12061                           pow( 1.08,(double)s ) - 1.0);
12062 
12063           //fprintf(stderr,"Decoded speed:%s, course:%s\n",p_station->speed,p_station->course);
12064 
12065         }
12066       }
12067       else      // Found pre-calculated radio range bytes
12068       {
12069         if (c == 90)
12070         {
12071           // pre-calculated radio range
12072           // Commented out to silence GCC 6.x warning about
12073           // "set but unused" variables.
12074           // range = 2 * pow(1.08,(double)s);    // miles
12075 
12076           // DK7IN: dirty hack...  but better than nothing
12077           if (s <= 5)                         // 2.9387 mi
12078           {
12079             xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "000");
12080           }
12081           else if (s <= 17)                   // 7.40 mi
12082           {
12083             xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "111");
12084           }
12085           else if (s <= 36)                   // 31.936 mi
12086           {
12087             xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "222");
12088           }
12089           else if (s <= 75)                   // 642.41 mi
12090           {
12091             xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "333");
12092           }
12093           else                       // max 90:  2037.8 mi
12094           {
12095             xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "444");
12096           }
12097         }
12098       }
12099     }
12100     (*info) += skip;    // delete position from comment
12101   }
12102 
12103   if (debug_level & 1)
12104   {
12105     if (ok)
12106     {
12107       fprintf(stderr,"*** extract_comp_position: Succeeded: %ld\t%ld\n",
12108               p_station->coord_lat,
12109               p_station->coord_lon);
12110     }
12111     else
12112     {
12113       fprintf(stderr,"*** extract_comp_position: Failed!\n");
12114     }
12115   }
12116 
12117   //fprintf(stderr,"  extract_comp_position end: %s\n",*info);
12118 
12119   return(ok);
12120 }
12121 
12122 
12123 
12124 
12125 
12126 //
12127 //  Extract speed and/or course from beginning of info field
12128 //
12129 // Returns course in degrees, speed in KNOTS.
12130 //
extract_speed_course(char * info,char * speed,char * course)12131 int extract_speed_course(char *info, char *speed, char *course)
12132 {
12133   int i,found,len;
12134 
12135   len = (int)strlen(info);
12136   found = 0;
12137   speed[0] = course[0] = '\0';
12138   if (len >= 7)
12139   {
12140     found = 1;
12141     for(i=0; found && i<7; i++)             // check data format
12142     {
12143       if (i==3)                           // check separator
12144       {
12145         if (info[i]!='/')
12146         {
12147           found = 0;
12148         }
12149       }
12150       else
12151       {
12152         if( !( isdigit((int)info[i])
12153                || (info[i] == ' ')     // Spaces and periods are allowed.  Need these
12154                || (info[i] == '.') ) ) // here so that we can get the field deleted
12155         {
12156           found = 0;
12157         }
12158       }
12159     }
12160   }
12161   if (found)
12162   {
12163     substr(course,info,3);
12164     substr(speed,info+4,3);
12165     for (i=0; i<=len-7; i++)      // delete speed/course from info field
12166     {
12167       info[i] = info[i+7];
12168     }
12169   }
12170   if (!found || atoi(course) < 1)     // course 0 means undefined
12171   {
12172     //        speed[0] ='\0';   // Don't do this!  We can have a valid
12173     //        speed without a valid course.
12174     course[0]='\0';
12175   }
12176   else    // recheck data format looking for undefined fields
12177   {
12178     for(i=0; i<2; i++)
12179     {
12180       if( !(isdigit((int)speed[i]) ) )
12181       {
12182         speed[0] = '\0';
12183       }
12184       if( !(isdigit((int)course[i]) ) )
12185       {
12186         course[0] = '\0';
12187       }
12188     }
12189   }
12190 
12191   return(found);
12192 }
12193 
12194 
12195 
12196 
12197 
12198 /*
12199  *  Extract bearing and number/range/quality from beginning of info field
12200  */
extract_bearing_NRQ(char * info,char * bearing,char * nrq)12201 int extract_bearing_NRQ(char *info, char *bearing, char *nrq)
12202 {
12203   int i,found,len;
12204 
12205   len = (int)strlen(info);
12206   found = 0;
12207   if (len >= 8)
12208   {
12209     found = 1;
12210     for(i=1; found && i<8; i++)         // check data format
12211       if(!(isdigit((int)info[i]) || (i==4 && info[i]=='/')))
12212       {
12213         found=0;
12214       }
12215   }
12216   if (found)
12217   {
12218     substr(bearing,info+1,3);
12219     substr(nrq,info+5,3);
12220 
12221     //fprintf(stderr,"Bearing: %s\tNRQ: %s\n", bearing, nrq);
12222 
12223     for (i=0; i<=len-8; i++)      // delete bearing/nrq from info field
12224     {
12225       info[i] = info[i+8];
12226     }
12227   }
12228 
12229   //    if (!found || nrq[2] == '0') {   // Q of 0 means useless bearing
12230   if (!found)
12231   {
12232     bearing[0] ='\0';
12233     nrq[0]='\0';
12234   }
12235   return(found);
12236 }
12237 
12238 
12239 
12240 
12241 
12242 /*
12243  *  Extract altitude from APRS info field          "/A=012345"    in feet
12244  */
extract_altitude(char * info,char * altitude)12245 int extract_altitude(char *info, char *altitude)
12246 {
12247   int i,ofs,found,len;
12248 
12249   found=0;
12250   len = (int)strlen(info);
12251   for(ofs=0; !found && ofs<len-8; ofs++)      // search for start sequence
12252     if (strncmp(info+ofs,"/A=",3)==0)
12253     {
12254       found=1;
12255       // Are negative altitudes even defined?  Yes!  In Mic-E spec to -10,000 meters
12256       if(!isdigit((int)info[ofs+3]) && info[ofs+3]!='-')  // First char must be digit or '-'
12257       {
12258         found=0;
12259       }
12260       for(i=4; found && i<9; i++)         // check data format for next 5 chars
12261         if(!isdigit((int)info[ofs+i]))
12262         {
12263           found=0;
12264         }
12265     }
12266   if (found)
12267   {
12268     ofs--;  // was one too much on exit from for loop
12269     substr(altitude,info+ofs+3,6);
12270     for (i=ofs; i<=len-9; i++)      // delete altitude from info field
12271     {
12272       info[i] = info[i+9];
12273     }
12274   }
12275   else
12276   {
12277     altitude[0] = '\0';
12278   }
12279   return(found);
12280 }
12281 
12282 
12283 
12284 
12285 
12286 // TODO:
12287 // Comment Field
12288 
12289 
12290 
12291 
12292 
12293 /*
12294  *  Extract powergain and/or range from APRS info field:
12295  * "PHG1234/", "PHG1234", or "RNG1234" from APRS data extension.
12296  */
extract_powergain_range(char * info,char * phgd)12297 int extract_powergain_range(char *info, char *phgd)
12298 {
12299   int i,found,len;
12300   char *info2;
12301 
12302 
12303   //fprintf(stderr,"Info:%s\n",info);
12304 
12305   // Check whether two strings of interest are present and snag a
12306   // pointer to them.
12307   info2 = strstr(info,"RNG");
12308   if (!info2)
12309   {
12310     info2 = strstr(info,"PHG");
12311   }
12312   if (!info2)
12313   {
12314     phgd[0] = '\0';
12315     return(0);
12316   }
12317 
12318   found=0;
12319   len = (int)strlen(info2);
12320 
12321   if (len >= 9 && strncmp(info2,"PHG",3)==0
12322       && info2[7]=='/'
12323       && info2[8]!='A'  // trailing '/' not defined in Reference...
12324       && isdigit((int)info2[3])
12325       && isdigit((int)info2[4])
12326       && isdigit((int)info2[5])
12327       && isdigit((int)info2[6]))
12328   {
12329     substr(phgd,info2,7);
12330     found = 1;
12331     for (i=0; i<=len-8; i++)      // delete powergain from data extension field
12332     {
12333       info2[i] = info2[i+8];
12334     }
12335   }
12336   else
12337   {
12338     if (len >= 7 && strncmp(info2,"PHG",3)==0
12339         && isdigit((int)info2[3])
12340         && isdigit((int)info2[4])
12341         && isdigit((int)info2[5])
12342         && isdigit((int)info2[6]))
12343     {
12344       substr(phgd,info2,7);
12345       found = 1;
12346       for (i=0; i<=len-7; i++)      // delete powergain from data extension field
12347       {
12348         info2[i] = info2[i+7];
12349       }
12350     }
12351     else if (len >= 7 && strncmp(info2,"RNG",3)==0
12352              && isdigit((int)info2[3])
12353              && isdigit((int)info2[4])
12354              && isdigit((int)info2[5])
12355              && isdigit((int)info2[6]))
12356     {
12357       substr(phgd,info2,7);
12358       found = 1;
12359       for (i=0; i<=len-7; i++)      // delete powergain from data extension field
12360       {
12361         info2[i] = info2[i+7];
12362       }
12363     }
12364     else
12365     {
12366       phgd[0] = '\0';
12367     }
12368   }
12369   return(found);
12370 }
12371 
12372 
12373 
12374 
12375 
12376 /*
12377  *  Extract omnidf from APRS info field          "DFS1234/"    from APRS data extension
12378  */
extract_omnidf(char * info,char * phgd)12379 int extract_omnidf(char *info, char *phgd)
12380 {
12381   int i,len;
12382 
12383   len = (int)strlen(info);
12384   if (len >= 8 && strncmp(info,"DFS",3)==0 && info[7]=='/'    // trailing '/' not defined in Reference...
12385       && isdigit((int)info[3]) && isdigit((int)info[5]) && isdigit((int)info[6]))
12386   {
12387     substr(phgd,info,7);
12388     for (i=0; i<=len-8; i++)      // delete omnidf from data extension field
12389     {
12390       info[i] = info[i+8];
12391     }
12392     return(1);
12393   }
12394   else
12395   {
12396     phgd[0] = '\0';
12397     return(0);
12398   }
12399 }
12400 
12401 
12402 
12403 
12404 
12405 /*
12406  *  Extract signpost data from APRS info field: "{123}", an APRS data extension
12407  *  Format can be {1}, {12}, or {123}.  Letters or digits are ok.
12408  */
extract_signpost(char * info,char * signpost)12409 int extract_signpost(char *info, char *signpost)
12410 {
12411   int i,found,len,done;
12412 
12413   //0123456
12414   //{1}
12415   //{12}
12416   //{121}
12417 
12418   found=0;
12419   len = (int)strlen(info);
12420   if ( (len > 2)
12421        && (info[0] == '{')
12422        && ( (info[2] == '}' ) || (info[3] == '}' ) || (info[4] == '}' ) ) )
12423   {
12424 
12425     i = 1;
12426     done = 0;
12427     while (!done)                   // Snag up to three digits
12428     {
12429       if (info[i] == '}')         // We're done
12430       {
12431         found = i;              // found = position of '}' character
12432         done++;
12433       }
12434       else
12435       {
12436         signpost[i-1] = info[i];
12437       }
12438 
12439       i++;
12440 
12441       if ( (i > 4) && !done)      // Something is wrong, we should be done by now
12442       {
12443         done++;
12444         signpost[0] = '\0';
12445         return(0);
12446       }
12447     }
12448     substr(signpost,info+1,found-1);
12449     found++;
12450     for (i=0; i<=len-found; i++)    // delete omnidf from data extension field
12451     {
12452       info[i] = info[i+found];
12453     }
12454     return(1);
12455   }
12456   else
12457   {
12458     signpost[0] = '\0';
12459     return(0);
12460   }
12461 }
12462 
12463 
12464 
12465 
12466 
12467 /*
12468  *  Extract probability_min data from APRS info field: "Pmin1.23,"
12469  *  Please note the ending comma.  We use it to delimit the field.
12470  */
extract_probability_min(char * info,char * prob_min,int prob_min_size)12471 int extract_probability_min(char *info, char *prob_min, int prob_min_size)
12472 {
12473   int len,done;
12474   char *c;
12475   char *d;
12476 
12477 
12478   //fprintf(stderr,"%s\n",info);
12479 
12480   len = (int)strlen(info);
12481   if (len < 6)            // Too short
12482   {
12483     //fprintf(stderr,"Pmin too short: %s\n",info);
12484     prob_min[0] = '\0';
12485     return(0);
12486   }
12487 
12488   c = strstr(info,"Pmin");
12489   if (c == NULL)          // Pmin not found
12490   {
12491     //fprintf(stderr,"Pmin not found: %s\n",info);
12492     prob_min[0] = '\0';
12493     return(0);
12494   }
12495 
12496   c = c+4;    // Skip the Pmin part
12497   // Find the ending comma
12498   d = c;
12499   done = 0;
12500   while (!done)
12501   {
12502     if (*d == ',')      // We're done
12503     {
12504       done++;
12505     }
12506     else
12507     {
12508       d++;
12509     }
12510 
12511     // Check for string too long
12512     if ( ((d-c) > 10) && !done)      // Something is wrong, we should be done by now
12513     {
12514       //fprintf(stderr,"Pmin too long: %d,%s\n",d-c,info);
12515       prob_min[0] = '\0';
12516       return(0);
12517     }
12518   }
12519 
12520   // Copy the substring across
12521   xastir_snprintf(prob_min,
12522                   prob_min_size,
12523                   "%s",
12524                   c);
12525   prob_min[d-c] = '\0';
12526   prob_min[10] = '\0';    // Just to make sure
12527 
12528   // Delete data from data extension field
12529   d++;    // Skip the comma
12530   done = 0;
12531   while (!done)
12532   {
12533     *(c-4) = *d;
12534     if (*d == '\0')
12535     {
12536       done++;
12537     }
12538     c++;
12539     d++;
12540   }
12541 
12542   return(1);
12543 }
12544 
12545 
12546 
12547 
12548 
12549 /*
12550  *  Extract probability_max data from APRS info field: "Pmax1.23,"
12551  *  Please note the ending comma.  We use it to delimit the field.
12552  */
extract_probability_max(char * info,char * prob_max,int prob_max_size)12553 int extract_probability_max(char *info, char *prob_max, int prob_max_size)
12554 {
12555   int len,done;
12556   char *c;
12557   char *d;
12558 
12559 
12560   //fprintf(stderr,"%s\n",info);
12561 
12562   len = (int)strlen(info);
12563   if (len < 6)            // Too short
12564   {
12565     //fprintf(stderr,"Pmax too short: %s\n",info);
12566     prob_max[0] = '\0';
12567     return(0);
12568   }
12569 
12570   c = strstr(info,"Pmax");
12571   if (c == NULL)          // Pmax not found
12572   {
12573     //fprintf(stderr,"Pmax not found: %s\n",info);
12574     prob_max[0] = '\0';
12575     return(0);
12576   }
12577 
12578   c = c+4;    // Skip the Pmax part
12579   // Find the ending comma
12580   d = c;
12581   done = 0;
12582   while (!done)
12583   {
12584     if (*d == ',')      // We're done
12585     {
12586       done++;
12587     }
12588     else
12589     {
12590       d++;
12591     }
12592 
12593     // Check for string too long
12594     if ( ((d-c) > 10) && !done)      // Something is wrong, we should be done by now
12595     {
12596       //fprintf(stderr,"Pmax too long: %d,%s\n",d-c,info);
12597       prob_max[0] = '\0';
12598       return(0);
12599     }
12600   }
12601 
12602   // Copy the substring across
12603   xastir_snprintf(prob_max,
12604                   prob_max_size,
12605                   "%s",
12606                   c);
12607   prob_max[d-c] = '\0';
12608   prob_max[10] = '\0';    // Just to make sure
12609 
12610   // Delete data from data extension field
12611   d++;    // Skip the comma
12612   done = 0;
12613   while (!done)
12614   {
12615     *(c-4) = *d;
12616     if (*d == '\0')
12617     {
12618       done++;
12619     }
12620     c++;
12621     d++;
12622   }
12623 
12624   return(1);
12625 }
12626 
12627 
12628 
12629 
12630 
clear_area(DataRow * p_station)12631 static void clear_area(DataRow *p_station)
12632 {
12633   p_station->aprs_symbol.area_object.type           = AREA_NONE;
12634   p_station->aprs_symbol.area_object.color          = AREA_GRAY_LO;
12635   p_station->aprs_symbol.area_object.sqrt_lat_off   = 0;
12636   p_station->aprs_symbol.area_object.sqrt_lon_off   = 0;
12637   p_station->aprs_symbol.area_object.corridor_width = 0;
12638 }
12639 
12640 
12641 
12642 
12643 
12644 /*
12645  *  Extract Area Object
12646  */
extract_area(DataRow * p_station,char * data)12647 void extract_area(DataRow *p_station, char *data)
12648 {
12649   int i, val, len;
12650   unsigned int uval;
12651   AreaObject temp_area;
12652 
12653   /* NOTE: If we are here, the symbol was the area symbol.  But if this
12654      is a slightly corrupted packet, we shouldn't blow away the area info
12655      for this station, since it could be from a previously received good
12656      packet.  So we will work on temp_area and only copy to p_station at
12657      the end, returning on any error as we parse. N7TAP */
12658 
12659   //fprintf(stderr,"Area Data: %s\n", data);
12660 
12661   len = (int)strlen(data);
12662   val = data[0] - '0';
12663   if (val >= 0 && val <= AREA_MAX)
12664   {
12665     temp_area.type = val;
12666     val = data[4] - '0';
12667     temp_area.color = AREA_BLACK_HI; // Initial value
12668     if (data[3] == '/')
12669     {
12670       if (val >=0 && val <= 9)
12671       {
12672         temp_area.color = val;
12673       }
12674       else
12675       {
12676         if (debug_level & 2)
12677         {
12678           fprintf(stderr,"Bad area color (/)");
12679         }
12680         return;
12681       }
12682     }
12683     else if (data[3] == '1')
12684     {
12685       if (val >=0 && val <= 5)
12686       {
12687         temp_area.color = 10 + val;
12688       }
12689       else
12690       {
12691         if (debug_level & 2)
12692         {
12693           fprintf(stderr,"Bad area color (1)");
12694         }
12695         return;
12696       }
12697     }
12698 
12699     val = 0;
12700     if (isdigit((int)data[1]) && isdigit((int)data[2]))
12701     {
12702       val = (10 * (data[1] - '0')) + (data[2] - '0');
12703     }
12704     else
12705     {
12706       if (debug_level & 2)
12707       {
12708         fprintf(stderr,"Bad area sqrt_lat_off");
12709       }
12710       return;
12711     }
12712     temp_area.sqrt_lat_off = val;
12713 
12714     val = 0;
12715     if (isdigit((int)data[5]) && isdigit((int)data[6]))
12716     {
12717       val = (10 * (data[5] - '0')) + (data[6] - '0');
12718     }
12719     else
12720     {
12721       if (debug_level & 2)
12722       {
12723         fprintf(stderr,"Bad area sqrt_lon_off");
12724       }
12725       return;
12726     }
12727     temp_area.sqrt_lon_off = val;
12728 
12729     for (i = 0; i <= len-7; i++) // delete area object from data extension field
12730     {
12731       data[i] = data[i+7];
12732     }
12733     len -= 7;
12734 
12735     if (temp_area.type == AREA_LINE_RIGHT || temp_area.type == AREA_LINE_LEFT)
12736     {
12737       if (data[0] == '{')
12738       {
12739         if (sscanf(data, "{%u}", &uval) == 1)
12740         {
12741           temp_area.corridor_width = uval & 0xffff;
12742           for (i = 0; i <= len; i++)
12743             if (data[i] == '}')
12744             {
12745               break;
12746             }
12747           uval = i+1;
12748           for (i = 0; i <= (int)(len-uval); i++)
12749           {
12750             data[i] = data[i+uval];  // delete corridor width
12751           }
12752         }
12753         else
12754         {
12755           if (debug_level & 2)
12756           {
12757             fprintf(stderr,"Bad corridor width identifier");
12758           }
12759           temp_area.corridor_width = 0;
12760           return;
12761         }
12762       }
12763       else
12764       {
12765         if (debug_level & 2)
12766         {
12767           fprintf(stderr,"No corridor width specified");
12768         }
12769         temp_area.corridor_width = 0;
12770       }
12771     }
12772     else
12773     {
12774       temp_area.corridor_width = 0;
12775     }
12776   }
12777   else
12778   {
12779     if (debug_level & 2)
12780     {
12781       fprintf(stderr,"Bad area type: %c\n", data[0]);
12782     }
12783     return;
12784   }
12785 
12786   memcpy(&(p_station->aprs_symbol.area_object), &temp_area, sizeof(AreaObject));
12787 
12788   if (debug_level & 2)
12789   {
12790     fprintf(stderr,"AreaObject: type=%d color=%d sqrt_lat_off=%d sqrt_lon_off=%d corridor_width=%d\n",
12791             p_station->aprs_symbol.area_object.type,
12792             p_station->aprs_symbol.area_object.color,
12793             p_station->aprs_symbol.area_object.sqrt_lat_off,
12794             p_station->aprs_symbol.area_object.sqrt_lon_off,
12795             p_station->aprs_symbol.area_object.corridor_width);
12796   }
12797 }
12798 
12799 
12800 
12801 
12802 
12803 /*
12804  *  Extract Time from begin of line      [APRS Reference, chapter 6]
12805  *
12806  * If a time string is found in "data", it is deleted from the
12807  * beginning of the string.
12808  */
extract_time(DataRow * UNUSED (p_station),char * data,int type)12809 int extract_time(DataRow * UNUSED(p_station), char *data, int type)
12810 {
12811   int len, i;
12812   int ok = 0;
12813 
12814   // todo: better check of time data ranges
12815   len = (int)strlen(data);
12816   if (type == APRS_WX2)
12817   {
12818     // 8 digit time from stand-alone positionless weather stations...
12819     if (len > 8)
12820     {
12821       // MMDDHHMM   zulu time
12822       // MM 01-12         todo: better check of time data ranges
12823       // DD 01-31
12824       // HH 01-23
12825       // MM 01-59
12826       ok = 1;
12827       for (i=0; ok && i<8; i++)
12828         if (!isdigit((int)data[i]))
12829         {
12830           ok = 0;
12831         }
12832       if (ok)
12833       {
12834         //                substr(p_station->station_time,data+2,6);
12835         //                p_station->station_time_type = 'z';
12836         for (i=0; i<=len-8; i++)       // delete time from data
12837         {
12838           data[i] = data[i+8];
12839         }
12840       }
12841     }
12842   }
12843   else
12844   {
12845     if (len > 6)
12846     {
12847       // Status messages only with optional zulu format
12848       // DK7IN: APRS ref says one of 'z' '/' 'h', but I found 'c' at HB9TJM-8   ???
12849       if (toupper(data[6])=='Z' || data[6]=='/' || toupper(data[6])=='H')
12850       {
12851         ok = 1;
12852       }
12853       for (i=0; ok && i<6; i++)
12854         if (!isdigit((int)data[i]))
12855         {
12856           ok = 0;
12857         }
12858       if (ok)
12859       {
12860         //                substr(p_station->station_time,data,6);
12861         //                p_station->station_time_type = data[6];
12862         for (i=0; i<=len-7; i++)       // delete time from data
12863         {
12864           data[i] = data[i+7];
12865         }
12866       }
12867     }
12868   }
12869   return(ok);
12870 }
12871 
12872 
12873 
12874 
12875 
12876 // APRS Data Extensions               [APRS Reference p.27]
12877 //  .../...  Course & Speed, may be followed by others (see p.27)
12878 //  .../...  Wind Dir and Speed
12879 //  PHG....  Station Power and Effective Antenna Height/Gain
12880 //  RNG....  Pre-Calculated Radio Range
12881 //  DFS....  DF Signal Strength and Effective Antenna Height/Gain
12882 //  T../C..  Area Object Descriptor
12883 
12884 /* Extract one of several possible APRS Data Extensions */
process_data_extension(DataRow * p_station,char * data,int UNUSED (type))12885 void process_data_extension(DataRow *p_station, char *data, int UNUSED(type) )
12886 {
12887   char temp1[7+1];
12888   char temp2[3+1];
12889   char temp3[10+1];
12890   char bearing[3+1];
12891   char nrq[3+1];
12892 
12893   if (p_station->aprs_symbol.aprs_type == '\\' && p_station->aprs_symbol.aprs_symbol == 'l')
12894   {
12895     /* This check needs to come first because the area object extension can look
12896        exactly like what extract_speed_course will attempt to decode. */
12897     extract_area(p_station, data);
12898   }
12899   else
12900   {
12901     clear_area(p_station); // we got a packet with a non area symbol, so clear the data
12902 
12903     if (extract_speed_course(data,temp1,temp2))    // ... from Mic-E, etc.
12904     {
12905       //fprintf(stderr,"extracted speed/course\n");
12906 
12907       if (atof(temp2) > 0)
12908       {
12909         //fprintf(stderr,"course is non-zero\n");
12910         xastir_snprintf(p_station->speed,
12911                         sizeof(p_station->speed),
12912                         "%06.2f",
12913                         atof(temp1));
12914         xastir_snprintf(p_station->course,  // in degrees
12915                         sizeof(p_station->course),
12916                         "%s",
12917                         temp2);
12918       }
12919 
12920       if (extract_bearing_NRQ(data, bearing, nrq))    // Beam headings from DF'ing
12921       {
12922         //fprintf(stderr,"extracted bearing and NRQ\n");
12923         xastir_snprintf(p_station->bearing,
12924                         sizeof(p_station->bearing),
12925                         "%s",
12926                         bearing);
12927         xastir_snprintf(p_station->NRQ,
12928                         sizeof(p_station->NRQ),
12929                         "%s",
12930                         nrq);
12931         p_station->signal_gain[0] = '\0';   // And blank out the shgd values
12932       }
12933     }
12934     // Don't try to extract speed & course if a compressed
12935     // object.  Test for beam headings for compressed packets
12936     // here
12937     else if (extract_bearing_NRQ(data, bearing, nrq))    // Beam headings from DF'ing
12938     {
12939 
12940       //fprintf(stderr,"extracted bearing and NRQ\n");
12941       xastir_snprintf(p_station->bearing,
12942                       sizeof(p_station->bearing),
12943                       "%s",
12944                       bearing);
12945       xastir_snprintf(p_station->NRQ,
12946                       sizeof(p_station->NRQ),
12947                       "%s",
12948                       nrq);
12949       p_station->signal_gain[0] = '\0';   // And blank out the shgd values
12950     }
12951     else
12952     {
12953       if (extract_powergain_range(data,temp1))
12954       {
12955 
12956         //fprintf(stderr,"Found power_gain: %s\n", temp1);
12957 
12958         xastir_snprintf(p_station->power_gain,
12959                         sizeof(p_station->power_gain),
12960                         "%s",
12961                         temp1);
12962 
12963         if (extract_bearing_NRQ(data, bearing, nrq))    // Beam headings from DF'ing
12964         {
12965           //fprintf(stderr,"extracted bearing and NRQ\n");
12966           xastir_snprintf(p_station->bearing,
12967                           sizeof(p_station->bearing),
12968                           "%s",
12969                           bearing);
12970           xastir_snprintf(p_station->NRQ,
12971                           sizeof(p_station->NRQ),
12972                           "%s",
12973                           nrq);
12974           p_station->signal_gain[0] = '\0';   // And blank out the shgd values
12975         }
12976       }
12977       else
12978       {
12979         if (extract_omnidf(data,temp1))
12980         {
12981           xastir_snprintf(p_station->signal_gain,
12982                           sizeof(p_station->signal_gain),
12983                           "%s",
12984                           temp1);   // Grab the SHGD values
12985           p_station->bearing[0] = '\0';   // And blank out the bearing/NRQ values
12986           p_station->NRQ[0] = '\0';
12987 
12988           // The spec shows speed/course before DFS, but example packets that
12989           // come with DOSaprs show DFSxxxx/speed/course.  We'll take care of
12990           // that possibility by trying to decode speed/course again.
12991           if (extract_speed_course(data,temp1,temp2))    // ... from Mic-E, etc.
12992           {
12993             //fprintf(stderr,"extracted speed/course\n");
12994             if (atof(temp2) > 0)
12995             {
12996               //fprintf(stderr,"course is non-zero\n");
12997               xastir_snprintf(p_station->speed,
12998                               sizeof(p_station->speed),
12999                               "%06.2f",
13000                               atof(temp1));
13001               xastir_snprintf(p_station->course,
13002                               sizeof(p_station->course),
13003                               "%s",
13004                               temp2);                    // in degrees
13005             }
13006           }
13007 
13008           // The spec shows that omnidf and bearing/NRQ can be in the same
13009           // packet, which makes no sense, but we'll try to decode it that
13010           // way anyway.
13011           if (extract_bearing_NRQ(data, bearing, nrq))    // Beam headings from DF'ing
13012           {
13013             //fprintf(stderr,"extracted bearing and NRQ\n");
13014             xastir_snprintf(p_station->bearing,
13015                             sizeof(p_station->bearing),
13016                             "%s",
13017                             bearing);
13018             xastir_snprintf(p_station->NRQ,
13019                             sizeof(p_station->NRQ),
13020                             "%s",
13021                             nrq);
13022             //p_station->signal_gain[0] = '\0';   // And blank out the shgd values
13023           }
13024         }
13025       }
13026     }
13027 
13028     if (extract_signpost(data, temp2))
13029     {
13030       //fprintf(stderr,"extracted signpost data\n");
13031       xastir_snprintf(p_station->signpost,
13032                       sizeof(p_station->signpost),
13033                       "%s",
13034                       temp2);
13035     }
13036 
13037     if (extract_probability_min(data, temp3, sizeof(temp3)))
13038     {
13039       if (strncasecmp(temp3, "0.0", sizeof(temp3)) == 0)
13040       {
13041         p_station->probability_min[0] = '\0';   // Clear it out
13042       }
13043       else if (strncasecmp(temp3, "0", sizeof(temp3)) == 0)
13044       {
13045         p_station->probability_min[0] = '\0';   // Clear it out
13046       }
13047       else
13048       {
13049         //fprintf(stderr,"extracted probability_min data: %s\n",temp3);
13050         xastir_snprintf(p_station->probability_min,
13051                         sizeof(p_station->probability_min),
13052                         "%s",
13053                         temp3);
13054       }
13055     }
13056     else
13057     {
13058       p_station->probability_min[0] = '\0';   // Clear it out
13059     }
13060 
13061     if (extract_probability_max(data, temp3, sizeof(temp3)))
13062     {
13063       if (strncasecmp(temp3, "0.0", sizeof(temp3)) == 0)
13064       {
13065         p_station->probability_max[0] = '\0';   // Clear it out
13066       }
13067       else if (strncasecmp(temp3, "0", sizeof(temp3)) == 0)
13068       {
13069         p_station->probability_max[0] = '\0';   // Clear it out
13070       }
13071       else
13072       {
13073         //fprintf(stderr,"extracted probability_max data: %s\n",temp3);
13074         xastir_snprintf(p_station->probability_max,
13075                         sizeof(p_station->probability_max),
13076                         "%s",
13077                         temp3);
13078       }
13079     }
13080     else
13081     {
13082       p_station->probability_max[0] = '\0';   // Clear it out
13083     }
13084   }
13085 }
13086 
13087 
13088 
13089 
13090 
13091 /* extract all available information from info field */
process_info_field(DataRow * p_station,char * info,int UNUSED (type))13092 void process_info_field(DataRow *p_station, char *info, int UNUSED(type) )
13093 {
13094   char temp_data[6+1];
13095   //    char time_data[MAX_TIME];
13096 
13097   if (extract_altitude(info,temp_data))                           // get altitude
13098   {
13099     xastir_snprintf(p_station->altitude, sizeof(p_station->altitude), "%.2f",atof(temp_data)*0.3048);
13100     //fprintf(stderr,"%.2f\n",atof(temp_data)*0.3048);
13101   }
13102   // do other things...
13103 }
13104 
13105 
13106 
13107 
13108 
13109 ////////////////////////////////////////////////////////////////////////////////////////////////////
13110 
13111 
13112 // type: 18
13113 // call_sign: VE6GRR-15
13114 // path: GPSLV,TCPIP,VE7DIE*
13115 // data: GPRMC,034728,A,5101.016,N,11359.464,W,000.0,284.9,110701,018.0,
13116 // from: T
13117 // port: 0
13118 // origin:
13119 // third_party: 1
13120 
13121 
13122 
13123 
13124 
13125 //
13126 //  Extract data for $GPRMC, it fails if there is no position!!
13127 //
13128 // GPRMC,UTC-Time,status(A/V),lat,N/S,lon,E/W,SOG,COG,UTC-Date,Mag-Var,E/W,Fix-Quality[*CHK]
13129 // 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]
13130 //
13131 // The last field before the checksum is entirely optional, and in
13132 // fact first appeared in NMEA 2.3 (fairly recently).  Most GPS's do
13133 // not currently put out that field.  The field may be null or
13134 // nonexistent including the comma.  Only "A" or "D" are considered
13135 // to be active and reliable fixes if this field is present.
13136 // Fix-Quality:
13137 //  A: Autonomous
13138 //  D: Differential
13139 //  E: Estimated
13140 //  N: Not Valid
13141 //  S: Simulator
13142 //
13143 // $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
13144 // $GPRMC,104748.821,A,4301.1492,N,08803.0374,W,0.085048,102.36,010605,,*1A
13145 // $GPRMC,104749.821,A,4301.1492,N,08803.0377,W,0.054215,74.60,010605,,*2D
13146 //
extract_RMC(DataRow * p_station,char * data,char * call_sign,char * path,int * num_digits)13147 int extract_RMC(DataRow *p_station, char *data, char *call_sign, char *path, int *num_digits)
13148 {
13149   char temp_data[40]; // short term string storage, MAX_CALLSIGN, ...  ???
13150   char lat_s[20];
13151   char long_s[20];
13152   int ok;
13153   char *Substring[12];  // Pointers to substrings parsed by split_string()
13154   char temp_string[MAX_MESSAGE_LENGTH+1];
13155   char temp_char;
13156 
13157 
13158   if (debug_level & 256)
13159   {
13160     fprintf(stderr,"extract_RMC\n");
13161   }
13162 
13163   // should we copy it before processing? it changes data: ',' gets substituted by '\0' !!
13164   ok = 0; // Start out as invalid.  If we get enough info, we change this to a 1.
13165 
13166   if ( (data == NULL) || (strlen(data) < 34) )    // Not enough data to parse position from.
13167   {
13168     if (debug_level & 256)
13169     {
13170       fprintf(stderr,"Invalid RMC string: Too short\n");
13171     }
13172     return(ok);
13173   }
13174 
13175   p_station->record_type = NORMAL_GPS_RMC;
13176   // Create a timestamp from the current time
13177   // get_time saves the time in temp_data
13178   xastir_snprintf(p_station->pos_time,
13179                   sizeof(p_station->pos_time),
13180                   "%s",
13181                   get_time(temp_data));
13182   p_station->flag &= (~ST_MSGCAP);    // clear "message capable" flag
13183 
13184   /* check aprs type on call sign */
13185   p_station->aprs_symbol = *id_callsign(call_sign, path);
13186 
13187   // Make a copy of the incoming data.  The string passed to
13188   // split_string() gets destroyed.
13189   xastir_snprintf(temp_string,
13190                   sizeof(temp_string),
13191                   "%s",
13192                   data);
13193   split_string(temp_string, Substring, 12, ',');
13194 
13195   // The Substring[] array contains pointers to each substring in
13196   // the original data string.
13197 
13198   // GPRMC,034728,A,5101.016,N,11359.464,W,000.0,284.9,110701,018.0,E*7D
13199   //   0     1    2    3     4    5      6   7    8      9     10    11
13200 
13201   if (Substring[0] == NULL)   // No GPRMC string
13202   {
13203     return(ok);
13204   }
13205 
13206   if (Substring[1] == NULL)   // No time string
13207   {
13208     return(ok);
13209   }
13210 
13211   if (Substring[2] == NULL)   // No valid fix char
13212   {
13213     return(ok);
13214   }
13215 
13216   if (Substring[2][0] != 'A' && Substring[2][0] != 'V')
13217   {
13218     return(ok);
13219   }
13220   // V is a warning but we can get good data still ?
13221   // DK7IN: got no position with 'V' !
13222 
13223   if (Substring[3] == NULL)   // No latitude string
13224   {
13225     return(ok);
13226   }
13227 
13228   if (Substring[4] == NULL)   // No latitude N/S
13229   {
13230     return(ok);
13231   }
13232 
13233   // Need to check lat_s for validity here.  Note that some GPS's put out another digit of precision
13234   // (4801.1234) or leave one out (4801.12).  Next character after digits should be a ','
13235 
13236   // Count digits after the decimal point for latitude
13237   if (strchr(Substring[3],'.'))
13238   {
13239     *num_digits = strlen(Substring[3]) - (int)(strchr(Substring[3],'.') - Substring[3]) - 1;
13240   }
13241   else
13242   {
13243     *num_digits = 0;
13244   }
13245 
13246   temp_char = toupper((int)Substring[4][0]);
13247 
13248   if (temp_char != 'N' && temp_char != 'S')   // Bad N/S
13249   {
13250     return(ok);
13251   }
13252 
13253   xastir_snprintf(lat_s,
13254                   sizeof(lat_s),
13255                   "%s%c",
13256                   Substring[3],
13257                   temp_char);
13258 
13259   if (Substring[5] == NULL)   // No longitude string
13260   {
13261     return(ok);
13262   }
13263 
13264   if (Substring[6] == NULL)   // No longitude E/W
13265   {
13266     return(ok);
13267   }
13268 
13269   // Need to check long_s for validity here.  Should be all digits.  Note that some GPS's put out another
13270   // digit of precision.  (12201.1234).  Next character after digits should be a ','
13271 
13272   temp_char = toupper((int)Substring[6][0]);
13273 
13274   if (temp_char != 'E' && temp_char != 'W')   // Bad E/W
13275   {
13276     return(ok);
13277   }
13278 
13279   xastir_snprintf(long_s,
13280                   sizeof(long_s),
13281                   "%s%c",
13282                   Substring[5],
13283                   temp_char);
13284 
13285   p_station->coord_lat = convert_lat_s2l(lat_s);
13286   p_station->coord_lon = convert_lon_s2l(long_s);
13287 
13288   // If we've made it this far, We have enough for a position now!
13289   ok = 1;
13290 
13291   // Now that we have a basic position, let's see what other data
13292   // can be parsed from the packet.  The rest of it can still be
13293   // corrupt, so we're proceeding carefully under yellow alert on
13294   // impulse engines only.
13295 
13296   // GPRMC,034728,A,5101.016,N,11359.464,W,000.0,284.9,110701,018.0,E*7D
13297   //   0     1    2    3     4    5      6   7    8      9     10    11
13298 
13299   if (Substring[7] == NULL)   // No speed string
13300   {
13301     p_station->speed[0] = '\0'; // No speed available
13302     return(ok);
13303   }
13304   else
13305   {
13306     xastir_snprintf(p_station->speed,
13307                     MAX_SPEED,
13308                     "%s",
13309                     Substring[7]);
13310     // Is it always knots, otherwise we need a conversion!
13311   }
13312 
13313   if (Substring[8] == NULL)   // No course string
13314   {
13315     xastir_snprintf(p_station->course,
13316                     sizeof(p_station->course),
13317                     "000.0");  // No course available
13318     return(ok);
13319   }
13320   else
13321   {
13322     xastir_snprintf(p_station->course,
13323                     MAX_COURSE,
13324                     "%s",
13325                     Substring[8]);
13326   }
13327 
13328   if (debug_level & 256)
13329   {
13330     if (ok)
13331     {
13332       fprintf(stderr,"extract_RMC succeeded: %s\n",data);
13333     }
13334     else
13335     {
13336       fprintf(stderr,"extract_RMC failed: %s\n",data);
13337     }
13338   }
13339 
13340   return(ok);
13341 }
13342 
13343 
13344 
13345 
13346 
13347 //
13348 //  Extract data for $GPGGA
13349 //
13350 // 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]
13351 // 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]
13352 //
13353 // GPS-Quality:
13354 //  0: Invalid Fix
13355 //  1: GPS Fix
13356 //  2: DGPS Fix
13357 //  3: PPS Fix
13358 //  4: RTK Fix
13359 //  5: Float RTK Fix
13360 //  6: Estimated (dead-reckoning) Fix
13361 //  7: Manual Input Mode
13362 //  8: Simulation Mode
13363 //
13364 // $GPGGA,170834,4124.8963,N,08151.6838,W,1,05,1.5,280.2,M,-34.0,M,,,*75
13365 // $GPGGA,104438.833,4301.1439,N,08803.0338,W,1,05,1.8,185.8,M,-34.2,M,0.0,0000*40
13366 //
13367 // nsat=Number of Satellites being tracked
13368 //
13369 //
extract_GGA(DataRow * p_station,char * data,char * call_sign,char * path,int * num_digits)13370 int extract_GGA(DataRow *p_station,char *data,char *call_sign, char *path, int *num_digits)
13371 {
13372   char temp_data[40]; // short term string storage, MAX_CALLSIGN, ...  ???
13373   char lat_s[20];
13374   char long_s[20];
13375   int  ok;
13376   char *Substring[15];  // Pointers to substrings parsed by split_string()
13377   char temp_string[MAX_MESSAGE_LENGTH+1];
13378   char temp_char;
13379   int  temp_num;
13380 
13381 
13382   if (debug_level & 256)
13383   {
13384     fprintf(stderr, "extract_GGA\n");
13385   }
13386 
13387   ok = 0; // Start out as invalid.  If we get enough info, we change this to a 1.
13388 
13389   if ( (data == NULL) || (strlen(data) < 32) )  // Not enough data to parse position from.
13390   {
13391     return(ok);
13392   }
13393 
13394   p_station->record_type = NORMAL_GPS_GGA;
13395   // Create a timestamp from the current time
13396   // get_time saves the time in temp_data
13397   xastir_snprintf(p_station->pos_time,
13398                   sizeof(p_station->pos_time),
13399                   "%s",
13400                   get_time(temp_data));
13401   p_station->flag &= (~ST_MSGCAP);    // clear "message capable" flag
13402 
13403   /* check aprs type on call sign */
13404   p_station->aprs_symbol = *id_callsign(call_sign, path);
13405 
13406   // Make a copy of the incoming data.  The string passed to
13407   // split_string() gets destroyed.
13408   xastir_snprintf(temp_string,
13409                   sizeof(temp_string),
13410                   "%s",
13411                   data);
13412   split_string(temp_string, Substring, 15, ',');
13413 
13414   // The Substring[] array contains pointers to each substring in
13415   // the original data string.
13416 
13417 
13418   // 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]
13419   //   0     1              2         3        4         5        6      7     8        9     1     1     1    1        1
13420   //                                                                                          0     1     2    3        4
13421 
13422   if (Substring[0] == NULL)  // No GPGGA string
13423   {
13424     return(ok);
13425   }
13426 
13427   if (Substring[1] == NULL)  // No time string
13428   {
13429     return(ok);
13430   }
13431 
13432   if (Substring[2] == NULL)   // No latitude string
13433   {
13434     return(ok);
13435   }
13436 
13437   if (Substring[3] == NULL)   // No latitude N/S
13438   {
13439     return(ok);
13440   }
13441 
13442   // Need to check lat_s for validity here.  Note that some GPS's put out another digit of precision
13443   // (4801.1234).  Next character after digits should be a ','
13444 
13445   // Count digits after the decimal point for latitude
13446   if (strchr(Substring[2],'.'))
13447   {
13448     *num_digits = strlen(Substring[2]) - (int)(strchr(Substring[2],'.') - Substring[2]) - 1;
13449   }
13450   else
13451   {
13452     *num_digits = 0;
13453   }
13454 
13455   temp_char = toupper((int)Substring[3][0]);
13456 
13457   if (temp_char != 'N' && temp_char != 'S')   // Bad N/S
13458   {
13459     return(ok);
13460   }
13461 
13462   xastir_snprintf(lat_s,
13463                   sizeof(lat_s),
13464                   "%s%c",
13465                   Substring[2],
13466                   temp_char);
13467 
13468   if (Substring[4] == NULL)   // No longitude string
13469   {
13470     return(ok);
13471   }
13472 
13473   if (Substring[5] == NULL)   // No longitude E/W
13474   {
13475     return(ok);
13476   }
13477 
13478   // Need to check long_s for validity here.  Should be all digits.  Note that some GPS's put out another
13479   // digit of precision.  (12201.1234).  Next character after digits should be a ','
13480 
13481   temp_char = toupper((int)Substring[5][0]);
13482 
13483   if (temp_char != 'E' && temp_char != 'W')   // Bad E/W
13484   {
13485     return(ok);
13486   }
13487 
13488   xastir_snprintf(long_s,
13489                   sizeof(long_s),
13490                   "%s%c",
13491                   Substring[4],
13492                   temp_char);
13493 
13494   p_station->coord_lat = convert_lat_s2l(lat_s);
13495   p_station->coord_lon = convert_lon_s2l(long_s);
13496 
13497   // If we've made it this far, We have enough for a position now!
13498   ok = 1;
13499 
13500 
13501   // Now that we have a basic position, let's see what other data
13502   // can be parsed from the packet.  The rest of it can still be
13503   // corrupt, so we're proceeding carefully under yellow alert on
13504   // impulse engines only.
13505 
13506   // Check for valid fix {
13507   if (Substring[6] == NULL
13508       || Substring[6][0] == '0'      // Fix quality
13509       || Substring[7] == NULL        // Sat number
13510       || Substring[8] == NULL        // hdop
13511       || Substring[9] == NULL)       // Altitude in meters
13512   {
13513     p_station->sats_visible[0] = '\0'; // Store empty sats visible
13514     p_station->altitude[0] = '\0';;    // Store empty altitude
13515     return(ok); // A field between fix quality and altitude is missing
13516   }
13517 
13518   // Need to check for validity of this number.  Should be 0-12?  Perhaps a few more with WAAS, GLONASS, etc?
13519   temp_num = atoi(Substring[7]);
13520   if (temp_num < 0 || temp_num > 30)
13521   {
13522     return(ok); // Number of satellites not valid
13523   }
13524   else
13525   {
13526     // Store
13527     xastir_snprintf(p_station->sats_visible,
13528                     sizeof(p_station->sats_visible),
13529                     "%d",
13530                     temp_num);
13531   }
13532 
13533 
13534   // Check for valid number for HDOP instead of just throwing it away?
13535 
13536 
13537   xastir_snprintf(p_station->altitude,
13538                   sizeof(p_station->altitude),
13539                   "%s",
13540                   Substring[9]); // Get altitude
13541 
13542   // Need to check for valid altitude before conversion
13543 
13544   // unit is in meters, if not adjust value ???
13545 
13546   if (Substring[10] == NULL)  // No units for altitude
13547   {
13548     return(ok);
13549   }
13550 
13551   if (Substring[10][0] != 'M')
13552   {
13553     //fprintf(stderr,"ERROR: should adjust altitude for meters\n");
13554     //} else {  // Altitude units wrong.  Assume altitude bad
13555     p_station->altitude[0] = '\0';
13556   }
13557 
13558   if (debug_level & 256)
13559   {
13560     if (ok)
13561     {
13562       fprintf(stderr,"extract_GGA succeeded: %s\n",data);
13563     }
13564     else
13565     {
13566       fprintf(stderr,"extract_GGA failed: %s\n",data);
13567     }
13568   }
13569 
13570   return(ok);
13571 }
13572 
13573 
13574 
13575 
13576 
13577 //
13578 //  Extract data for $GPGLL
13579 //
13580 // $GPGLL,4748.811,N,12219.564,W,033850,A*3C
13581 // lat, long, UTCtime in hhmmss, A=Valid, checksum
13582 //
13583 // GPGLL,4748.811,N,12219.564,W,033850,A*3C
13584 //   0       1    2      3    4    5   6
13585 //
extract_GLL(DataRow * p_station,char * data,char * call_sign,char * path,int * num_digits)13586 int extract_GLL(DataRow *p_station,char *data,char *call_sign, char *path, int *num_digits)
13587 {
13588   char temp_data[40]; // short term string storage, MAX_CALLSIGN, ...  ???
13589   char lat_s[20];
13590   char long_s[20];
13591   int ok;
13592   char *Substring[7];  // Pointers to substrings parsed by split_string()
13593   char temp_string[MAX_MESSAGE_LENGTH+1];
13594   char temp_char;
13595 
13596 
13597   if (debug_level & 256)
13598   {
13599     fprintf(stderr, "extract_GLL\n");
13600   }
13601 
13602   ok = 0; // Start out as invalid.  If we get enough info, we change this to a 1.
13603 
13604   if ( (data == NULL) || (strlen(data) < 28) )  // Not enough data to parse position from.
13605   {
13606     return(ok);
13607   }
13608 
13609   p_station->record_type = NORMAL_GPS_GLL;
13610   // Create a timestamp from the current time
13611   // get_time saves the time in temp_data
13612   xastir_snprintf(p_station->pos_time,
13613                   sizeof(p_station->pos_time),
13614                   "%s",
13615                   get_time(temp_data));
13616   p_station->flag &= (~ST_MSGCAP);    // clear "message capable" flag
13617 
13618   /* check aprs type on call sign */
13619   p_station->aprs_symbol = *id_callsign(call_sign, path);
13620 
13621   // Make a copy of the incoming data.  The string passed to
13622   // split_string() gets destroyed.
13623   xastir_snprintf(temp_string,
13624                   sizeof(temp_string),
13625                   "%s",
13626                   data);
13627   split_string(temp_string, Substring, 7, ',');
13628 
13629   // The Substring[] array contains pointers to each substring in
13630   // the original data string.
13631 
13632   if (Substring[0] == NULL)  // No GPGGA string
13633   {
13634     return(ok);
13635   }
13636 
13637   if (Substring[1] == NULL)  // No latitude string
13638   {
13639     return(ok);
13640   }
13641 
13642   if (Substring[2] == NULL)   // No N/S string
13643   {
13644     return(ok);
13645   }
13646 
13647   if (Substring[3] == NULL)   // No longitude string
13648   {
13649     return(ok);
13650   }
13651 
13652   if (Substring[4] == NULL)   // No E/W string
13653   {
13654     return(ok);
13655   }
13656 
13657   temp_char = toupper((int)Substring[2][0]);
13658   if (temp_char != 'N' && temp_char != 'S')
13659   {
13660     return(ok);
13661   }
13662 
13663   xastir_snprintf(lat_s,
13664                   sizeof(lat_s),
13665                   "%s%c",
13666                   Substring[1],
13667                   temp_char);
13668   // Need to check lat_s for validity here.  Note that some GPS's put out another digit of precision
13669   // (4801.1234).  Next character after digits should be a ','
13670 
13671   // Count digits after the decimal point for latitude
13672   if (strchr(Substring[1],'.'))
13673   {
13674     *num_digits = strlen(Substring[1]) - (int)(strchr(Substring[1],'.') - Substring[1]) - 1;
13675   }
13676   else
13677   {
13678     *num_digits = 0;
13679   }
13680 
13681   temp_char = toupper((int)Substring[4][0]);
13682   if (temp_char != 'E' && temp_char != 'W')
13683   {
13684     return(ok);
13685   }
13686 
13687   xastir_snprintf(long_s,
13688                   sizeof(long_s),
13689                   "%s%c",
13690                   Substring[3],
13691                   temp_char);
13692   // Need to check long_s for validity here.  Should be all digits.  Note that some GPS's put out another
13693   // digit of precision.  (12201.1234).  Next character after digits should be a ','
13694 
13695   p_station->coord_lat = convert_lat_s2l(lat_s);
13696   p_station->coord_lon = convert_lon_s2l(long_s);
13697   ok = 1; // We have enough for a position now
13698 
13699   xastir_snprintf(p_station->course,
13700                   sizeof(p_station->course),
13701                   "000.0");  // Fill in with dummy values
13702   p_station->speed[0] = '\0';        // Fill in with dummy values
13703 
13704   // A is valid, V is a warning but we can get good data still?
13705   // We don't currently check the data valid flag.
13706 
13707   return(ok);
13708 }
13709 
13710 
13711 
13712 
13713 
13714 // Add a status line to the linked-list of status records
13715 // associated with a station.  Note that a blank status line is
13716 // allowed, but we don't store that unless we have seen a non-blank
13717 // status line previously.
13718 //
add_status(DataRow * p_station,char * status_string)13719 void add_status(DataRow *p_station, char *status_string)
13720 {
13721   CommentRow *ptr;
13722   int add_it = 0;
13723   int len;
13724 
13725 
13726   len = strlen(status_string);
13727 
13728   // Eliminate line-end chars
13729   if (len > 1)
13730   {
13731     if ( (status_string[len-1] == '\n')
13732          || (status_string[len-1] == '\r') )
13733     {
13734       status_string[len-1] = '\0';
13735     }
13736   }
13737 
13738   // Shorten it
13739   //fprintf(stderr,"1Status: (%s)\n",status_string);
13740   (void)remove_trailing_spaces(status_string);
13741   //fprintf(stderr,"2Status: (%s)\n",status_string);
13742   (void)remove_leading_spaces(status_string);
13743   //fprintf(stderr,"3Status: (%s)\n",status_string);
13744 
13745   len = strlen(status_string);
13746 
13747   // Check for valid pointer
13748   if (p_station != NULL)
13749   {
13750 
13751     // We should probably create a new station record for this station
13752     // if there isn't one.  This allows us to collect as much info about
13753     // a station as we can until a posit comes in for it.  Right now we
13754     // don't do this.  If we decide to do this in the future, we also
13755     // need a method to find out the info about that station without
13756     // having to click on an icon, 'cuz the symbol won't be on our map
13757     // until we have a posit.
13758 
13759     //fprintf(stderr,"Station:%s\tStatus:%s\n",p_station->call_sign,status_string);
13760 
13761     // Check whether we have any data stored for this station
13762     if (p_station->status_data == NULL)
13763     {
13764       if (len > 0)
13765       {
13766         // No status stored yet and new status is non-NULL,
13767         // so add it to the list.
13768         add_it++;
13769       }
13770     }
13771     else    // We have status data stored already
13772     {
13773       // Check for an identical string
13774       CommentRow *ptr2;
13775       int ii = 0;
13776 
13777       ptr = p_station->status_data;
13778       ptr2 = ptr;
13779       while (ptr != NULL)
13780       {
13781 
13782         // Note that both text_ptr and comment_string can be
13783         // empty strings.
13784 
13785         if (strcasecmp(ptr->text_ptr, status_string) == 0)
13786         {
13787           // Found a matching string
13788           //fprintf(stderr,"Found match:
13789           //%s:%s\n",p_station->call_sign,status_string);
13790 
13791           // Instead of updating the timestamp, we'll delete the record from
13792           // the list and add it to the top in the code below.  Make sure to
13793           // tweak the "ii" pointer so that we don't end up shortening the
13794           // list unnecessarily.
13795           if (ptr == p_station->status_data)
13796           {
13797 
13798             // Only update the timestamp: We're at the
13799             // beginning of the list already.
13800             ptr->sec_heard = sec_now();
13801 
13802             return; // No need to add a new record
13803           }
13804           else    // Delete the record
13805           {
13806             CommentRow *ptr3;
13807 
13808             // Keep a pointer to the record
13809             ptr3 = ptr;
13810 
13811             // Close the chain, skipping this record
13812             ptr2->next = ptr3->next;
13813 
13814             // Skip "ptr" over the record we wish to
13815             // delete
13816             ptr = ptr3->next;
13817 
13818             // Free the record
13819             free(ptr3->text_ptr);
13820             free(ptr3);
13821 
13822             // Muck with the counter 'cuz we just
13823             // deleted one record
13824             ii--;
13825           }
13826         }
13827         ptr2 = ptr; // Back one record
13828         if (ptr != NULL)
13829         {
13830           ptr = ptr->next;
13831         }
13832         ii++;
13833       }
13834 
13835 
13836       // No matching string found, or new timestamp found for
13837       // old record.  Add it to the top of the list.
13838       add_it++;
13839       //fprintf(stderr,"No match:
13840       //%s:%s\n",p_station->call_sign,status_string);
13841 
13842       // We counted the records.  If we have more than
13843       // MAX_STATUS_LINES records we'll delete/free the last
13844       // one to make room for the next.  This keeps us from
13845       // storing unique status records ad infinitum for active
13846       // stations, limiting the total space used.
13847       //
13848       if (ii >= MAX_STATUS_LINES)
13849       {
13850         // We know we didn't get a match, and that our list
13851         // is full (as full as we want it to be).  Traverse
13852         // the list again, looking for ptr2->next->next ==
13853         // NULL.  If found, free last record and set the
13854         // ptr2->next pointer to NULL.
13855         ptr2 = p_station->status_data;
13856         while (ptr2->next->next != NULL)
13857         {
13858           ptr2 = ptr2->next;
13859         }
13860         // At this point, we have a pointer to the last
13861         // record in ptr2->next.  Free it and the text
13862         // string in it.
13863         free(ptr2->next->text_ptr);
13864         free(ptr2->next);
13865         ptr2->next = NULL;
13866       }
13867     }
13868 
13869     if (add_it)     // We add to the beginning so we don't have
13870     {
13871       // to traverse the linked list.  This also
13872       // puts new records at the beginning of the
13873       // list to keep them in sorted order.
13874 
13875       ptr = p_station->status_data;  // Save old pointer to records
13876       p_station->status_data = (CommentRow *)malloc(sizeof(CommentRow));
13877       CHECKMALLOC(p_station->status_data);
13878 
13879       p_station->status_data->next = ptr;    // Link in old records or NULL
13880 
13881       // Malloc the string space we'll need, attach it to our
13882       // new record
13883       p_station->status_data->text_ptr = (char *)malloc(sizeof(char) * (len+1));
13884       CHECKMALLOC(p_station->status_data->text_ptr);
13885 
13886       // Fill in the string
13887       xastir_snprintf(p_station->status_data->text_ptr,
13888                       len+1,
13889                       "%s",
13890                       status_string);
13891 
13892       // Fill in the timestamp
13893       p_station->status_data->sec_heard = sec_now();
13894 
13895       //fprintf(stderr,"Station:%s\tStatus:%s\n\n",p_station->call_sign,p_station->status_data->text_ptr);
13896     }
13897   }
13898 }
13899 
13900 
13901 
13902 
13903 
13904 // Add a comment line to the linked-list of comment records
13905 // associated with a station.  Note that a blank comment is allowed
13906 // and necessary for the times when we wish to blank out the comment
13907 // on an object/item, but we don't store that unless we have seen a
13908 // non-blank comment line previously.
13909 //
add_comment(DataRow * p_station,char * comment_string)13910 void add_comment(DataRow *p_station, char *comment_string)
13911 {
13912   CommentRow *ptr;
13913   int add_it = 0;
13914   int len;
13915 
13916 
13917   len = strlen(comment_string);
13918 
13919   // Eliminate line-end chars
13920   if (len > 1)
13921   {
13922     if ( (comment_string[len-1] == '\n')
13923          || (comment_string[len-1] == '\r') )
13924     {
13925       comment_string[len-1] = '\0';
13926     }
13927   }
13928 
13929   // Shorten it
13930   //fprintf(stderr,"1Comment: (%s)\n",comment_string);
13931   (void)remove_trailing_spaces(comment_string);
13932   //fprintf(stderr,"2Comment: (%s)\n",comment_string);
13933 
13934   ///////TVR DEBUGING RESULTS 28 March 2007:
13935   //NO! DON'T DO THIS --- it breaks multipoint objects!
13936   //    (void)remove_leading_spaces(comment_string);
13937   ///////////////////////////////////////////
13938 
13939   //fprintf(stderr,"3Comment: (%s)\n",comment_string);
13940 
13941   len = strlen(comment_string);
13942 
13943   // Check for valid pointer
13944   if (p_station != NULL)
13945   {
13946 
13947     // Check whether we have any data stored for this station
13948     if (p_station->comment_data == NULL)
13949     {
13950       if (len > 0)
13951       {
13952         // No comments stored yet and new comment is
13953         // non-NULL, so add it to the list.
13954         add_it++;
13955       }
13956     }
13957     else    // We have comment data stored already
13958     {
13959       // Check for an identical string
13960       CommentRow *ptr2;
13961       int ii = 0;
13962 
13963       ptr = p_station->comment_data;
13964       ptr2 = ptr;
13965       while (ptr != NULL)
13966       {
13967 
13968         // Note that both text_ptr and comment_string can be
13969         // empty strings.
13970 
13971         if (strcasecmp(ptr->text_ptr, comment_string) == 0)
13972         {
13973           // Found a matching string
13974           //fprintf(stderr,"Found match: %s:%s\n",p_station->call_sign,comment_string);
13975 
13976           // Instead of updating the timestamp, we'll delete the record from
13977           // the list and add it to the top in the code below.  Make sure to
13978           // tweak the "ii" pointer so that we don't end up shortening the
13979           // list unnecessarily.
13980           if (ptr == p_station->comment_data)
13981           {
13982             // Only update the timestamp:  We're at the
13983             // beginning of the list already.
13984             ptr->sec_heard = sec_now();
13985 
13986             return; // No need to add a new record
13987           }
13988           else    // Delete the record
13989           {
13990             CommentRow *ptr3;
13991 
13992             // Keep a pointer to the record
13993             ptr3 = ptr;
13994 
13995             // Close the chain, skipping this record
13996             ptr2->next = ptr3->next;
13997 
13998             // Skip "ptr" over the record we with to
13999             // delete
14000             ptr = ptr3->next;
14001 
14002             // Free the record
14003             free(ptr3->text_ptr);
14004             free(ptr3);
14005 
14006             // Muck with the counter 'cuz we just
14007             // deleted one record
14008             ii--;
14009           }
14010         }
14011         ptr2 = ptr; // Keep this pointer one record back as
14012         // we progress.
14013 
14014         if (ptr != NULL)
14015         {
14016           ptr = ptr->next;
14017         }
14018 
14019         ii++;
14020       }
14021       // No matching string found, or new timestamp found for
14022       // old record.  Add it to the top of the list.
14023       add_it++;
14024       //fprintf(stderr,"No match: %s:%s\n",p_station->call_sign,comment_string);
14025 
14026       // We counted the records.  If we have more than
14027       // MAX_COMMENT_LINES records we'll delete/free the last
14028       // one to make room for the next.  This keeps us from
14029       // storing unique comment records ad infinitum for
14030       // active stations, limiting the total space used.
14031       //
14032       if (ii >= MAX_COMMENT_LINES)
14033       {
14034 
14035         // We know we didn't get a match, and that our list
14036         // is full (as we want it to be).  Traverse the list
14037         // again, looking for ptr2->next->next == NULL.  If
14038         // found, free that last record and set the
14039         // ptr2->next pointer to NULL.
14040         ptr2 = p_station->comment_data;
14041         while (ptr2->next->next != NULL)
14042         {
14043           ptr2 = ptr2->next;
14044         }
14045         // At this point, we have a pointer to the last
14046         // record in ptr2->next.  Free it and the text
14047         // string in it.
14048         free(ptr2->next->text_ptr);
14049         free(ptr2->next);
14050         ptr2->next = NULL;
14051       }
14052     }
14053 
14054     if (add_it)     // We add to the beginning so we don't have
14055     {
14056       // to traverse the linked list.  This also
14057       // puts new records at the beginning of the
14058       // list to keep them in sorted order.
14059 
14060       ptr = p_station->comment_data;  // Save old pointer to records
14061       p_station->comment_data = (CommentRow *)malloc(sizeof(CommentRow));
14062       CHECKMALLOC(p_station->comment_data);
14063 
14064       p_station->comment_data->next = ptr;    // Link in old records or NULL
14065 
14066       // Malloc the string space we'll need, attach it to our
14067       // new record
14068       p_station->comment_data->text_ptr = (char *)malloc(sizeof(char) * (len+1));
14069       CHECKMALLOC(p_station->comment_data->text_ptr);
14070 
14071       // Fill in the string
14072       xastir_snprintf(p_station->comment_data->text_ptr,
14073                       len+1,
14074                       "%s",
14075                       comment_string);
14076 
14077       // Fill in the timestamp
14078       p_station->comment_data->sec_heard = sec_now();
14079     }
14080   }
14081 }
14082 
14083 
14084 
14085 
14086 
14087 /*
14088  *  Add data from APRS information field to station database
14089  *  Returns a 1 if successful
14090  */
data_add(int type,char * call_sign,char * path,char * data,char from,int port,char * origin,int third_party,int station_is_mine,int object_is_mine)14091 int data_add(int type,
14092              char *call_sign,
14093              char *path,
14094              char *data,
14095              char from,
14096              int port,
14097              char *origin,
14098              int third_party,
14099              int station_is_mine,
14100              int object_is_mine)
14101 {
14102 
14103   DataRow *p_station;
14104   DataRow *p_time;
14105   char call[MAX_CALLSIGN+1];
14106   char new_station;
14107   long last_lat, last_lon;
14108   char last_alt[MAX_ALTITUDE];
14109   char last_speed[MAX_SPEED+1];
14110   char last_course[MAX_COURSE+1];
14111   time_t last_stn_sec;
14112   short last_flag;
14113   char temp_data[40]; // short term string storage, MAX_CALLSIGN, ...
14114   long l_lat, l_lon;
14115   double distance;
14116   char station_id[600];
14117   int found_pos;
14118   float value;
14119   WeatherRow *weather;
14120   int moving;
14121   int changed_pos;
14122   int screen_update;
14123   int ok, store;
14124   int ok_to_display;
14125   int compr_pos;
14126   char *p = NULL; // KC2ELS - used for WIDEn-N
14127   int direct = 0;
14128   int new_origin_is_mine = 0;
14129   int num_digits = 0; // Number of digits after decimal point in NMEA string
14130 
14131 #ifdef HAVE_DB
14132   int ii;  // loop counter for interfaces list
14133 #endif /* HAVE_DB */
14134 
14135   // call and path had been validated before
14136   // Check "data" against the max APRS length, and dump the packet if too long.
14137   if ( (data != NULL) && (strlen(data) > MAX_INFO_FIELD_SIZE) )     // Overly long packet.  Throw it away.
14138   {
14139     if (debug_level & 1)
14140     {
14141       fprintf(stderr,"data_add: Overly long packet.  Throwing it away.\n");
14142     }
14143     return(0);  // Not an ok packet
14144   }
14145 
14146   // Check for some reasonable string in call_sign parameter
14147   if (call_sign == NULL || strlen(call_sign) == 0)
14148   {
14149     if (debug_level & 1)
14150     {
14151       fprintf(stderr,"data_add():call_sign was NULL or empty, exiting\n");
14152     }
14153     return(0);
14154   }
14155 
14156   if (debug_level & 1)
14157     fprintf(stderr,"data_add:\n\ttype: %d\n\tcall_sign: %s\n\tpath: %s\n\tdata: %s\n\tfrom: %c\n\tport: %d\n\torigin: %s\n\tthird_party: %d\n",
14158             type,
14159             call_sign,
14160             path,
14161             data ? data : "NULL",       // This parameter may be NULL, if exp1 then exp2 else exp3
14162             from,
14163             port,
14164             origin ? origin : "NULL",   // This parameter may be NULL
14165             third_party);
14166 
14167   if (origin && is_my_call(origin, 1))
14168   {
14169     new_origin_is_mine++;   // The new object/item is owned by me
14170   }
14171 
14172   weather = NULL; // only to make the compiler happy...
14173   found_pos = 1;
14174   xastir_snprintf(call,
14175                   sizeof(call),
14176                   "%s",
14177                   call_sign);
14178   p_station = NULL;
14179   new_station = (char)FALSE;                          // to make the compiler happy...
14180   last_lat = 0L;
14181   last_lon = 0L;
14182   last_stn_sec = sec_now();
14183   last_alt[0]    = '\0';
14184   last_speed[0]  = '\0';
14185   last_course[0] = '\0';
14186   last_flag      = 0;
14187   ok = 0;
14188   store = 0;
14189   p_time = NULL;                                      // add to end of time sorted list (newest)
14190   compr_pos = 0;
14191 
14192   if (search_station_name(&p_station,call,1))         // If we found the station in our list
14193   {
14194 
14195     if (debug_level & 1)
14196     {
14197       fprintf(stderr,"data_add: Found existing station record.\n");
14198     }
14199 
14200     move_station_time(p_station,p_time);        // update time, change position in time sorted list
14201     new_station = (char)FALSE;                  // we have seen this one before
14202 
14203     if (is_my_station(p_station))
14204     {
14205       station_is_mine++; // Station is  me
14206     }
14207     //fprintf(stderr,"checks ok\n");
14208   }
14209   else
14210   {
14211     //fprintf(stderr,"data_add()\n");
14212 
14213     if (debug_level & 1)
14214     {
14215       fprintf(stderr,"data_add: No existing station record found.\n");
14216     }
14217 
14218     p_station = add_new_station(p_station,p_time,call);     // create storage
14219     new_station = (char)TRUE;                       // for new station
14220   }
14221 
14222   if (p_station != NULL)
14223   {
14224 
14225     last_lat = p_station->coord_lat;                // remember last position
14226     last_lon = p_station->coord_lon;
14227     last_stn_sec = p_station->sec_heard;
14228     xastir_snprintf(last_alt,
14229                     sizeof(last_alt),
14230                     "%s",
14231                     p_station->altitude);
14232     xastir_snprintf(last_speed,
14233                     sizeof(last_speed),
14234                     "%s",
14235                     p_station->speed);
14236     xastir_snprintf(last_course,
14237                     sizeof(last_course),
14238                     "%s",
14239                     p_station->course);
14240     last_flag = p_station->flag;
14241 
14242     // Wipe out old data so that it doesn't hang around forever
14243     p_station->altitude[0] = '\0';
14244     p_station->speed[0] = '\0';
14245     p_station->course[0] = '\0';
14246 
14247     ok = 1;                         // succeed as default
14248 
14249 
14250     switch (type)
14251     {
14252 
14253       case (APRS_MICE):           // Mic-E format
14254       case (APRS_FIXED):          // '!'
14255       case (APRS_MSGCAP):         // '='
14256 
14257         if (!extract_position(p_station,&data,type))            // uncompressed lat/lon
14258         {
14259           compr_pos = 1;
14260           if (!extract_comp_position(p_station,&data,type))   // compressed lat/lon
14261           {
14262             ok = 0;
14263           }
14264           else
14265           {
14266             p_station->pos_amb = 0;  // No ambiguity in compressed posits
14267           }
14268         }
14269 
14270         if (ok)
14271         {
14272 
14273           // Create a timestamp from the current time
14274           xastir_snprintf(p_station->pos_time,
14275                           sizeof(p_station->pos_time),
14276                           "%s",
14277                           get_time(temp_data));
14278           (void)extract_storm(p_station,data,compr_pos);
14279           (void)extract_weather(p_station,data,compr_pos);    // look for weather data first
14280           process_data_extension(p_station,data,type);        // PHG, speed, etc.
14281           process_info_field(p_station,data,type);            // altitude
14282 
14283           if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
14284           {
14285             extract_multipoints(p_station, data, type, 1);
14286           }
14287 
14288           add_comment(p_station,data);
14289 
14290           p_station->record_type = NORMAL_APRS;
14291           if (type == APRS_MSGCAP)
14292           {
14293             p_station->flag |= ST_MSGCAP;  // set "message capable" flag
14294           }
14295           else
14296           {
14297             p_station->flag &= (~ST_MSGCAP);  // clear "message capable" flag
14298           }
14299 
14300           // Assign a non-default value for the error
14301           // ellipse?
14302           if (type == APRS_MICE || !compr_pos)
14303           {
14304             p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
14305             p_station->lat_precision = 60;
14306             p_station->lon_precision = 60;
14307           }
14308           else
14309           {
14310             p_station->error_ellipse_radius = 600; // Default of 6m
14311             p_station->lat_precision = 6;
14312             p_station->lon_precision = 6;
14313           }
14314 
14315         }
14316         break;
14317 
14318       /*
14319         case (APRS_DOWN):           // '/'
14320         ok = extract_time(p_station, data, type);               // we need a time
14321         if (ok) {
14322         if (!extract_position(p_station,&data,type)) {      // uncompressed lat/lon
14323         compr_pos = 1;
14324         if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
14325         ok = 0;
14326         else
14327         p_station->pos_amb = 0; // No ambiguity in compressed posits
14328         }
14329         }
14330 
14331         if (ok) {
14332 
14333         // Create a timestamp from the current time
14334         xastir_snprintf(p_station->pos_time,
14335         sizeof(p_station->pos_time),
14336         "%s",
14337         get_time(temp_data));
14338         process_data_extension(p_station,data,type);        // PHG, speed, etc.
14339         process_info_field(p_station,data,type);            // altitude
14340 
14341         if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
14342         extract_multipoints(p_station, data, type, 1);
14343 
14344         add_comment(p_station,data);
14345 
14346         p_station->record_type = DOWN_APRS;
14347         p_station->flag &= (~ST_MSGCAP);            // clear "message capable" flag
14348 
14349         // Assign a non-default value for the error
14350         // ellipse?
14351         if (!compr_pos) {
14352         p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
14353         p_station->lat_precision = 60;
14354         p_station->lon_precision = 60;
14355         }
14356         else {
14357         p_station->error_ellipse_radius = 600; // Default of 6m
14358         p_station->lat_precision = 6;
14359         p_station->lon_precision = 6;
14360         }
14361         }
14362         break;
14363       */
14364 
14365       case (APRS_DF):             // '@'
14366       case (APRS_MOBILE):         // '@'
14367 
14368         ok = extract_time(p_station, data, type);               // we need a time
14369         if (ok)
14370         {
14371           if (!extract_position(p_station,&data,type))        // uncompressed lat/lon
14372           {
14373             compr_pos = 1;
14374             if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
14375             {
14376               ok = 0;
14377             }
14378             else
14379             {
14380               p_station->pos_amb = 0;  // No ambiguity in compressed posits
14381             }
14382           }
14383         }
14384         if (ok)
14385         {
14386 
14387           process_data_extension(p_station,data,type);        // PHG, speed, etc.
14388           process_info_field(p_station,data,type);            // altitude
14389 
14390           if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
14391           {
14392             extract_multipoints(p_station, data, type, 1);
14393           }
14394 
14395           add_comment(p_station,data);
14396 
14397           if(type == APRS_MOBILE)
14398           {
14399             p_station->record_type = MOBILE_APRS;
14400           }
14401           else
14402           {
14403             p_station->record_type = DF_APRS;
14404           }
14405           //@ stations have messaging per spec
14406           p_station->flag |= (ST_MSGCAP);            // set "message capable" flag
14407 
14408           // Assign a non-default value for the error
14409           // ellipse?
14410           if (!compr_pos)
14411           {
14412             p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
14413             p_station->lat_precision = 60;
14414             p_station->lon_precision = 60;
14415           }
14416           else
14417           {
14418             p_station->error_ellipse_radius = 600; // Default of 6m
14419             p_station->lat_precision = 6;
14420             p_station->lon_precision = 6;
14421           }
14422         }
14423         break;
14424 
14425       case (APRS_GRID):
14426 
14427         ok = extract_position(p_station, &data, type);
14428         if (ok)
14429         {
14430 
14431           if (debug_level & 1)
14432           {
14433             fprintf(stderr,"data_add: Got grid data for %s\n", call);
14434           }
14435 
14436           process_info_field(p_station,data,type);            // altitude
14437 
14438           if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
14439           {
14440             extract_multipoints(p_station, data, type, 1);
14441           }
14442 
14443           add_comment(p_station,data);
14444 
14445           // Assign a non-default value for the error
14446           // ellipse?
14447           //                    p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
14448 
14449           // WE7U
14450           // This needs to change based on the number of grid letters/digits specified
14451           //                    p_station->lat_precision = 60;
14452           //                    p_station->lon_precision = 60;
14453         }
14454         else
14455         {
14456           if (debug_level & 1)
14457           {
14458             fprintf(stderr,"data_add: Bad grid data for %s : %s\n", call, data);
14459           }
14460         }
14461         break;
14462 
14463       case (STATION_CALL_DATA):
14464 
14465         p_station->record_type = NORMAL_APRS;
14466         found_pos = 0;
14467         break;
14468 
14469       case (APRS_STATUS):         // '>' Status Reports     [APRS Reference, chapter 16]
14470 
14471         (void)extract_time(p_station, data, type);              // we need a time
14472         // todo: could contain Maidenhead or beam heading+power
14473 
14474         if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
14475         {
14476           extract_multipoints(p_station, data, type, 1);
14477         }
14478 
14479         add_status(p_station,data);
14480 
14481         p_station->flag |= (ST_STATUS);                         // set "Status" flag
14482         p_station->record_type = NORMAL_APRS;                   // ???
14483         found_pos = 0;
14484         break;
14485 
14486       case (OTHER_DATA):          // Other Packets          [APRS Reference, chapter 19]
14487 
14488         // non-APRS beacons, treated as status reports until we get a real one
14489 
14490         if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
14491         {
14492           extract_multipoints(p_station, data, type, 1);
14493         }
14494 
14495         if ((p_station->flag & (~ST_STATUS)) == 0)              // only store if no status yet
14496         {
14497 
14498           add_status(p_station,data);
14499 
14500           p_station->record_type = NORMAL_APRS;               // ???
14501         }
14502         found_pos = 0;
14503         break;
14504 
14505       case (APRS_OBJECT):
14506         if (debug_level & 2048)
14507         {
14508           fprintf (stderr,"  Object: before any extractions at all, data is \"%s\"\n",data);
14509         }
14510 
14511         // If old match is a killed Object (owner doesn't
14512         // matter), new one is an active Object and owned by
14513         // us, remove the old record and create a new one
14514         // for storing this Object.  Do the same for Items
14515         // in the next section below.
14516         //
14517         // The easiest implementation might be to remove the
14518         // old record and then call this routine again with
14519         // the same parameters, which will cause a brand-new
14520         // record to be created.
14521         //
14522         // The new record we're processing is an active
14523         // object, as data_add() won't be called on a killed
14524         // object.
14525         //
14526         //                if ( is_my_call(origin,1)  // If new Object is owned by me (including SSID)
14527         if (new_origin_is_mine
14528             && !(p_station->flag & ST_ACTIVE)
14529             && (p_station->flag & ST_OBJECT) )    // Old record was a killed Object
14530         {
14531           station_del_ptr(p_station);  // Remove old killed Object
14532           // *completely*
14533           redo_list = (int)TRUE;
14534           return( data_add(type, call_sign, path, data, from, port, origin, third_party, 0, 1) );
14535         }
14536 
14537         ok = extract_time(p_station, data, type);               // we need a time
14538         if (ok)
14539         {
14540           if (!extract_position(p_station,&data,type))        // uncompressed lat/lon
14541           {
14542             compr_pos = 1;
14543             if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
14544             {
14545               ok = 0;
14546             }
14547             else
14548             {
14549               p_station->pos_amb = 0;  // No ambiguity in compressed posits
14550             }
14551           }
14552         }
14553         p_station->flag |= ST_OBJECT;                           // Set "Object" flag
14554         if (ok)
14555         {
14556 
14557           // If object was owned by me but another station
14558           // is transmitting it now, write entries into
14559           // the object.log file showing that we don't own
14560           // this object anymore.
14561           //                    if ( (is_my_call(p_station->origin,1))  // If station was owned by me (including SSID)
14562           //                            && (!is_my_call(origin,1)) ) {  // But isn't now
14563           if (is_my_object_item(p_station)    // If station was owned by me (include SSID)
14564               && !new_origin_is_mine)     // But isn't now
14565           {
14566 
14567             disown_object_item(call_sign, origin);
14568           }
14569 
14570           // If station is owned by me (including SSID)
14571           // but it's a new object/item
14572           //                    if ( (is_my_call(p_station->origin,1))
14573           if (new_origin_is_mine
14574               && (p_station->transmit_time_increment == 0) )
14575           {
14576             // This will get us transmitting this object
14577             // on the decaying algorithm schedule.
14578             // We've transmitted it once if we've just
14579             // gotten to this code.
14580             p_station->transmit_time_increment = OBJECT_CHECK_RATE;
14581             //fprintf(stderr,"data_add(): Setting transmit_time_increment to %d\n", OBJECT_CHECK_RATE);
14582           }
14583 
14584           // Create a timestamp from the current time
14585           xastir_snprintf(p_station->pos_time,
14586                           sizeof(p_station->pos_time),
14587                           "%s",
14588                           get_time(temp_data));
14589 
14590           xastir_snprintf(p_station->origin,
14591                           sizeof(p_station->origin),
14592                           "%s",
14593                           origin);                   // define it as object
14594           if (debug_level & 2048)
14595           {
14596             fprintf (stderr,"  Object: before any extractions, data is \"%s\"\n",data);
14597           }
14598           (void)extract_storm(p_station,data,compr_pos);
14599           (void)extract_weather(p_station,data,compr_pos);    // look for wx info
14600           process_data_extension(p_station,data,type);        // PHG, speed, etc.
14601           process_info_field(p_station,data,type);            // altitude
14602 
14603           if (debug_level & 2048)
14604           {
14605             fprintf (stderr,"  Object: calling extract_multipoints with data \"%s\"\n",data);
14606           }
14607           if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
14608           {
14609             extract_multipoints(p_station, data, type, 0);
14610           }
14611 
14612           add_comment(p_station,data);
14613 
14614           // the last char always was missing...
14615           //p_station->comments[ strlen(p_station->comments) - 1 ] = '\0';  // Wipe out '\n'
14616           // moved that to decode_ax25_line
14617           // and don't added a '\n' in interface.c
14618           p_station->record_type = NORMAL_APRS;
14619           p_station->flag &= (~ST_MSGCAP);            // clear "message capable" flag
14620 
14621           // Assign a non-default value for the error
14622           // ellipse?
14623           if (!compr_pos)
14624           {
14625             p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
14626             p_station->lat_precision = 60;
14627             p_station->lon_precision = 60;
14628           }
14629           else
14630           {
14631             p_station->error_ellipse_radius = 600; // Default of 6m
14632             p_station->lat_precision = 6;
14633             p_station->lon_precision = 6;
14634           }
14635         }
14636         break;
14637 
14638       case (APRS_ITEM):
14639 
14640         // If old match is a killed Item (owner doesn't
14641         // matter), new one is an active Item and owned by
14642         // us, remove the old record and create a new one
14643         // for storing this Item.  Do the same for Objects
14644         // in the previous section above.
14645         //
14646         // The easiest implementation might be to remove the
14647         // old record and then call this routine again with
14648         // the same parameters, which will cause a brand-new
14649         // record to be created.
14650         //
14651         // The new record we're processing is an active
14652         // Item, as data_add() won't be called on a killed
14653         // Item.
14654         //
14655         //                if ( is_my_call(origin,1)  // If new Item is owned by me (including SSID)
14656         if (new_origin_is_mine
14657             && !(p_station->flag & ST_ACTIVE)
14658             && (p_station->flag & ST_ITEM) )    // Old record was a killed Item
14659         {
14660 
14661           station_del_ptr(p_station);  // Remove old killed Item
14662           // *completely*
14663           redo_list = (int)TRUE;
14664           return( data_add(type, call_sign, path, data, from, port, origin, third_party, 0, 1) );
14665         }
14666 
14667         if (!extract_position(p_station,&data,type))            // uncompressed lat/lon
14668         {
14669           compr_pos = 1;
14670           if (!extract_comp_position(p_station,&data,type))   // compressed lat/lon
14671           {
14672             ok = 0;
14673           }
14674           else
14675           {
14676             p_station->pos_amb = 0;  // No ambiguity in compressed posits
14677           }
14678         }
14679         p_station->flag |= ST_ITEM;                             // Set "Item" flag
14680         if (ok)
14681         {
14682 
14683           // If item was owned by me but another station
14684           // is transmitting it now, write entries into
14685           // the object.log file showing that we don't own
14686           // this item anymore.
14687           //                    if ( (is_my_call(p_station->origin,1))  // If station was owned by me (including SSID)
14688           //                            && (!is_my_call(origin,1)) ) {  // But isn't now
14689           if (is_my_object_item(p_station)
14690               && !new_origin_is_mine)    // But isn't now
14691           {
14692 
14693             disown_object_item(call_sign,origin);
14694           }
14695 
14696           // If station is owned by me (including SSID)
14697           // but it's a new object/item
14698           //                    if ( (is_my_call(p_station->origin,1))
14699           if (is_my_object_item(p_station)
14700               && (p_station->transmit_time_increment == 0) )
14701           {
14702             // This will get us transmitting this object
14703             // on the decaying algorithm schedule.
14704             // We've transmitted it once if we've just
14705             // gotten to this code.
14706             p_station->transmit_time_increment = OBJECT_CHECK_RATE;
14707             //fprintf(stderr,"data_add(): Setting transmit_time_increment to %d\n", OBJECT_CHECK_RATE);
14708           }
14709 
14710           // Create a timestamp from the current time
14711           xastir_snprintf(p_station->pos_time,
14712                           sizeof(p_station->pos_time),
14713                           "%s",
14714                           get_time(temp_data));
14715           xastir_snprintf(p_station->origin,
14716                           sizeof(p_station->origin),
14717                           "%s",
14718                           origin);                   // define it as item
14719           (void)extract_storm(p_station,data,compr_pos);
14720           (void)extract_weather(p_station,data,compr_pos);    // look for wx info
14721           process_data_extension(p_station,data,type);        // PHG, speed, etc.
14722           process_info_field(p_station,data,type);            // altitude
14723 
14724           if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
14725           {
14726             extract_multipoints(p_station, data, type, 0);
14727           }
14728 
14729           add_comment(p_station,data);
14730 
14731           // the last char always was missing...
14732           //p_station->comments[ strlen(p_station->comments) - 1 ] = '\0';  // Wipe out '\n'
14733           // moved that to decode_ax25_line
14734           // and don't added a '\n' in interface.c
14735           p_station->record_type = NORMAL_APRS;
14736           p_station->flag &= (~ST_MSGCAP);            // clear "message capable" flag
14737 
14738           // Assign a non-default value for the error
14739           // ellipse?
14740           if (!compr_pos)
14741           {
14742             p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
14743             p_station->lat_precision = 60;
14744             p_station->lon_precision = 60;
14745           }
14746           else
14747           {
14748             p_station->error_ellipse_radius = 600; // Default of 6m
14749             p_station->lat_precision = 6;
14750             p_station->lon_precision = 6;
14751           }
14752         }
14753         break;
14754 
14755       case (APRS_WX1):    // weather in '@' or '/' packet
14756 
14757         ok = extract_time(p_station, data, type);               // we need a time
14758         if (ok)
14759         {
14760           if (!extract_position(p_station,&data,type))        // uncompressed lat/lon
14761           {
14762             compr_pos = 1;
14763             if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
14764             {
14765               ok = 0;
14766             }
14767             else
14768             {
14769               p_station->pos_amb = 0;  // No ambiguity in compressed posits
14770             }
14771           }
14772         }
14773         if (ok)
14774         {
14775 
14776           (void)extract_storm(p_station,data,compr_pos);
14777           (void)extract_weather(p_station,data,compr_pos);
14778           p_station->record_type = (char)APRS_WX1;
14779 
14780           process_info_field(p_station,data,type);            // altitude
14781 
14782           if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
14783           {
14784             extract_multipoints(p_station, data, type, 1);
14785           }
14786 
14787           add_comment(p_station,data);
14788 
14789           // Assign a non-default value for the error
14790           // ellipse?
14791           if (!compr_pos)
14792           {
14793             p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
14794             p_station->lat_precision = 60;
14795             p_station->lon_precision = 60;
14796           }
14797           else
14798           {
14799             p_station->error_ellipse_radius = 600; // Default of 6m
14800             p_station->lat_precision = 6;
14801             p_station->lon_precision = 6;
14802           }
14803         }
14804         break;
14805 
14806       case (APRS_WX2):            // '_'
14807 
14808         ok = extract_time(p_station, data, type);               // we need a time
14809         if (ok)
14810         {
14811           (void)extract_storm(p_station,data,compr_pos);
14812           (void)extract_weather(p_station,data,0);            // look for weather data first
14813           p_station->record_type = (char)APRS_WX2;
14814           found_pos = 0;
14815 
14816           process_info_field(p_station,data,type);            // altitude
14817 
14818           if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
14819           {
14820             extract_multipoints(p_station, data, type, 1);
14821           }
14822         }
14823         break;
14824 
14825       case (APRS_WX4):            // '#'          Peet Bros U-II (km/h)
14826       case (APRS_WX6):            // '*'          Peet Bros U-II (mph)
14827       case (APRS_WX3):            // '!'          Peet Bros Ultimeter 2000 (data logging mode)
14828       case (APRS_WX5):            // '$ULTW'      Peet Bros Ultimeter 2000 (packet mode)
14829 
14830         if (get_weather_record(p_station))      // get existing or create new weather record
14831         {
14832           weather = p_station->weather_data;
14833           if (type == APRS_WX3)   // Peet Bros Ultimeter 2000 data logging mode
14834           {
14835             decode_U2000_L(1,(unsigned char *)data,weather);
14836           }
14837           else if (type == APRS_WX5) // Peet Bros Ultimeter 2000 packet mode
14838           {
14839             decode_U2000_P(1,(unsigned char *)data,weather);
14840           }
14841           else    // Peet Bros Ultimeter-II
14842           {
14843             decode_Peet_Bros(1,(unsigned char *)data,weather,type);
14844           }
14845           p_station->record_type = (char)type;
14846           // Create a timestamp from the current time
14847           xastir_snprintf(weather->wx_time,
14848                           sizeof(weather->wx_time),
14849                           "%s",
14850                           get_time(temp_data));
14851           weather->wx_sec_time = sec_now();
14852           found_pos = 0;
14853         }
14854         break;
14855 
14856 
14857       // GPRMC, digits after decimal point
14858       // ---------------------------------
14859       // 2  = 25.5 meter error ellipse
14860       // 3  =  6.0 meter error ellipse
14861       // 4+ =  6.0 meter error ellipse
14862 
14863 
14864       case (GPS_RMC):             // $GPRMC
14865 
14866         // WE7U
14867         // Change this function to return HDOP and the number of characters
14868         // after the decimal point.
14869         ok = extract_RMC(p_station,data,call_sign,path,&num_digits);
14870 
14871         if (ok)
14872         {
14873           // Assign a non-default value for the error
14874           // ellipse?
14875           //
14876           // WE7U
14877           // Degrade based on the precision provided in the sentence.  If only
14878           // 2 digits after decimal point, give it 2550 as a radius (25.5m).
14879           // Best (smallest) circle should be 600 as we have no augmentation
14880           // flag to check here for anything better.
14881           //
14882           switch (num_digits)
14883           {
14884 
14885             case 0:
14886               p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
14887               p_station->lat_precision = 6000;
14888               p_station->lon_precision = 6000;
14889               break;
14890 
14891             case 1:
14892               p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
14893               p_station->lat_precision = 600;
14894               p_station->lon_precision = 600;
14895               break;
14896 
14897             case 2:
14898               p_station->error_ellipse_radius = 600; // Default of 6m
14899               p_station->lat_precision = 60;
14900               p_station->lon_precision = 60;
14901               break;
14902 
14903             case 3:
14904               p_station->error_ellipse_radius = 600; // Default of 6m
14905               p_station->lat_precision = 6;
14906               p_station->lon_precision = 6;
14907               break;
14908 
14909             case 4:
14910             case 5:
14911             case 6:
14912             case 7:
14913               p_station->error_ellipse_radius = 600; // Default of 6m
14914               p_station->lat_precision = 0;
14915               p_station->lon_precision = 0;
14916               break;
14917 
14918             default:
14919               p_station->error_ellipse_radius = 600; // Default of 6m
14920               p_station->lat_precision = 60;
14921               p_station->lon_precision = 60;
14922               break;
14923           }
14924         }
14925         break;
14926 
14927 
14928       // GPGGA, digits after decimal point, w/o augmentation
14929       // ---------------------------------------------------
14930       // 2   = 25.5 meter error ellipse
14931       // 3   =  6.0 meter error ellipse unless HDOP>4, then 10.0 meters
14932       // 4+  =  6.0 meter error ellipse unless HDOP>4, then 10.0 meters
14933       //
14934       //
14935       // GPGGA, digits after decimal point, w/augmentation
14936       // --------------------------------------------------
14937       // 2   = 25.5 meter error ellipse
14938       // 3   =  2.5 meter error ellipse unless HDOP>4, then 10.0 meters
14939       // 4+  =  0.6 meter error ellipse unless HDOP>4, then 10.0 meters
14940 
14941 
14942       case (GPS_GGA):             // $GPGGA
14943 
14944         // WE7U
14945         // Change this function to return HDOP and the number of characters
14946         // after the decimal point.
14947         ok = extract_GGA(p_station,data,call_sign,path,&num_digits);
14948 
14949         if (ok)
14950         {
14951           // Assign a non-default value for the error
14952           // ellipse?
14953           //
14954           // WE7U
14955           // Degrade based on the precision provided in the sentence.  If only
14956           // 2 digits after decimal point, give it 2550 as a radius (25.5m).
14957           // 3 digits: 6m w/o augmentation unless HDOP >4 = 10m, 2.5m w/augmentation.
14958           // 4+ digits: 6m w/o augmentation unless HDOP >4 = 10m, 0.6m w/augmentation.
14959           //
14960           switch (num_digits)
14961           {
14962 
14963             case 0:
14964               p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
14965               p_station->lat_precision = 6000;
14966               p_station->lon_precision = 6000;
14967               break;
14968 
14969             case 1:
14970               p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
14971               p_station->lat_precision = 600;
14972               p_station->lon_precision = 600;
14973               break;
14974 
14975             case 2:
14976               p_station->error_ellipse_radius = 600; // Default of 6m
14977               p_station->lat_precision = 60;
14978               p_station->lon_precision = 60;
14979               break;
14980 
14981             case 3:
14982               p_station->error_ellipse_radius = 600; // Default of 6m
14983               p_station->lat_precision = 6;
14984               p_station->lon_precision = 6;
14985               break;
14986 
14987             case 4:
14988             case 5:
14989             case 6:
14990             case 7:
14991               p_station->error_ellipse_radius = 600; // Default of 6m
14992               p_station->lat_precision = 0;
14993               p_station->lon_precision = 0;
14994               break;
14995 
14996             default:
14997               p_station->error_ellipse_radius = 600; // Default of 6m
14998               p_station->lat_precision = 60;
14999               p_station->lon_precision = 60;
15000               break;
15001           }
15002         }
15003         break;
15004 
15005 
15006       // GPGLL, digits after decimal point
15007       // ---------------------------------
15008       // 2  = 25.5 meter error ellipse
15009       // 3  =  6.0 meter error ellipse
15010       // 4+ =  6.0 meter error ellipse
15011 
15012 
15013       case (GPS_GLL):             // $GPGLL
15014         ok = extract_GLL(p_station,data,call_sign,path,&num_digits);
15015 
15016         if (ok)
15017         {
15018           // Assign a non-default value for the error
15019           // ellipse?
15020           //
15021           // WE7U
15022           // Degrade based on the precision provided in the sentence.  If only
15023           // 2 digits after decimal point, give it 2550 as a radius, otherwise
15024           // give it 600.
15025           //
15026           switch (num_digits)
15027           {
15028 
15029             case 0:
15030               p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
15031               p_station->lat_precision = 6000;
15032               p_station->lon_precision = 6000;
15033               break;
15034 
15035             case 1:
15036               p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
15037               p_station->lat_precision = 600;
15038               p_station->lon_precision = 600;
15039               break;
15040 
15041             case 2:
15042               p_station->error_ellipse_radius = 600; // Default of 6m
15043               p_station->lat_precision = 60;
15044               p_station->lon_precision = 60;
15045               break;
15046 
15047             case 3:
15048               p_station->error_ellipse_radius = 600; // Default of 6m
15049               p_station->lat_precision = 6;
15050               p_station->lon_precision = 6;
15051               break;
15052 
15053             case 4:
15054             case 5:
15055             case 6:
15056             case 7:
15057               p_station->error_ellipse_radius = 600; // Default of 6m
15058               p_station->lat_precision = 0;
15059               p_station->lon_precision = 0;
15060               break;
15061 
15062             default:
15063               p_station->error_ellipse_radius = 600; // Default of 6m
15064               p_station->lat_precision = 60;
15065               p_station->lon_precision = 60;
15066               break;
15067           }
15068 
15069         }
15070         break;
15071 
15072       default:
15073 
15074         fprintf(stderr,"ERROR: UNKNOWN TYPE in data_add\n");
15075         ok = 0;
15076         break;
15077     }
15078 
15079     // Left this one in, just in case.  Perhaps somebody might
15080     // attach a multipoint string onto the end of a packet we
15081     // might not expect.  For this case we need to check whether
15082     // we have multipoints first, as we don't want to erase the
15083     // work we might have done with a previous call to
15084     // extract_multipoints().
15085     if (ok && (p_station->coord_lat > 0)
15086         && (p_station->coord_lon > 0)
15087         && (p_station->num_multipoints == 0) )    // No multipoints found yet
15088     {
15089 
15090       extract_multipoints(p_station, data, type, 0);
15091     }
15092   }
15093 
15094   if (!ok)    // non-APRS beacon, treat it as Other Packet   [APRS Reference, chapter 19]
15095   {
15096 
15097     if (debug_level & 1)
15098     {
15099       char filtered_data[MAX_LINE_SIZE + 1];
15100 
15101       xastir_snprintf(filtered_data,
15102                       sizeof(filtered_data),
15103                       "%s",
15104                       data-1);
15105       makePrintable(filtered_data);
15106       fprintf(stderr,"store non-APRS data as status: %s: |%s|\n",call,filtered_data);
15107     }
15108 
15109     // GPRMC etc. without a position is here too, but it should not be stored as status!
15110 
15111     // store it as status report until we get a real one
15112     if ((p_station->flag & (~ST_STATUS)) == 0)           // only store it if no status yet
15113     {
15114 
15115       add_status(p_station,data-1);
15116 
15117       p_station->record_type = NORMAL_APRS;               // ???
15118 
15119     }
15120 
15121     ok = 1;
15122     found_pos = 0;
15123   }
15124 
15125   curr_sec = sec_now();
15126   if (ok)
15127   {
15128 
15129     // data packet is valid
15130     // announce own echo, we soon discard that packet...
15131     //        if (!new_station && is_my_call(p_station->call_sign,1) // Check SSID as well
15132     if (!new_station
15133         && is_my_station(p_station) // Check SSID as well
15134         && strchr(path,'*') != NULL)
15135     {
15136 
15137       upd_echo(path);   // store digi that echoes my signal...
15138       statusline(langcode("BBARSTA033"),0);   // Echo from digipeater
15139 
15140     }
15141     // check if data is just a secondary echo from another digi
15142     if ((last_flag & ST_VIATNC) == 0
15143         || (curr_sec - last_stn_sec) > 15
15144         || p_station->coord_lon != last_lon
15145         || p_station->coord_lat != last_lat)
15146 
15147     {
15148       store = 1;  // don't store secondary echos
15149     }
15150   }
15151 
15152   if (!ok && new_station)
15153   {
15154     delete_station_memory(p_station);  // remove unused record
15155   }
15156 
15157   if (store)
15158   {
15159 
15160     // we now have valid data to store into database
15161     // make time index unique by adding a serial number
15162 
15163     if (station_is_mine)
15164     {
15165       // This station is me.  Set the
15166       // flag which shows that we own/control this
15167       // station.  We use this flag later in lieu
15168       // of the is_my_call() function in order to speed things
15169       // up.
15170       //
15171       p_station->flag |= ST_MYSTATION;
15172     }
15173 
15174     // Check whether it's a locally-owned object/item
15175     if ( object_is_mine
15176          || (   new_origin_is_mine
15177                 && (p_station->flag & ST_ACTIVE)
15178                 && (p_station->flag & ST_OBJECT) ) )
15179     {
15180 
15181       p_station->flag |= ST_MYOBJITEM;
15182 
15183       // Do nothing else.  We don't want to update the
15184       // last-heard time so that it'll expire from the queue
15185       // normally, unless it is a new object/item.
15186       //
15187       if (new_station)
15188       {
15189         p_station->sec_heard = curr_sec;
15190       }
15191 
15192       // We need an exception later in this function for the
15193       // case where we've moved an object/item (by how much?).
15194       // We need to update the time in this case so that it'll
15195       // expire later (in fact it could already be expired
15196       // when we move it).  We should be able to move expired
15197       // objects/items to make them active again.  Perhaps
15198       // some other method as well?.
15199     }
15200     else
15201     {
15202       // Reset the "my object" flag
15203       p_station->flag &= ~ST_MYOBJITEM;
15204 
15205       p_station->sec_heard = curr_sec;    // Give it a new timestamp
15206     }
15207 
15208     if (curr_sec != last_sec)       // todo: if old time calculate time_sn from database
15209     {
15210       last_sec = curr_sec;
15211       next_time_sn = 0;           // restart time serial number
15212     }
15213 
15214     p_station->time_sn = next_time_sn++;            // todo: warning if serial number too high
15215     if (from == DATA_VIA_TNC)                       // heard via TNC
15216     {
15217       if (!third_party)   // Not a third-party packet
15218       {
15219         p_station->flag |= ST_VIATNC;               // set "via TNC" flag
15220         p_station->heard_via_tnc_last_time = curr_sec;
15221         p_station->heard_via_tnc_port = port;
15222       }
15223       else    // Third-party packet
15224       {
15225         // Leave the previous setting of "flag" alone.
15226         // Specifically do NOT set the ST_VIATNC flag if it
15227         // was a third-party packet.
15228       }
15229     }
15230     else    // heard other than TNC
15231     {
15232       if (new_station)    // new station
15233       {
15234         p_station->flag &= (~ST_VIATNC);  // clear "via TNC" flag
15235         //fprintf(stderr,"New_station: Cleared ST_VIATNC flag: %s\n", p_station->call_sign);
15236         p_station->heard_via_tnc_last_time = 0l;
15237       }
15238     }
15239     p_station->last_port_heard = port;
15240     p_station->data_via = from;
15241     // Create a timestamp from the current time
15242     xastir_snprintf(p_station->packet_time,
15243                     sizeof(p_station->packet_time),
15244                     "%s",
15245                     get_time(temp_data)); // get_time returns value in temp_data
15246 
15247     p_station->flag |= ST_ACTIVE;
15248     if (third_party)
15249     {
15250       p_station->flag |= ST_3RD_PT;  // set "third party" flag
15251     }
15252     else
15253     {
15254       p_station->flag &= (~ST_3RD_PT);  // clear "third party" flag
15255     }
15256     if (origin != NULL && strcmp(origin,"INET") == 0)  // special treatment for inet names
15257       xastir_snprintf(p_station->origin,
15258                       sizeof(p_station->origin),
15259                       "%s",
15260                       origin);           // to keep them separated from calls
15261     if (origin != NULL && strcmp(origin,"INET-NWS") == 0)  // special treatment for NWS
15262       xastir_snprintf(p_station->origin,
15263                       sizeof(p_station->origin),
15264                       "%s",
15265                       origin);           // to keep them separated from calls
15266 
15267     if (origin != NULL && strcmp(origin,"INET-BOM") == 0)  // special treatment for BOM (AU)
15268       xastir_snprintf(p_station->origin,
15269                       sizeof(p_station->origin),
15270                       "%s",
15271                       origin);           // to keep them separated from calls
15272 
15273     if (origin == NULL || origin[0] == '\0')        // normal call
15274     {
15275       p_station->origin[0] = '\0';  // undefine possible former object with same name
15276     }
15277 
15278 
15279     //--------------------------------------------------------------------
15280 
15281     // KC2ELS
15282     // Okay, here are the standards for ST_DIRECT:
15283     // 1.  The packet must have been received via TNC.
15284     // 2.  The packet must not have any * flags.
15285     // 3.  If present, the first WIDEn-N (or TRACEn-N) must have n=N.
15286     // A station retains the ST_DIRECT setting.  If
15287     // "st_direct_timeout" seconds have passed since we set
15288     // that bit then APRSD queries and displays based on the
15289     // ST_DIRECT bit will skip that station.
15290 
15291     // In order to make this scheme work for stations that straddle both
15292     // RF and INET, we need to make sure that node_path_ptr doesn't get
15293     // overwritten with an INET path if there's an RF path already in
15294     // there and it has been less than st_direct_timeout seconds since
15295     // the station was last heard on RF.
15296 
15297     if ((from == DATA_VIA_TNC)             // Heard via TNC
15298         && !third_party                // Not a 3RD-Party packet
15299         && path != NULL                // Path is not NULL
15300         && strchr(path,'*') == NULL)   // No asterisk found
15301     {
15302 
15303       // Look for WIDE or TRACE
15304       if ((((p = strstr(path,"WIDE")) != NULL)
15305            && (p+=4)) ||
15306           (((p = strstr(path,"TRACE")) != NULL)
15307            && (p+=5)))
15308       {
15309 
15310         // Look for n=N on WIDEn-N/TRACEn-N digi field
15311         if ((*p != '\0') && isdigit((int)*p))
15312         {
15313           if ((*(p+1) != '\0') && (*(p+1) == '-'))
15314           {
15315             if ((*(p+2) != '\0') && isdigit((int)*(p+2)))
15316             {
15317               if (*(p) == *(p+2))
15318               {
15319                 direct = 1;
15320               }
15321               else
15322               {
15323                 direct = 0;
15324               }
15325             }
15326             else
15327             {
15328               direct = 0;
15329             }
15330           }
15331           else
15332           {
15333             direct = 0;
15334           }
15335         }
15336         else
15337         {
15338           direct = 1;
15339         }
15340       }
15341       else
15342       {
15343         direct = 1;
15344       }
15345     }
15346     else
15347     {
15348       direct = 0;
15349     }
15350 
15351     if (direct == 1)
15352     {
15353       // This packet was heard direct.  Set the ST_DIRECT bit
15354       // and save the timestamp away.
15355       if (debug_level & 1)
15356       {
15357         fprintf(stderr,"Setting ST_DIRECT for station %s\n",
15358                 p_station->call_sign);
15359       }
15360       p_station->direct_heard = curr_sec;
15361       p_station->flag |= (ST_DIRECT);
15362     }
15363     else
15364     {
15365       // This packet was NOT heard direct.  Check whether we
15366       // need to expire the ST_DIRECT bit.  A lot of fixed
15367       // stations transmit every 30 minutes.  One hour gives
15368       // us time to receive a direct packet from them among
15369       // all the digipeated packets.
15370 
15371       if ((p_station->flag & ST_DIRECT) != 0 &&
15372           curr_sec > (p_station->direct_heard + st_direct_timeout))
15373       {
15374         if (debug_level & 1)
15375           fprintf(stderr,"Clearing ST_DIRECT for station %s\n",
15376                   p_station->call_sign);
15377         p_station->flag &= (~ST_DIRECT);
15378       }
15379     }
15380 
15381     // If heard on TNC, overwrite node_path_ptr if any of these
15382     // conditions are met:
15383     //     *) direct == 1 (packet was heard direct)
15384     //     *) ST_DIRECT flag == 0 (packet hasn't been heard
15385     //     direct recently)
15386     //     *) ST_DIRECT is set, st_direct_timeout has expired
15387     //     (packet hasn't been heard direct recently)
15388     //
15389     // These rules will allow us to keep directly heard paths
15390     // saved for at least an hour (st_direct_timeout), and not
15391     // get overwritten with digipeated paths during that time.
15392     //
15393     if ((from == DATA_VIA_TNC)  // Heard via TNC
15394         && !third_party     // Not a 3RD-Party packet
15395         && path != NULL)    // Path is not NULL
15396     {
15397 
15398       // Heard on TNC interface and not third party.  Check
15399       // the other conditions listed in the comments above to
15400       // decide whether we should overwrite the node_path_ptr
15401       // variable.
15402       //
15403       if ( direct   // This packet was heard direct
15404            || (p_station->flag & ST_DIRECT) == 0  // Not heard direct lately
15405            || ( (p_station->flag & ST_DIRECT) != 0 // Not heard direct lately
15406                 && (curr_sec > (p_station->direct_heard+st_direct_timeout) ) ) )
15407       {
15408 
15409         // Free any old path we might have
15410         if (p_station->node_path_ptr != NULL)
15411         {
15412           free(p_station->node_path_ptr);
15413         }
15414         // Malloc and store the new path
15415         p_station->node_path_ptr = (char *)malloc(strlen(path) + 1);
15416         CHECKMALLOC(p_station->node_path_ptr);
15417 
15418         substr(p_station->node_path_ptr,path,strlen(path));
15419       }
15420     }
15421 
15422     // If a 3rd-party packet heard on TNC, overwrite
15423     // node_path_ptr only if heard_via_tnc_last_time is older
15424     // than one hour (zero counts as well!), plus clear the
15425     // ST_DIRECT and ST_VIATNC bits in this case.  This makes us
15426     // keep the RF path around for at least one hour after the
15427     // station is heard.
15428     //
15429     else if ((from == DATA_VIA_TNC)  // Heard via TNC
15430              && third_party      // It's a 3RD-Party packet
15431              && path != NULL)    // Path is not NULL
15432     {
15433 
15434       // 3rd-party packet heard on TNC interface.  Check if
15435       // heard_via_tnc_last_time is older than an hour.  If
15436       // so, overwrite the path and clear a few bits to show
15437       // that it has timed out on RF and we're now receiving
15438       // that station from an igate.
15439       //
15440       if (curr_sec > (p_station->heard_via_tnc_last_time + 60*60))
15441       {
15442 
15443         // Yep, more than one hour old or is a zero,
15444         // overwrite the node_path_ptr variable with the new
15445         // one.  We're only hearing this station on INET
15446         // now.
15447 
15448         // Free any old path we might have
15449         if (p_station->node_path_ptr != NULL)
15450         {
15451           free(p_station->node_path_ptr);
15452         }
15453         // Malloc and store the new path
15454         p_station->node_path_ptr = (char *)malloc(strlen(path) + 1);
15455         CHECKMALLOC(p_station->node_path_ptr);
15456 
15457         substr(p_station->node_path_ptr,path,strlen(path));
15458 
15459         // Clear the ST_VIATNC bit
15460         p_station->flag &= ~ST_VIATNC;
15461       }
15462 
15463       // If direct_heard is over an hour old, clear the
15464       // ST_DIRECT flag.  We're only hearing this station on
15465       // INET now.
15466       //
15467       if (curr_sec > (p_station->direct_heard + st_direct_timeout))
15468       {
15469 
15470         // Yep, more than one hour old or is a zero, clear
15471         // the ST_DIRECT flag.
15472         p_station->flag &= ~ST_DIRECT;
15473       }
15474     }
15475 
15476     // If heard on INET then overwrite node_path_ptr only if
15477     // heard_via_tnc_last_time is older than one hour (zero
15478     // counts as well!), plus clear the ST_DIRECT and ST_VIATNC
15479     // bits in this case.  This makes us keep the RF path around
15480     // for at least one hour after the station is heard.
15481     //
15482     else if (from != DATA_VIA_TNC  // From an INET interface
15483              && !third_party        // Not a 3RD-Party packet
15484              && path != NULL)       // Path is not NULL
15485     {
15486 
15487       // Heard on INET interface.  Check if
15488       // heard_via_tnc_last_time is older than an hour.  If
15489       // so, overwrite the path and clear a few bits to show
15490       // that it has timed out on RF and we're now receiving
15491       // that station from the INET feeds.
15492       //
15493       if (curr_sec > (p_station->heard_via_tnc_last_time + 60*60))
15494       {
15495 
15496         // Yep, more than one hour old or is a zero,
15497         // overwrite the node_path_ptr variable with the new
15498         // one.  We're only hearing this station on INET
15499         // now.
15500 
15501         // Free any old path we might have
15502         if (p_station->node_path_ptr != NULL)
15503         {
15504           free(p_station->node_path_ptr);
15505         }
15506         // Malloc and store the new path
15507         p_station->node_path_ptr = (char *)malloc(strlen(path) + 1);
15508         CHECKMALLOC(p_station->node_path_ptr);
15509 
15510         substr(p_station->node_path_ptr,path,strlen(path));
15511 
15512         // Clear the ST_VIATNC bit
15513         p_station->flag &= ~ST_VIATNC;
15514         /*
15515           fprintf(stderr,
15516           "\ntype:%d call:%s path:%s data:%s from:%c port:%d origin:%s 3rd:%d\n",
15517           type,
15518           call_sign,
15519           path,
15520           data,
15521           from,
15522           port,
15523           origin,
15524           third_party);
15525           fprintf(stderr,"Cleared ST_VIATNC flag (2): %s\n", p_station->call_sign);
15526         */
15527       }
15528 
15529       // If direct_heard is over an hour old, clear the
15530       // ST_DIRECT flag.  We're only hearing this station on
15531       // INET now.
15532       //
15533       if (curr_sec > (p_station->direct_heard + st_direct_timeout))
15534       {
15535 
15536         // Yep, more than one hour old or is a zero, clear
15537         // the ST_DIRECT flag.
15538         p_station->flag &= ~ST_DIRECT;
15539       }
15540     }
15541 
15542 
15543     //---------------------------------------------------------------------
15544 
15545     p_station->num_packets += 1;
15546     redo_list = (int)TRUE;          // we may need to update the lists
15547 
15548     if (found_pos)          // if station has a position with the data
15549     {
15550       if (position_on_extd_screen(p_station->coord_lat,p_station->coord_lon))
15551       {
15552         p_station->flag |= (ST_INVIEW);   // set   "In View" flag
15553         if (debug_level & 256)
15554         {
15555           fprintf(stderr,"Setting ST_INVIEW flag\n");
15556         }
15557       }
15558       else
15559       {
15560         p_station->flag &= (~ST_INVIEW);  // clear "In View" flag
15561         if (debug_level & 256)
15562         {
15563           fprintf(stderr,"Clearing ST_INVIEW flag\n");
15564         }
15565       }
15566     }
15567 
15568     screen_update = 0;
15569     if (new_station)
15570     {
15571       if (debug_level & 256)
15572       {
15573         fprintf(stderr,"New Station %s\n", p_station->call_sign);
15574       }
15575       if (strlen(p_station->speed) > 0 && atof(p_station->speed) > 0)
15576       {
15577         p_station->flag |= (ST_MOVING); // it has a speed, so it's moving
15578         moving = 1;
15579       }
15580       if (position_on_screen(p_station->coord_lat,p_station->coord_lon))
15581       {
15582 
15583         if (p_station->coord_lat != 0 && p_station->coord_lon != 0)     // discard undef positions from screen
15584         {
15585           if (!altnet || is_altnet(p_station) )
15586           {
15587             display_station(da,p_station,1);
15588             screen_update = 1;  // ???
15589           }
15590         }
15591       }
15592     }
15593     else          // we had seen this station before...
15594     {
15595       if (debug_level & 256)
15596       {
15597         fprintf(stderr,"New Data for %s %ld %ld\n", p_station->call_sign,
15598                 p_station->coord_lat, p_station->coord_lon);
15599       }
15600       if (found_pos && position_defined(p_station->coord_lat,p_station->coord_lon,1))   // ignore undefined and 0N/0E
15601       {
15602         if (debug_level & 256)
15603         {
15604           fprintf(stderr,"  Valid position for %s\n",
15605                   p_station->call_sign);
15606         }
15607         if (p_station->newest_trackpoint != NULL)
15608         {
15609           if (debug_level & 256)
15610           {
15611             fprintf(stderr,"Station has a trail: %s\n",
15612                     p_station->call_sign);
15613           }
15614           moving = 1;                         // it's moving if it has a trail
15615         }
15616         else
15617         {
15618           if (strlen(p_station->speed) > 0 && atof(p_station->speed) > 0)
15619           {
15620             if (debug_level & 256)
15621             {
15622               fprintf(stderr,"Speed detected on %s\n",
15623                       p_station->call_sign);
15624             }
15625             moving = 1;                     // declare it moving, if it has a speed
15626           }
15627           else
15628           {
15629             if (debug_level & 256)
15630             {
15631               fprintf(stderr,"Position defined: %d, Changed: %s\n",
15632                       position_defined(last_lat, last_lon, 1),
15633                       (p_station->coord_lat != last_lat ||
15634                        p_station->coord_lon != last_lon) ?
15635                       "Yes" : "No");
15636             }
15637 
15638             // Here's where we detect movement
15639             if (position_defined(last_lat,last_lon,1)
15640                 && (p_station->coord_lat != last_lat || p_station->coord_lon != last_lon))
15641             {
15642               if (debug_level & 256)
15643               {
15644                 fprintf(stderr,"Position Change detected on %s\n",
15645                         p_station->call_sign);
15646               }
15647               moving = 1;                 // it's moving if it has changed the position
15648             }
15649             else
15650             {
15651               if (debug_level & 256)
15652               {
15653                 fprintf(stderr,"Station %s still appears stationary.\n",
15654                         p_station->call_sign);
15655                 fprintf(stderr," %s stationary at %ld %ld (%ld %ld)\n",
15656                         p_station->call_sign,
15657                         p_station->coord_lat, p_station->coord_lon,
15658                         last_lat,             last_lon);
15659               }
15660               moving = 0;
15661             }
15662           }
15663         }
15664         changed_pos = 0;
15665         if (moving == 1)
15666         {
15667           p_station->flag |= (ST_MOVING);
15668           // we have a moving station, process trails
15669           if (atoi(p_station->speed) < TRAIL_MAX_SPEED)       // reject high speed data (undef gives 0)
15670           {
15671             // we now may already have the 2nd position, so store the old one first
15672             if (debug_level & 256)
15673             {
15674               fprintf(stderr,"Station %s valid speed %s\n",
15675                       p_station->call_sign, p_station->speed);
15676             }
15677             if (p_station->newest_trackpoint == NULL)
15678             {
15679               if (debug_level & 256)
15680               {
15681                 fprintf(stderr,"Station %s no trail history.\n",
15682                         p_station->call_sign);
15683               }
15684               if (position_defined(last_lat,last_lon,1))    // ignore undefined and 0N/0E
15685               {
15686                 if (debug_level & 256)
15687                 {
15688                   fprintf(stderr,"Storing old position for %s\n",
15689                           p_station->call_sign);
15690                 }
15691                 (void)store_trail_point(p_station,
15692                                         last_lon,
15693                                         last_lat,
15694                                         last_stn_sec,
15695                                         last_alt,
15696                                         last_speed,
15697                                         last_course,
15698                                         last_flag);
15699               }
15700             }
15701             //if (   p_station->coord_lon != last_lon
15702             //    || p_station->coord_lat != last_lat ) {
15703             // we don't store redundant points (may change this
15704             // later ?)
15705             //
15706             // There are often echoes delayed 15 minutes
15707             // or so it looks ugly on the trail, so I
15708             // want to discard them This also discards
15709             // immediate echoes. Duplicates back in time
15710             // up to TRAIL_ECHO_TIME minutes are
15711             // discarded.
15712             //
15713             if (!is_trailpoint_echo(p_station))
15714             {
15715               (void)store_trail_point(p_station,
15716                                       p_station->coord_lon,
15717                                       p_station->coord_lat,
15718                                       p_station->sec_heard,
15719                                       p_station->altitude,
15720                                       p_station->speed,
15721                                       p_station->course,
15722                                       p_station->flag);
15723               changed_pos = 1;
15724 
15725               // Check whether it's a locally-owned object/item
15726               if (object_is_mine)
15727               {
15728 
15729                 // Update time, change position in
15730                 // time-sorted list to change
15731                 // expiration time.
15732                 move_station_time(p_station,p_time);
15733                 // Give it a new timestamp
15734                 p_station->sec_heard = curr_sec;
15735 
15736                 //fprintf(stderr,"Updating last heard time\n");
15737               }
15738             }
15739             else if (debug_level & 256)
15740             {
15741               fprintf(stderr,"Trailpoint echo detected for %s\n",
15742                       p_station->call_sign);
15743             }
15744           }
15745           else
15746           {
15747             if (debug_level & 256 || debug_level & 1)
15748             {
15749               fprintf(stderr,"Speed over %d mph\n",TRAIL_MAX_SPEED);
15750             }
15751           }
15752 
15753           if (track_station_on == 1)          // maybe we are tracking a station
15754           {
15755             track_station(da,tracking_station_call,p_station);
15756           }
15757         } // moving...
15758 
15759         // now do the drawing to the screen
15760         ok_to_display = !altnet || is_altnet(p_station); // Optimization step, needed twice below.
15761         screen_update = 0;
15762         if (changed_pos == 1 && Display_.trail && ((p_station->flag & ST_INVIEW) != 0))
15763         {
15764           if (ok_to_display)
15765           {
15766             if (debug_level & 256)
15767             {
15768               fprintf(stderr,"Adding Solid Trail for %s\n",
15769                       p_station->call_sign);
15770             }
15771             draw_trail(da,p_station,1);         // update trail
15772             screen_update = 1;
15773           }
15774           else if (debug_level & 256)
15775           {
15776             fprintf(stderr,"Skipped trail for %s (altnet)\n",
15777                     p_station->call_sign);
15778           }
15779         }
15780         if (position_on_screen(p_station->coord_lat,p_station->coord_lon))
15781         {
15782           if (changed_pos == 1 || !position_defined(last_lat,last_lon,0))
15783           {
15784             if (ok_to_display)
15785             {
15786               display_station(da,p_station,1);// update symbol
15787               screen_update = 1;
15788             }
15789           }
15790         }
15791       } // defined position
15792     }
15793 
15794     if (screen_update)
15795     {
15796       if (p_station->data_via == 'T')     // Data from local TNC
15797       {
15798         //WE7U
15799         // or data_via == 'I' and last_port_heard == AGWPE interface
15800         redraw_on_new_data = 2; // Update all symbols NOW!
15801       }
15802       else if (p_station->data_via == 'F')    // If data from file
15803       {
15804         redraw_on_new_data = 1; // Update each 2 secs
15805       }
15806       //            else if (scale_y > 2048) {  // Wider area of world
15807       else
15808       {
15809         redraw_on_new_data = 0; // Update each 60 secs
15810       }
15811     }
15812 
15813     // announce stations in the status line
15814     //        if (!is_my_call(p_station->call_sign,1) // Check SSID as well
15815     //                && !is_my_call(p_station->origin,1) // Check SSID as well
15816     if (!is_my_station(p_station)
15817         && !is_my_object_item(p_station) // Check SSID as well
15818         && !wait_to_redraw)
15819     {
15820       if (new_station)
15821       {
15822         if (p_station->origin[0] == '\0')   // new station
15823         {
15824           xastir_snprintf(station_id, sizeof(station_id), langcode("BBARSTA001"),p_station->call_sign);
15825         }
15826         else                                // new object
15827         {
15828           xastir_snprintf(station_id, sizeof(station_id), langcode("BBARSTA000"),p_station->call_sign);
15829         }
15830       }
15831       else                                  // updated data
15832       {
15833         xastir_snprintf(station_id, sizeof(station_id), langcode("BBARSTA002"),p_station->call_sign);
15834       }
15835 
15836       statusline(station_id,0);
15837     }
15838 
15839     // announce new station with sound file or speech synthesis
15840     if (new_station && !wait_to_redraw)     // && !is_my_call(p_station->call_sign,1) // ???
15841     {
15842       if (sound_play_new_station)
15843       {
15844         play_sound(sound_command,sound_new_station);
15845       }
15846 
15847 #ifdef HAVE_FESTIVAL
15848       if (festival_speak_new_station)
15849       {
15850         char speech_callsign[50];
15851 
15852         xastir_snprintf(speech_callsign,
15853                         sizeof(speech_callsign),
15854                         "%s",
15855                         p_station->call_sign);
15856         spell_it_out(speech_callsign, 50);
15857 
15858         xastir_snprintf(station_id,
15859                         sizeof(station_id),
15860                         "%s, %s",
15861                         langcode("SPCHSTR010"),
15862                         speech_callsign);
15863         SayText(station_id);
15864       }
15865 #endif  // HAVE_FESTIVAL
15866     }
15867 
15868     // check for range and DX
15869     //        if (found_pos && !is_my_call(p_station->call_sign,1)) { // Check SSID also
15870     if (found_pos
15871         && !is_my_station(p_station))   // Check SSID also
15872     {
15873 
15874       // if station has a position with the data
15875       /* Check Audio Alarms based on incoming packet */
15876       /* FG don't care if this is on screen or off get position */
15877       l_lat = convert_lat_s2l(my_lat);
15878       l_lon = convert_lon_s2l(my_long);
15879 
15880       // Get distance in nautical miles.
15881       value = (float)calc_distance_course(l_lat,l_lon,
15882                                           p_station->coord_lat,p_station->coord_lon,temp_data,sizeof(temp_data));
15883 
15884       // Convert to whatever measurement value we're currently using
15885       distance = value * cvt_kn2len;
15886 
15887       /* check ranges */
15888       if ((distance > atof(prox_min)) && (distance < atof(prox_max)))
15889       {
15890 
15891         //fprintf(stderr,"Station within proximity circle, creating waypoint\n");
15892         create_garmin_waypoint(p_station->coord_lat,
15893                                p_station->coord_lon,
15894                                p_station->call_sign);
15895 
15896         if (sound_play_prox_message)
15897         {
15898           xastir_snprintf(station_id, sizeof(station_id),
15899                           "%s < %.3f %s",p_station->call_sign,
15900                           distance,
15901                           english_units?langcode("UNIOP00004"):langcode("UNIOP00005"));
15902           statusline(station_id,0);
15903           play_sound(sound_command,sound_prox_message);
15904           /*fprintf(stderr,"%s> PROX distance %f\n",p_station->call_sign, distance);*/
15905         }
15906       }
15907 #ifdef HAVE_FESTIVAL
15908       if ((distance > atof(prox_min)) && (distance < atof(prox_max)) && festival_speak_proximity_alert)
15909       {
15910         char speech_callsign[50];
15911 
15912         xastir_snprintf(speech_callsign,
15913                         sizeof(speech_callsign),
15914                         "%s",
15915                         p_station->call_sign);
15916         spell_it_out(speech_callsign, 50);
15917 
15918         if (english_units)
15919         {
15920           if (distance < 1.0)
15921             xastir_snprintf(station_id, sizeof(station_id), langcode("SPCHSTR005"), speech_callsign,
15922                             (int)(distance * 1760), langcode("SPCHSTR004")); // say it in yards
15923           else if ((int)((distance * 10) + 0.5) % 10)
15924             xastir_snprintf(station_id, sizeof(station_id), langcode("SPCHSTR006"), speech_callsign, distance,
15925                             langcode("SPCHSTR003")); // say it in miles with one decimal
15926           else
15927             xastir_snprintf(station_id, sizeof(station_id), langcode("SPCHSTR005"), speech_callsign, (int)(distance + 0.5),
15928                             langcode("SPCHSTR003")); // say it in miles with no decimal
15929         }
15930         else
15931         {
15932           if (distance < 1.0)
15933             xastir_snprintf(station_id, sizeof(station_id), langcode("SPCHSTR005"), speech_callsign,
15934                             (int)(distance * 1000), langcode("SPCHSTR002")); // say it in meters
15935           else if ((int)((distance * 10) + 0.5) % 10)
15936             xastir_snprintf(station_id, sizeof(station_id), langcode("SPCHSTR006"), speech_callsign, distance,
15937                             langcode("SPCHSTR001")); // say it in kilometers with one decimal
15938           else
15939             xastir_snprintf(station_id, sizeof(station_id), langcode("SPCHSTR005"), speech_callsign, (int)(distance + 0.5),
15940                             langcode("SPCHSTR001")); // say it in kilometers with no decimal
15941         }
15942         SayText(station_id);
15943       }
15944 #endif  // HAVE_FESTIVAL
15945       /* FG really should check the path before we do this and add setup for these ranges */
15946       if (sound_play_band_open_message && from == DATA_VIA_TNC && !(p_station->flag & ST_3RD_PT) &&
15947           (distance > atof(bando_min)) && (distance < atof(bando_max)))
15948       {
15949         xastir_snprintf(station_id, sizeof(station_id), "%s %s %.1f %s",p_station->call_sign, langcode("UMBNDO0001"),
15950                         distance, english_units?langcode("UNIOP00004"):langcode("UNIOP00005"));
15951         statusline(station_id,0);
15952         play_sound(sound_command,sound_band_open_message);
15953         /*fprintf(stderr,"%s> BO distance %f\n",p_station->call_sign, distance);*/
15954       }
15955 #ifdef HAVE_FESTIVAL
15956       if (festival_speak_band_opening && from == DATA_VIA_TNC && !(p_station->flag & ST_3RD_PT) &&
15957           (distance > atof(bando_min)) && (distance < atof(bando_max)))
15958       {
15959         char speech_callsign[50];
15960 
15961         xastir_snprintf(speech_callsign,
15962                         sizeof(speech_callsign),
15963                         "%s",
15964                         p_station->call_sign);
15965         spell_it_out(speech_callsign, 50);
15966 
15967         xastir_snprintf(station_id,
15968                         sizeof(station_id),
15969                         langcode("SPCHSTR011"),
15970                         speech_callsign,
15971                         distance,
15972                         english_units?langcode("SPCHSTR003"):langcode("SPCHSTR001"));
15973         SayText(station_id);
15974       }
15975 #endif  // HAVE_FESTIVAL
15976 
15977     } // end found_pos
15978 
15979 
15980 #ifdef HAVE_DB
15981     // Clumsy way of doing things - needs a more elegant approach
15982     // iterate through interfaces
15983     if (p_station->data_via != DATA_VIA_DATABASE)
15984     {
15985       if (debug_level & 4096)
15986       {
15987         fprintf(stderr,"Trying to store station %s to database interfaces.\n",p_station->call_sign);
15988       }
15989       for (ii=0; ii<MAX_IFACE_DEVICES; ii++)
15990       {
15991         if (debug_level & 4096)
15992         {
15993           fprintf(stderr,"Trying interface [%d] ",ii);
15994           fprintf(stderr,"connection [%p]\n",&connections[ii]);
15995         }
15996         if (&connections[ii] != NULL)
15997         {
15998           // Note < 4 is an artificial upper limit that may catch cases where the memmory
15999           // for the connection has been overwritten.
16000           if (connections[ii].type > 0 && connections[ii].type < 4)
16001           {
16002             if (debug_level & 4096)
16003             {
16004               fprintf(stderr,"type=[%d]\n",connections[ii].type);
16005             }
16006             if (port_data[ii].status == DEVICE_UP)
16007             {
16008               if (connections[ii].descriptor->device_type==DEVICE_SQL_DATABASE)
16009               {
16010                 if (debug_level & 4096)
16011                 {
16012                   fprintf(stderr,"Trying interface %d\n",ii);
16013                 }
16014                 // if interface is a sql server interface
16015                 // write station data to sql database
16016                 ok = storeStationSimpleToGisDb(&connections[ii], p_station);
16017                 if (ok==1)
16018                 {
16019                   if (debug_level & 4096)
16020                   {
16021                     fprintf(stderr,"Stored station %s to database interface %d.\n",p_station->call_sign,ii);
16022                   }
16023                 }
16024                 else
16025                 {
16026                   pingConnection(&connections[ii]);
16027                 }
16028               }
16029             }
16030           }
16031         }
16032       }
16033     }
16034 #endif /* HAVE_DB */
16035 
16036   }   // valid data into database
16037 
16038   return(ok);
16039 }   // End of data_add() function
16040 
16041 
16042 
16043 
16044 
16045 // Code to compute SmartBeaconing(tm) rates.
16046 //
16047 // SmartBeaconing(tm) was invented by Steve Bragg (KA9MVA) and Tony Arnerich
16048 // (KD7TA).  Its main goal is to change the beacon rate based on speed
16049 // and cornering.  It does speed-variant corner pegging and
16050 // speed-variant posit rate.
16051 
16052 // Some tweaks have been added to the generic SmartBeaconing(tm) algorithm,
16053 // but are current labeled as experimental and commented out:  1) We do
16054 // a posit as soon as we first cross below the sb_low_speed_limit, and
16055 // 2) We do a posit as soon as we cross above the sb_low_speed_limit if
16056 // we haven't done a posit for sb_turn_time seconds.  These tweaks are
16057 // intended to help show that the mobile station has stopped (so that
16058 // dead-reckoning doesn't keep it moving across the map on other
16059 // people's displays) and to more quickly show that the station is
16060 // moving again (for the case where they're in stop-and-go traffic
16061 // perhaps).
16062 //
16063 // It's possible that these new tweaks won't work well for the case
16064 // where a station is traveling near the speed of sb_low_speed_limit.
16065 // In this case they'll generate a posit each time they go below it and
16066 // every time they go above it if they haven't done a posit in
16067 // sb_turn_time seconds.  This could result in a lot of posits very
16068 // quickly.  We may need to add yet another limit just above the
16069 // sb_low_speed_limit for hysteresis, and not posit until we cross above
16070 // that new limit.
16071 //
16072 // Several special SmartBeaconing(tm) parameters come into play here:
16073 //
16074 // sb_turn_min          Minimum degrees at which corner pegging will
16075 //                      occur.  The next parameter affects this for
16076 //                      lower speeds.
16077 //
16078 // sb_turn_slope        Fudget factor for making turns less sensitive at
16079 //                      lower speeds.  No real units on this one.
16080 //                      It ends up being non-linear over the speed
16081 //                      range the way the original SmartBeaconing(tm)
16082 //                      algorithm works.
16083 //
16084 // sb_turn_time         Dead-time before/after a corner peg beacon.
16085 //                      Units are in seconds.
16086 //
16087 // sb_posit_fast        Fast posit rate, used if >= sb_high_speed_limit.
16088 //                      Units are in seconds.
16089 //
16090 // sb_posit_slow        Slow posit rate, used if <= sb_low_speed_limit.
16091 //                      Units are in minutes.
16092 //
16093 // sb_low_speed_limit   Low speed limit, units are in Mph.
16094 //
16095 // sb_high_speed_limit  High speed limit, units are in Mph.
16096 //
16097 //
16098 //  Input: Course in degrees
16099 //         Speed in knots
16100 //
16101 // Output: May force beacons by setting posit_next_time to various
16102 //         values.
16103 //
16104 // Modify: sb_POSIT_rate
16105 //         sb_current_heading
16106 //         sb_last_heading
16107 //         posit_next_time
16108 //
16109 //
16110 // With the defaults compiled into the code, here are the
16111 // turn_thresholds for a few speeds:
16112 //
16113 // Example: sb_turn_min = 20
16114 //          sb_turn_slope = 25
16115 //          sb_high_speed_limit = 60
16116 //
16117 //      > 60mph  20 degrees
16118 //        50mph  25 degrees
16119 //        40mph  26 degrees
16120 //        30mph  28 degrees
16121 //        20mph  33 degrees
16122 //        10mph  45 degrees
16123 //        3mph 103 degrees (we limit it to 80 now)
16124 //        2mph 145 degrees (we limit it to 80 now)
16125 //
16126 // I added a max threshold of 80 degrees into the code.  145 degrees
16127 // is unreasonable to expect except for perhaps switchback or 'U'
16128 // turns.
16129 //
16130 // It'd probably be better to do a linear interpolation of
16131 // turn_threshold based on min/max speed and min/max turns.  That's
16132 // not how the SmartBeaconing(tm) algorithm coders implemented it in
16133 // the HamHud though.
16134 //
compute_smart_beacon(char * current_course,char * current_speed)16135 void compute_smart_beacon(char *current_course, char *current_speed)
16136 {
16137   int course;
16138   int speed;
16139   int turn_threshold;
16140   time_t secs_since_beacon;
16141   int heading_change_since_beacon;
16142   int beacon_now = 0;
16143   int curr_sec = sec_now();
16144 
16145   // Don't compute SmartBeaconing(tm) parameters or force any beacons
16146   // if we're not in that mode!
16147   if (!smart_beaconing)
16148   {
16149     return;
16150   }
16151 
16152   // Convert from knots to mph/kph (whichever is selected)
16153   speed = (int)(atof(current_speed) * cvt_kn2len + 0.5); // Poor man's rounding
16154 
16155   course = atoi(current_course);
16156 
16157   secs_since_beacon = curr_sec - posit_last_time;
16158 
16159   // Check for the low speed threshold, set to slow posit rate if
16160   // we're going slow.
16161   if (speed <= sb_low_speed_limit)
16162   {
16163     //fprintf(stderr,"Slow speed\n");
16164 
16165 
16166     // EXPERIMENTAL!!!
16167     ////////////////////////////////////////////////////////////////////
16168     // Check to see if we're just crossing the threshold, if so,
16169     // beacon.  This keeps dead-reckoning working properly on
16170     // other people's displays.  Be careful for speeds near this
16171     // threshold though.  We really need a slow-speed rate and a
16172     // stop rate, with some distance between them, in order to
16173     // have some hysteresis for these posits.
16174     //        if (sb_POSIT_rate != (sb_posit_slow * 60) ) { // Previous rate was _not_ the slow rate
16175     //            beacon_now++; // Force a posit right away
16176     //            //fprintf(stderr,"Stopping, POSIT!\n");
16177     //        }
16178     ////////////////////////////////////////////////////////////////////
16179 
16180 
16181     // Set to slow posit rate
16182     sb_POSIT_rate = sb_posit_slow * 60; // Convert to seconds
16183   }
16184   else    // We're moving faster than the low speed limit
16185   {
16186 
16187 
16188     // EXPERIMENTAL!!!
16189     ////////////////////////////////////////////////////////////////////
16190     // Check to see if we're just starting to move.  Again, we
16191     // probably need yet-another-speed-limit here to provide
16192     // some hysteresis.
16193     //        if ( (secs_since_beacon > sb_turn_time)    // Haven't beaconed for a bit
16194     //                && (sb_POSIT_rate == (sb_posit_slow * 60) ) ) { // Last rate was the slow rate
16195     //            beacon_now++; // Force a posit right away
16196     //            //fprintf(stderr,"Starting to move, POSIT!\n");
16197     //        }
16198     ////////////////////////////////////////////////////////////////////
16199 
16200 
16201     // Start with turn_min degrees as the threshold
16202     turn_threshold = sb_turn_min;
16203 
16204     // Adjust rate according to speed
16205     if (speed > sb_high_speed_limit)    // We're above the high limit
16206     {
16207       sb_POSIT_rate = sb_posit_fast;
16208       //fprintf(stderr,"Setting fast rate\n");
16209     }
16210     else    // We're between the high/low limits.  Set a between rate
16211     {
16212       sb_POSIT_rate = (sb_posit_fast * sb_high_speed_limit) / speed;
16213       //fprintf(stderr,"Setting medium rate\n");
16214 
16215       // Adjust turn threshold according to speed
16216       turn_threshold += (int)( (sb_turn_slope * 10) / speed);
16217     }
16218 
16219     // Force a maximum turn threshold of 80 degrees (still too
16220     // high?)
16221     if (turn_threshold > 80)
16222     {
16223       turn_threshold = 80;
16224     }
16225 
16226     // Check to see if we've written anything into
16227     // sb_last_heading variable yet.  If not, write the current
16228     // course into it.
16229     if (sb_last_heading == -1)
16230     {
16231       sb_last_heading = course;
16232     }
16233 
16234     // Corner-pegging.  Note that we don't corner-peg if we're
16235     // below the low-speed threshold.
16236     heading_change_since_beacon = abs(course - sb_last_heading);
16237     if (heading_change_since_beacon > 180)
16238     {
16239       heading_change_since_beacon = 360 - heading_change_since_beacon;
16240     }
16241 
16242     //fprintf(stderr,"course change:%d\n",heading_change_since_beacon);
16243 
16244     if ( (heading_change_since_beacon > turn_threshold)
16245          && (secs_since_beacon > sb_turn_time) )
16246     {
16247       beacon_now++;   // Force a posit right away
16248 
16249       //fprintf(stderr,"Corner, POSIT!\tOld:%d\tNew:%d\tDifference:%d\tSpeed: %d\tTurn Threshold:%d\n",
16250       //    sb_last_heading,
16251       //    course,
16252       //    heading_change_since_beacon,
16253       //    speed,
16254       //    turn_threshold);
16255     }
16256 
16257 
16258     // EXPERIMENTAL
16259     ////////////////////////////////////////////////////////////////////
16260     // If we haven't beaconed for a bit (3 * sb_turn_time?), and
16261     // just completed a turn, check to see if our heading has
16262     // stabilized yet.  If so, beacon the latest heading.  We'll
16263     // have to save another variable which says whether the last
16264     // beacon was caused by corner-pegging.  The net effect is
16265     // that we'll get an extra posit coming out of a turn that
16266     // specifies our correct course and probably a more accurate
16267     // speed until the next posit.  This should make
16268     // dead-reckoning work even better.
16269     if (0)
16270     {
16271     }
16272     ////////////////////////////////////////////////////////////////////
16273 
16274 
16275   }
16276 
16277   // Check to see whether we've sped up sufficiently for the
16278   // posit_next_time variable to be too far out.  If so, shorten
16279   // that interval to match the current speed.
16280   if ( (posit_next_time - curr_sec) > sb_POSIT_rate)
16281   {
16282     posit_next_time = curr_sec + sb_POSIT_rate;
16283   }
16284 
16285 
16286   if (beacon_now)
16287   {
16288     posit_next_time = 0;    // Force a posit right away
16289   }
16290 
16291   // Should we also check for a rate too fast for the current
16292   // speed?  Probably not.  It'll get modified at the next beacon
16293   // time, which will happen quickly.
16294 
16295   // Save course for use later.  It gets put into sb_last_heading
16296   // in UpdateTime() if a beacon occurs.  We then use it above to
16297   // determine the course deviation since the last time we
16298   // beaconed.
16299   sb_current_heading = course;
16300 }
16301 
16302 
16303 
16304 
16305 
16306 // Speed is in knots
my_station_gps_change(char * pos_long,char * pos_lat,char * course,char * speed,char UNUSED (speedu),char * alt,char * sats)16307 void my_station_gps_change(char *pos_long, char *pos_lat, char *course, char *speed, char UNUSED(speedu), char *alt, char *sats)
16308 {
16309   long pos_long_temp, pos_lat_temp;
16310   char temp_data[40];   // short term string storage
16311   char temp_lat[12];
16312   char temp_long[12];
16313   DataRow *p_station;
16314   DataRow *p_time;
16315 
16316   // Note that speed will be in knots 'cuz it was derived from a
16317   // GPRMC string without modification.
16318 
16319   // Recompute the SmartBeaconing(tm) parameters based on current/past
16320   // course & speed.  Sending the speed in knots.
16321   //fprintf(stderr,"Speed: %s\n",speed);
16322   compute_smart_beacon(course, speed);
16323 
16324   p_station = NULL;
16325   if (!search_station_name(&p_station,my_callsign,1))    // find my data in the database
16326   {
16327     p_time = NULL;          // add to end of time sorted list
16328     //fprintf(stderr,"my_station_gps_change()\n");
16329     p_station = add_new_station(p_station,p_time,my_callsign);
16330   }
16331   p_station->flag |= ST_ACTIVE;
16332   p_station->data_via = 'L';
16333   p_station->flag &= (~ST_3RD_PT);            // clear "third party" flag
16334   p_station->record_type = NORMAL_APRS;
16335 
16336   // Free any old path we might have
16337   if (p_station->node_path_ptr != NULL)
16338   {
16339     free(p_station->node_path_ptr);
16340   }
16341   // Malloc and store the new path
16342   p_station->node_path_ptr = (char *)malloc(strlen("local") + 1);
16343   CHECKMALLOC(p_station->node_path_ptr);
16344 
16345   substr(p_station->node_path_ptr,"local",strlen("local"));
16346 
16347   // Create a timestamp from the current time
16348   xastir_snprintf(p_station->packet_time,
16349                   sizeof(p_station->packet_time),
16350                   "%s",
16351                   get_time(temp_data));
16352   // Create a timestamp from the current time
16353   xastir_snprintf(p_station->pos_time,
16354                   sizeof(p_station->pos_time),
16355                   "%s",
16356                   get_time(temp_data));
16357   p_station->flag |= ST_MSGCAP;               // set "message capable" flag
16358 
16359   /* convert to long and weed out any odd data */
16360   pos_long_temp = convert_lon_s2l(pos_long);
16361   pos_lat_temp  = convert_lat_s2l(pos_lat);
16362 
16363   /* convert back to clean string for config data */
16364   convert_lon_l2s(pos_long_temp, temp_data, sizeof(temp_data), CONVERT_HP_NORMAL);
16365   xastir_snprintf(temp_long, sizeof(temp_long), "%c%c%c%c%c.%c%c%c%c",temp_data[0],temp_data[1],temp_data[2], temp_data[4],temp_data[5],
16366                   temp_data[7],temp_data[8], temp_data[9], temp_data[10]);
16367   convert_lat_l2s(pos_lat_temp, temp_data, sizeof(temp_data), CONVERT_HP_NORMAL);
16368   xastir_snprintf(temp_lat, sizeof(temp_lat), "%c%c%c%c.%c%c%c%c",temp_data[0],temp_data[1],temp_data[3],temp_data[4], temp_data[6],
16369                   temp_data[7], temp_data[8],temp_data[9]);
16370 
16371   /* fill the data in */    // ???????????????
16372   memcpy(my_lat, temp_lat, sizeof(my_lat));
16373   my_lat[sizeof(my_lat)-1] = '\0';  // Terminate string
16374 
16375   memcpy(my_long, temp_long, sizeof(my_long));
16376   my_long[sizeof(my_long)-1] = '\0';  // Terminate string
16377 
16378   p_station->coord_lat = convert_lat_s2l(my_lat);
16379   p_station->coord_lon = convert_lon_s2l(my_long);
16380 
16381   if ((p_station->coord_lon != pos_long_temp) || (p_station->coord_lat != pos_lat_temp))
16382   {
16383     /* check to see if enough to change pos on screen */
16384     if ((pos_long_temp>NW_corner_longitude) && (pos_long_temp<SE_corner_longitude))
16385     {
16386       if ((pos_lat_temp>NW_corner_latitude) && (pos_lat_temp<SE_corner_latitude))
16387       {
16388         if((labs((p_station->coord_lon+(scale_x/2))-pos_long_temp)/scale_x)>0
16389             || (labs((p_station->coord_lat+(scale_y/2))-pos_lat_temp)/scale_y)>0)
16390         {
16391           //redraw_on_new_data = 1;   // redraw next chance
16392           //redraw_on_new_data = 2;     // better response?
16393           if (debug_level & 256)
16394           {
16395             fprintf(stderr,"Redraw on new gps data \n");
16396           }
16397           statusline(langcode("BBARSTA038"),0);
16398         }
16399         else if (debug_level & 256)
16400         {
16401           fprintf(stderr,"New Position same pixel as old.\n");
16402         }
16403       }
16404       else if (debug_level & 256)
16405       {
16406         fprintf(stderr,"New Position is off edge of screen.\n");
16407       }
16408     }
16409     else if (debug_level & 256)
16410     {
16411       fprintf(stderr,"New position is off side of screen.\n");
16412     }
16413   }
16414 
16415   p_station->coord_lat = pos_lat_temp;    // DK7IN: we have it already !??
16416   p_station->coord_lon = pos_long_temp;
16417 
16418   curr_sec = sec_now();
16419   my_last_altitude_time = curr_sec;
16420   xastir_snprintf(p_station->speed,
16421                   sizeof(p_station->speed),
16422                   "%s",
16423                   speed);
16424   // is speed always in knots, otherwise we need a conversion!
16425   xastir_snprintf(p_station->course,
16426                   sizeof(p_station->course),
16427                   "%s",
16428                   course);
16429   xastir_snprintf(p_station->altitude,
16430                   sizeof(p_station->altitude),
16431                   "%s",
16432                   alt);
16433   // altu;    unit should always be meters  ????
16434 
16435   if(debug_level & 256)
16436     fprintf(stderr,"GPS MY_LAT <%s> MY_LONG <%s> MY_ALT <%s>\n",
16437             my_lat, my_long, alt);
16438 
16439   /* get my last altitude meters to feet */
16440   my_last_altitude=(long)(atof(alt)*3.28084);
16441 
16442   /* get my last course in deg */
16443   my_last_course=atoi(course);
16444 
16445   /* get my last speed in knots */
16446   my_last_speed = atoi(speed);
16447   xastir_snprintf(p_station->sats_visible,
16448                   sizeof(p_station->sats_visible),
16449                   "%s",
16450                   sats);
16451 
16452   // Update "heard" time for our new position
16453   p_station->sec_heard = curr_sec;
16454 
16455   //if (   p_station->coord_lon != last_lon
16456   //    || p_station->coord_lat != last_lat ) {
16457   // we don't store redundant points (may change this later ?)
16458   // There are often echoes delayed 15 minutes or so it looks ugly
16459   // on the trail, so I want to discard them This also discards
16460   // immediate echoes.  Duplicates back in time up to
16461   // TRAIL_ECHO_TIME minutes are discarded.
16462   //
16463   if (!is_trailpoint_echo(p_station))
16464   {
16465     (void)store_trail_point(p_station,
16466                             p_station->coord_lon,
16467                             p_station->coord_lat,
16468                             curr_sec,
16469                             p_station->altitude,
16470                             p_station->speed,
16471                             p_station->course,
16472                             p_station->flag);
16473   }
16474   if (debug_level & 256)
16475   {
16476     fprintf(stderr,"Adding Solid Trail for %s\n",
16477             p_station->call_sign);
16478   }
16479   draw_trail(da,p_station,1);         // update trail
16480   display_station(da,p_station,1);    // update symbol
16481 
16482   if (track_station_on == 1)          // maybe we are tracking ourselves?
16483   {
16484     track_station(da,tracking_station_call,p_station);
16485   }
16486 
16487   // We parsed a good GPS string, so allow beaconing to proceed
16488   // normally for a while.
16489   my_position_valid = 3;
16490   //fprintf(stderr,"Valid GPS input: my_position_valid = 3\n");
16491 
16492   //redraw_on_new_data = 1;   // redraw next chance
16493   redraw_on_new_data = 2;     // Immediate update of symbols/tracks
16494 }
16495 
16496 
16497 
16498 
16499 
my_station_add(char * my_callsign,char my_group,char my_symbol,char * my_long,char * my_lat,char * my_phg,char * my_comment,char my_amb)16500 void my_station_add(char *my_callsign, char my_group, char my_symbol, char *my_long, char *my_lat, char *my_phg, char *my_comment, char my_amb)
16501 {
16502   DataRow *p_station;
16503   DataRow *p_time;
16504   char temp_data[40];   // short term string storage
16505   char *strp;
16506 
16507   p_station = NULL;
16508   if (!search_station_name(&p_station,my_callsign,1))    // find call
16509   {
16510     p_time = NULL;          // add to end of time sorted list
16511     //fprintf(stderr,"my_station_add()\n");
16512     p_station = add_new_station(p_station,p_time,my_callsign);
16513   }
16514   p_station->flag |= ST_ACTIVE;
16515   p_station->flag |= ST_MYSTATION;
16516   p_station->data_via = 'L';
16517   p_station->flag &= (~ST_3RD_PT);            // clear "third party" flag
16518   p_station->record_type = NORMAL_APRS;
16519 
16520   if (transmit_compressed_posit)
16521   {
16522     // Compressed posit
16523     p_station->error_ellipse_radius = 600; // Default of 6m
16524     p_station->lat_precision = 6;
16525     p_station->lon_precision = 6;
16526   }
16527   else
16528   {
16529     // Standard APRS posit
16530     p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
16531     p_station->lat_precision = 60;
16532     p_station->lon_precision = 60;
16533   }
16534 
16535   // Free any old path we might have
16536   if (p_station->node_path_ptr != NULL)
16537   {
16538     free(p_station->node_path_ptr);
16539   }
16540   // Malloc and store the new path
16541   p_station->node_path_ptr = (char *)malloc(strlen("local") + 1);
16542   CHECKMALLOC(p_station->node_path_ptr);
16543 
16544   substr(p_station->node_path_ptr,"local",strlen("local"));
16545 
16546   // Create a timestamp from the current time
16547   xastir_snprintf(p_station->packet_time,
16548                   sizeof(p_station->packet_time),
16549                   "%s",
16550                   get_time(temp_data));
16551   // Create a timestamp from the current time
16552   xastir_snprintf(p_station->pos_time,
16553                   sizeof(p_station->pos_time),
16554                   "%s",
16555                   get_time(temp_data));
16556   p_station->flag |= ST_MSGCAP;               // set "message capable" flag
16557 
16558   /* Symbol overlay */
16559   if(my_group != '/' && my_group != '\\')
16560   {
16561     // Found an overlay character.  Check it.
16562     if ( (my_group >= '0' && my_group <= '9')
16563          || (my_group >= 'A' && my_group <= 'Z') )
16564     {
16565       // Overlay character is good
16566       p_station->aprs_symbol.aprs_type = '\\';
16567       p_station->aprs_symbol.special_overlay = my_group;
16568     }
16569     else
16570     {
16571       // Found a bad overlay character
16572       p_station->aprs_symbol.aprs_type = my_group;
16573       p_station->aprs_symbol.special_overlay = '\0';
16574     }
16575   }
16576   else      // Normal symbol, no overlay
16577   {
16578     p_station->aprs_symbol.aprs_type = my_group;
16579     p_station->aprs_symbol.special_overlay = '\0';
16580   }
16581   p_station->aprs_symbol.aprs_symbol = my_symbol;
16582 
16583   p_station->pos_amb = my_amb;
16584   xastir_snprintf(temp_data,
16585                   sizeof(temp_data),
16586                   "%s",
16587                   my_lat);
16588 
16589   //fprintf(stderr," my_lat:%s\n",temp_data);
16590 
16591   temp_data[9] = '\0';
16592 
16593   strp = &temp_data[20];
16594   xastir_snprintf(strp,
16595                   //        sizeof(strp),   // No good, as strp is a pointer
16596                   (int)(sizeof(temp_data) / 2),
16597                   "%s",
16598                   my_long);
16599   strp[10] = '\0';
16600 
16601   //fprintf(stderr,"my_long:%s\n",my_long);
16602   //fprintf(stderr,"my_long:%s\n",strp);
16603 
16604   switch (my_amb)
16605   {
16606     case 1: // 1/10th minute
16607       temp_data[6] = strp[7] = '5';
16608       break;
16609     case 2: // 1 minute
16610       temp_data[5] = strp[6] = '5';
16611       temp_data[6] = '0';
16612       strp[7]      = '0';
16613       break;
16614     case 3: // 10 minutes
16615       temp_data[3] = strp[4] = '5';
16616       temp_data[5] = temp_data[6] = '0';
16617       strp[6]      = strp[7]      = '0';
16618       break;
16619     case 4: // 1 degree
16620       temp_data[2] = strp[3] = '3';
16621       temp_data[3] = temp_data[5] = temp_data[6] = '0';
16622       strp[4]      = strp[6]      = strp[7]      = '0';
16623       break;
16624     case 0:
16625     default:
16626       break;
16627   }
16628   p_station->coord_lat = convert_lat_s2l(temp_data);
16629   p_station->coord_lon = convert_lon_s2l(strp);
16630 
16631   if (position_on_extd_screen(p_station->coord_lat,p_station->coord_lon))
16632   {
16633     p_station->flag |= (ST_INVIEW);   // set   "In View" flag
16634   }
16635   else
16636   {
16637     p_station->flag &= (~ST_INVIEW);  // clear "In View" flag
16638   }
16639 
16640   substr(p_station->power_gain,my_phg,7);
16641 
16642   add_comment(p_station,my_comment);
16643 
16644   my_last_course = 0;         // set my last course in deg to zero
16645   redo_list = (int)TRUE;      // update active station lists
16646 }
16647 
16648 
16649 
16650 
16651 
16652 // Write the text from the packet_data_string out to the dialog if
16653 // the dialog exists.  The user can contract/expand the dialog and
16654 // always have it filled with the most current data out of the
16655 // string.
16656 //
display_packet_data(void)16657 void display_packet_data(void)
16658 {
16659 
16660   if( (Display_data_dialog != NULL)
16661       && (redraw_on_new_packet_data !=0))
16662   {
16663     int pos;
16664     int last_char;
16665     int i;
16666 
16667     // Find out the last character position in the dialog text
16668     // area.
16669     last_char = XmTextGetLastPosition(Display_data_text);
16670 
16671     //fprintf(stderr,"In display_packet_data: first_line=%d,next_line=%d,ncharsdel=%d,nlinesadd=%d\n",first_line,next_line,ncharsdel,nlinesadd);
16672 
16673     if (first_line != -1)   // there is data in the array
16674     {
16675       if (last_char == 0 || ncharsdel>=last_char)
16676       {
16677         //fprintf(stderr,"  Starting from clean slate...\n");
16678         // but there is no text in the dialog or more chars to delete than
16679         // there actually are in the dialog
16680         // Clear the dialog just in case:
16681         XmTextReplace(Display_data_text,0,last_char,"");
16682 
16683         // display all the data in the ring
16684         for (i=first_line; i != next_line;
16685              i=(i+1)%MAX_PACKET_DATA_DISPLAY)
16686         {
16687           XmTextReplace(Display_data_text,last_char,last_char,
16688                         packet_data_string[i]);
16689           last_char=XmTextGetLastPosition(Display_data_text);
16690           pos=last_char;
16691           XtVaSetValues(Display_data_text,XmNcursorPosition,
16692                         pos,NULL);
16693         }
16694         // Now clear counters so they're always the number of lines to
16695         // add or characters to delete *since last display*
16696         nlinesadd=0;
16697         ncharsdel=0;
16698       }
16699       else     // there is stuff left over after we delete old stuff
16700       {
16701         if (ncharsdel)   // we have something to delete off the top
16702         {
16703           //fprintf(stderr,"  Must delete %d characters\n",ncharsdel);
16704           XmTextReplace(Display_data_text,0,ncharsdel,"");
16705           ncharsdel=0;
16706         }
16707         if (nlinesadd)   // and there's new stuff to add at end
16708         {
16709           //fprintf(stderr,"  Must add %d lines\n",nlinesadd);
16710           last_char=XmTextGetLastPosition(Display_data_text);
16711           for (i=(next_line+MAX_PACKET_DATA_DISPLAY
16712                   -nlinesadd)%MAX_PACKET_DATA_DISPLAY;
16713                i != next_line;
16714                i=(i+1)%MAX_PACKET_DATA_DISPLAY)
16715           {
16716             //fprintf(stderr,"     Adding data from line %d\n",i);
16717             XmTextReplace(Display_data_text,last_char,last_char,
16718                           packet_data_string[i]);
16719             last_char=XmTextGetLastPosition(Display_data_text);
16720             pos=last_char;
16721             XtVaSetValues(Display_data_text,XmNcursorPosition,
16722                           pos,NULL);
16723           }
16724           nlinesadd=0;
16725         }
16726       }
16727     }
16728   }
16729   redraw_on_new_packet_data=0;
16730 }
16731 
16732 
16733 
16734 
16735 
16736 //
16737 // Note that the length of "line" can be up to MAX_DEVICE_BUFFER,
16738 // which is currently set to 4096.
16739 //
16740 // data_port == -1 for x_spider port, normal interface number
16741 // otherwise.  -99 should give a "**" display, meaning all ports.
16742 //
packet_data_add(char * from,char * line,int data_port)16743 void packet_data_add(char *from, char *line, int data_port)
16744 {
16745   int offset;
16746   char prefix[3] = "";
16747   int local_tnc_interface = 0;
16748   int network_interface = 0;
16749 
16750 
16751   if (data_port == -1)    // x_spider port (server port)
16752   {
16753     xastir_snprintf(prefix,sizeof(prefix),"sp");
16754   }
16755   else if (data_port == -99)  // All ports, used for transmitting
16756   {
16757     xastir_snprintf(prefix,sizeof(prefix),"**");
16758   }
16759   else
16760   {
16761     xastir_snprintf(prefix,sizeof(prefix),"%2d",data_port);
16762   }
16763 
16764   offset=0;
16765   if (line[0]==(char)3)
16766   {
16767     offset=1;
16768   }
16769 
16770   // Check whether local or network interface
16771   if (is_local_interface(data_port) || data_port == -99)
16772   {
16773     local_tnc_interface++;
16774   }
16775 
16776   if (is_network_interface(data_port) || data_port == -1 || data_port == -99)
16777   {
16778     network_interface++;
16779   }
16780 
16781   // Compare Display_packet_data_type against the port type
16782   // associated with data_port to determine whether or not to
16783   // display it.
16784   //
16785   switch (Display_packet_data_type)
16786   {
16787 
16788     case 2:     // Display NET data only
16789       if (!network_interface)
16790       {
16791         return;  // Don't display it
16792       }
16793       break;
16794 
16795     case 1:     // Display TNC data only
16796       if (!local_tnc_interface)
16797       {
16798         return;  // Don't display it
16799       }
16800       break;
16801 
16802     case 0:     // Display both TNC and NET data
16803     default:
16804       break;
16805   }
16806 
16807   //    fprintf(stderr,"display:%d, port:%d\n",
16808   //        Display_packet_data_type,
16809   //        data_port);
16810 
16811   // Check the Capabilities toggle to see if we only want to show
16812   // Station Capability packets
16813   if (show_only_station_capabilities)
16814   {
16815     if (!strstr(line, ":<") // Not a capabilities response
16816         && !( strstr(line, my_callsign) && strstr(line, "?IGATE?") ) )
16817     {
16818       // Not a capabilities response and not my ?IGATE?
16819       // request, don't display the packet.
16820       return;
16821     }
16822   }
16823 
16824   // Check the "Mine Only" toggle to see if we only want to show
16825   // our own packets
16826   if (Display_packet_data_mine_only)
16827   {
16828     char short_call[MAX_CALLSIGN];
16829     char *p;
16830 
16831     memcpy(short_call, my_callsign, sizeof(short_call));
16832     short_call[sizeof(short_call)-1] = '\0';  // Terminate string
16833     if ( (p = index(short_call,'-')) )
16834     {
16835       *p = '\0';  // Terminate it
16836     }
16837 
16838     if (!strstr(line, short_call))
16839     {
16840       return;
16841     }
16842   }
16843 
16844   redraw_on_new_packet_data++;
16845 
16846   // Now save the packet in the history:
16847   xastir_snprintf(packet_data_string[next_line],MAX_LINE_SIZE,"%s:%s-> %s\n",
16848                   prefix,from,line+offset);
16849   next_line = (next_line+1)%MAX_PACKET_DATA_DISPLAY;
16850   nlinesadd++;
16851   if (first_line == -1)
16852   {
16853     first_line = 0;
16854   }
16855   else if (first_line == next_line)
16856   {
16857     ncharsdel += strlen(packet_data_string[first_line]);
16858     first_line = (first_line + 1) %MAX_PACKET_DATA_DISPLAY;
16859   }
16860 
16861 }
16862 
16863 
16864 
16865 
16866 
16867 /*
16868  *  Decode Mic-E encoded data
16869  */
decode_Mic_E(char * call_sign,char * path,char * info,char from,int port,int third_party)16870 int decode_Mic_E(char *call_sign,char *path,char *info,char from,int port,int third_party)
16871 {
16872   int  ii;
16873   int  offset;
16874   unsigned char s_b1;
16875   unsigned char s_b2;
16876   unsigned char s_b3;
16877   unsigned char s_b4;
16878   unsigned char s_b5;
16879   unsigned char s_b6;
16880   // unsigned char s_b7;
16881   int  north,west,long_offset;
16882   int  d,m,h;
16883   char temp[MAX_LINE_SIZE+1];     // Note: Must be big in case we get long concatenated packets
16884   char new_info[MAX_LINE_SIZE+1]; // Note: Must be big in case we get long concatenated packets
16885   int  course;
16886   int  speed;
16887   int  msg1,msg2,msg3,msg;
16888   int  info_size;
16889   long alt;
16890   int  msgtyp;
16891   char rig_type[10];
16892   int ok;
16893 
16894   // MIC-E Data Format   [APRS Reference, chapter 10]
16895 
16896   // todo:  error check
16897   //        drop wrong positions from receive errors...
16898   //        drop 0N/0E position (p.25)
16899 
16900   /* First 7 bytes of info[] contains the APRS data type ID,    */
16901   /* longitude, speed, course.                    */
16902   /* The 6-byte destination field of path[] contains latitude,    */
16903   /* N/S bit, E/W bit, longitude offset, message code.        */
16904   /*
16905 
16906     MIC-E Destination Field Format:
16907     -------------------------------
16908     Ar1DDDD0 Br1DDDD0 Cr1MMMM0 Nr1MMMM0 Lr1HHHH0 Wr1HHHH0 CrrSSID0
16909     D = Latitude Degrees.
16910     M = Latitude Minutes.
16911     H = Latitude Hundredths of Minutes.
16912     ABC = Message bits, complemented.
16913     N = N/S latitude bit (N=1).
16914     W = E/W longitude bit (W=1).
16915     L = 100's of longitude degrees (L=1 means add 100 degrees to longitude
16916     in the Info field).
16917     C = Command/Response flag (see AX.25 specification).
16918     r = reserved for future use (currently 0).
16919 
16920   */
16921   /****************************************************************************
16922    * I still don't handle:                                                     *
16923    *    Custom message bits                                                    *
16924    *    SSID special routing                                                   *
16925    *    Beta versions of the MIC-E (which use a slightly different format).    *
16926    *                                                                           *
16927    * DK7IN : lat/long with custom msg works, altitude/course/speed works       *
16928    *****************************************************************************/
16929 
16930   if (debug_level & 1)
16931   {
16932     fprintf(stderr,"decode_Mic_E:  FOUND MIC-E\n");
16933   }
16934 
16935   // Note that the first MIC-E character was not passed to us, so we're
16936   // starting just past it.
16937   // Check for valid symbol table character.  Should be '/' or '\'
16938   // or 0-9, A-Z.
16939   //
16940   if (        info[7] == '/'                          // Primary table
16941               ||  info[7] == '\\'                         // Alternate table
16942               || (info[7] >= '0' && info[7] <= '9')       // Overlay char
16943               || (info[7] >= 'A' && info[7] <= 'Z') )     // Overlay char
16944   {
16945 
16946     // We're good, keep going
16947 
16948   }
16949   else   // Symbol table or overlay char incorrect
16950   {
16951 
16952     if (info[6] == '/' || info[6] == '\\')      // Found it back one char in string
16953     {
16954       // Don't print out the full info string here because it
16955       // can contain unprintable characters.  In fact, we
16956       // should check the chars we do print out to make sure
16957       // they're printable, else print a space char.
16958       if (debug_level & 1)
16959       {
16960         fprintf(stderr,"decode_Mic_E: Symbol table (%c), symbol (%c) swapped or corrupted packet?  Call=%s, Path=%s\n",
16961                 ((info[7] > 0x1f) && (info[7] < 0x7f)) ? info[7] : ' ',
16962                 ((info[6] > 0x1f) && (info[6] < 0x7f)) ? info[6] : ' ',
16963                 call_sign,
16964                 path);
16965         fprintf(stderr,"Returned from data_add, invalid symbol table character: %c\n",info[7]);
16966       }
16967     }
16968 
16969     return(1);  // No good, not MIC-E format or corrupted packet.  Return 1
16970     // so that it won't get added to the database at all.
16971   }
16972 
16973   // Check for valid symbol.  Should be between '!' and '~' only.
16974   if (info[6] < '!' || info[6] > '~')
16975   {
16976     if (debug_level & 1)
16977     {
16978       fprintf(stderr,"Returned from data_add, invalid symbol\n");
16979     }
16980 
16981     return(1);  // No good, not MIC-E format or corrupted packet.  Return 1
16982     // so that it won't get added to the database at all.
16983   }
16984 
16985   // Check for minimum MIC-E size.
16986   if (strlen(info) < 8)
16987   {
16988     if (debug_level & 1)
16989     {
16990       fprintf(stderr,"Returned from data_add, packet too short\n");
16991     }
16992 
16993     return(1);  // No good, not MIC-E format or corrupted packet.  Return 1
16994     // so that it won't get added to the database at all.
16995   }
16996 
16997   // Check for 8-bit characters in the first eight slots.  Not
16998   // allowed per Mic-E chapter of the spec.
16999   for (ii = 0; ii < 8; ii++)
17000   {
17001     if ((unsigned char)info[ii] > 0x7f)
17002     {
17003       // 8-bit data was found in the lat/long/course/speed
17004       // portion.  Bad packet.  Drop it.
17005       //fprintf(stderr, "%s: 8-bits found in Mic-E packet initial portion. Dropping it.\n", call_sign);
17006       return(1);
17007     }
17008   }
17009 
17010   // Check whether we have more data.  If flag character is 0x1d
17011   // (8-bit telemetry flag) then don't do the 8-bit check below.
17012   if (strlen(info) > 8)
17013   {
17014 
17015     // Check for the 8-bit telemetry flag
17016     if ((unsigned char)info[8] == 0x1d)
17017     {
17018       // 8-bit telemetry found, skip the check loop below
17019     }
17020     else    // 8-bit telemetry flag was not found.  Check that
17021     {
17022       // we only have 7-bit characters through the rest of
17023       // the packet.
17024 
17025       for (ii = 8; ii < (int)strlen(info); ii++)
17026       {
17027 
17028         if ((unsigned char)info[ii] > 0x7f)
17029         {
17030           // 8-bit data was found.  Bad packet.  Drop it.
17031           //fprintf(stderr, "%s: 8-bits found in Mic-E packet final portion (not 8-bit telemetry). Dropping it.\n", call_sign);
17032           return(1);
17033         }
17034       }
17035     }
17036   }
17037 
17038   //fprintf(stderr,"Path1:%s\n",path);
17039 
17040   msg1 = (int)( ((unsigned char)path[0] & 0x40) >>4 );
17041   msg2 = (int)( ((unsigned char)path[1] & 0x40) >>5 );
17042   msg3 = (int)( ((unsigned char)path[2] & 0x40) >>6 );
17043   msg = msg1 | msg2 | msg3;   // We now have the complemented message number in one variable
17044   msg = msg ^ 0x07;           // And this is now the normal message number
17045   msgtyp = 0;                 // DK7IN: Std message, I have to add custom msg decoding
17046 
17047   //fprintf(stderr,"Msg: %d\n",msg);
17048 
17049   /* Snag the latitude from the destination field, Assume TAPR-2 */
17050   /* DK7IN: latitude now works with custom message */
17051   s_b1 = (unsigned char)( (path[0] & 0x0f) + (char)0x2f );
17052   //fprintf(stderr,"path0:%c\ts_b1:%c\n",path[0],s_b1);
17053   if (path[0] & 0x10)     // A-J
17054   {
17055     s_b1 += (unsigned char)1;
17056   }
17057 
17058   if (s_b1 > (unsigned char)0x39)        // K,L,Z
17059   {
17060     s_b1 = (unsigned char)0x20;
17061   }
17062   //fprintf(stderr,"s_b1:%c\n",s_b1);
17063 
17064   s_b2 = (unsigned char)( (path[1] & 0x0f) + (char)0x2f );
17065   //fprintf(stderr,"path1:%c\ts_b2:%c\n",path[1],s_b2);
17066   if (path[1] & 0x10)     // A-J
17067   {
17068     s_b2 += (unsigned char)1;
17069   }
17070 
17071   if (s_b2 > (unsigned char)0x39)        // K,L,Z
17072   {
17073     s_b2 = (unsigned char)0x20;
17074   }
17075   //fprintf(stderr,"s_b2:%c\n",s_b2);
17076 
17077   s_b3 = (unsigned char)( (path[2] & (char)0x0f) + (char)0x2f );
17078   //fprintf(stderr,"path2:%c\ts_b3:%c\n",path[2],s_b3);
17079   if (path[2] & 0x10)     // A-J
17080   {
17081     s_b3 += (unsigned char)1;
17082   }
17083 
17084   if (s_b3 > (unsigned char)0x39)        // K,L,Z
17085   {
17086     s_b3 = (unsigned char)0x20;
17087   }
17088   //fprintf(stderr,"s_b3:%c\n",s_b3);
17089 
17090   s_b4 = (unsigned char)( (path[3] & 0x0f) + (char)0x30 );
17091   //fprintf(stderr,"path3:%c\ts_b4:%c\n",path[3],s_b4);
17092   if (s_b4 > (unsigned char)0x39)        // L,Z
17093   {
17094     s_b4 = (unsigned char)0x20;
17095   }
17096   //fprintf(stderr,"s_b4:%c\n",s_b4);
17097 
17098   s_b5 = (unsigned char)( (path[4] & 0x0f) + (char)0x30 );
17099   //fprintf(stderr,"path4:%c\ts_b5:%c\n",path[4],s_b5);
17100   if (s_b5 > (unsigned char)0x39)        // L,Z
17101   {
17102     s_b5 = (unsigned char)0x20;
17103   }
17104   //fprintf(stderr,"s_b5:%c\n",s_b5);
17105 
17106   s_b6 = (unsigned char)( (path[5] & 0x0f) + (char)0x30 );
17107   //fprintf(stderr,"path5:%c\ts_b6:%c\n",path[5],s_b6);
17108   if (s_b6 > (unsigned char)0x39)        // L,Z
17109   {
17110     s_b6 = (unsigned char)0x20;
17111   }
17112   //fprintf(stderr,"s_b6:%c\n",s_b6);
17113 
17114   // s_b7 =  (unsigned char)path[6];        // SSID, not used here
17115   //fprintf(stderr,"path6:%c\ts_b7:%c\n",path[6],s_b7);
17116 
17117   //fprintf(stderr,"\n");
17118 
17119   // Special tests for 'L' due to position ambiguity deviances in
17120   // the APRS spec table.  'L' has the 0x40 bit set, but they
17121   // chose in the spec to have that represent position ambiguity
17122   // _without_ the North/West/Long Offset bit being set.  Yuk!
17123   // Please also note that the tapr.org Mic-E document (not the
17124   // APRS spec) has the state of the bit wrong in columns 2 and 3
17125   // of their table.  Reverse them.
17126   if (path[3] == 'L')
17127   {
17128     north = 0;
17129   }
17130   else
17131   {
17132     north = (int)((path[3] & 0x40) == (char)0x40);  // N/S Lat Indicator
17133   }
17134 
17135   if (path[4] == 'L')
17136   {
17137     long_offset = 0;
17138   }
17139   else
17140   {
17141     long_offset = (int)((path[4] & 0x40) == (char)0x40);  // Longitude Offset
17142   }
17143 
17144   if (path[5] == 'L')
17145   {
17146     west = 0;
17147   }
17148   else
17149   {
17150     west = (int)((path[5] & 0x40) == (char)0x40);  // W/E Long Indicator
17151   }
17152 
17153   //fprintf(stderr,"north:%c->%d\tlat:%c->%d\twest:%c->%d\n",path[3],north,path[4],long_offset,path[5],west);
17154 
17155   /* Put the latitude string into the temp variable */
17156   xastir_snprintf(temp, sizeof(temp), "%c%c%c%c.%c%c%c%c",s_b1,s_b2,s_b3,s_b4,s_b5,s_b6,
17157                   (north ? 'N': 'S'), info[7]);   // info[7] = symbol table
17158 
17159   /* Compute degrees longitude */
17160   xastir_snprintf(new_info,
17161                   sizeof(new_info),
17162                   "%s",
17163                   temp);
17164   d = (int) info[0]-28;
17165 
17166   if (long_offset)
17167   {
17168     d += 100;
17169   }
17170 
17171   if ((180<=d)&&(d<=189))  // ??
17172   {
17173     d -= 80;
17174   }
17175 
17176   if ((190<=d)&&(d<=199))  // ??
17177   {
17178     d -= 190;
17179   }
17180 
17181   /* Compute minutes longitude */
17182   m = (int) info[1]-28;
17183   if (m>=60)
17184   {
17185     m -= 60;
17186   }
17187 
17188   /* Compute hundredths of minutes longitude */
17189   h = (int) info[2]-28;
17190   /* Add the longitude string into the temp variable */
17191   xastir_snprintf(temp, sizeof(temp), "%03d%02d.%02d%c%c",d,m,h,(west ? 'W': 'E'), info[6]);
17192   strncat(new_info,
17193           temp,
17194           sizeof(new_info) - 1 - strlen(new_info));
17195 
17196   /* Compute speed in knots */
17197   speed = (int)( ( info[3] - (char)28 ) * (char)10 );
17198   speed += ( (int)( (info[4] - (char)28) / (char)10) );
17199   if (speed >= 800)
17200   {
17201     speed -= 800;  // in knots
17202   }
17203 
17204   /* Compute course */
17205   course = (int)( ( ( (info[4] - (char)28) % 10) * (char)100) + (info[5] - (char)28) );
17206   if (course >= 400)
17207   {
17208     course -= 400;
17209   }
17210 
17211   /*  ???
17212       fprintf(stderr,"info[4]-28 mod 10 - 4 = %d\n",( ( (int)info[4]) - 28) % 10 - 4);
17213       fprintf(stderr,"info[5]-28 = %d\n", ( (int)info[5]) - 28 );
17214   */
17215   xastir_snprintf(temp, sizeof(temp), "%03d/%03d",course,speed);
17216   strncat(new_info,
17217           temp,
17218           sizeof(new_info) - 1 - strlen(new_info));
17219   offset = 8;   // start of rest of info
17220 
17221   /* search for rig type in Mic-E data */
17222   rig_type[0] = '\0';
17223   if (info[offset] != '\0' && (info[offset] == '>' || info[offset] == ']'))
17224   {
17225     /* detected type code:     > TH-D7    ] TM-D700 */
17226     if (info[offset] == '>')
17227       xastir_snprintf(rig_type,
17228                       sizeof(rig_type),
17229                       " TH-D7");
17230     else
17231       xastir_snprintf(rig_type,
17232                       sizeof(rig_type),
17233                       " TM-D700");
17234 
17235     offset++;
17236   }
17237 
17238   info_size = (int)strlen(info);
17239   /* search for compressed altitude in Mic-E data */  // {
17240   if (info_size >= offset+4 && info[offset+3] == '}')    // {
17241   {
17242     /* detected altitude  ___} */
17243     alt = ((((long)info[offset] - (long)33) * (long)91 +(long)info[offset+1] - (long)33) * (long)91
17244            + (long)info[offset+2] - (long)33) - 10000;  // altitude in meters
17245     alt /= 0.3048;                                // altitude in feet, as in normal APRS
17246 
17247     //32808 is -10000 meters, or 10 km (deepest ocean), which is as low as a MIC-E
17248     //packet may go.  Upper limit is mostly a guess.
17249     if ( (alt > 500000) || (alt < -32809) )    // Altitude is whacko.  Skip it.
17250     {
17251       if (debug_level & 1)
17252       {
17253         fprintf(stderr,"decode_Mic_E:  Altitude is whacko:  %ld feet, skipping altitude...\n", alt);
17254       }
17255       offset += 4;
17256     }
17257     else    // Altitude is ok
17258     {
17259       xastir_snprintf(temp, sizeof(temp), " /A=%06ld",alt);
17260       offset += 4;
17261       strncat(new_info,
17262               temp,
17263               sizeof(new_info) - 1 - strlen(new_info));
17264     }
17265   }
17266 
17267   /* start of comment */
17268   if (strlen(rig_type) > 0)
17269   {
17270     xastir_snprintf(temp, sizeof(temp), "%s",rig_type);
17271     strncat(new_info,
17272             temp,
17273             sizeof(new_info) - 1 - strlen(new_info));
17274   }
17275 
17276   strncat(new_info,
17277           " Mic-E ",
17278           sizeof(new_info) - 1 - strlen(new_info));
17279   if (msgtyp == 0)
17280   {
17281     switch (msg)
17282     {
17283       case 1:
17284         strncat(new_info,
17285                 "Enroute",
17286                 sizeof(new_info) - 1 - strlen(new_info));
17287         break;
17288 
17289       case 2:
17290         strncat(new_info,
17291                 "In Service",
17292                 sizeof(new_info) - 1 - strlen(new_info));
17293         break;
17294 
17295       case 3:
17296         strncat(new_info,
17297                 "Returning",
17298                 sizeof(new_info) - 1 - strlen(new_info));
17299         break;
17300 
17301       case 4:
17302         strncat(new_info,
17303                 "Committed",
17304                 sizeof(new_info) - 1 - strlen(new_info));
17305         break;
17306 
17307       case 5:
17308         strncat(new_info,
17309                 "Special",
17310                 sizeof(new_info) - 1 - strlen(new_info));
17311         break;
17312 
17313       case 6:
17314         strncat(new_info,
17315                 "Priority",
17316                 sizeof(new_info) - 1 - strlen(new_info));
17317         break;
17318 
17319       case 7:
17320         strncat(new_info,
17321                 "Emergency",
17322                 sizeof(new_info) - 1 - strlen(new_info));
17323 
17324         // Do a popup to alert the operator to this
17325         // condition.  Make sure we haven't popped up an
17326         // emergency message for this station within the
17327         // last 30 minutes.  If we pop these up constantly
17328         // it gets quite annoying.
17329         // EMERGENCY
17330 
17331         if (emergency_distance_check)
17332         {
17333           double distance;
17334           char course_deg[5];
17335 
17336 
17337           distance = distance_from_my_station(call_sign, course_deg);
17338 
17339           // Because of the distance check we have to receive a valid position
17340           // from the station BEFORE we process the EMERGENCY portion and
17341           // check distance, doing the popups.  We need to figure out a way to
17342           // throw the packet back into the queue if it was an emergency
17343           // packet so that we process these packets twice each.  That way
17344           // only one packet from the emergency station is required to
17345           // generate the popups.
17346 
17347           if (distance == 0.0)
17348           {
17349             process_emergency_packet_again++;
17350           }
17351 
17352           // Check whether the station is near enough to
17353           // us to require that we alert on the packet.
17354           //
17355           // This may be slightly controversial, but if we
17356           // don't know WHERE a station is, we can't help
17357           // much in an emergency, can we?  The
17358           // zero-distance check helps in the case where
17359           // we haven't yet or never get a position packet
17360           // for a station.  As soon as we have a position
17361           // and it is within a reasonable range, we do
17362           // our emergency popups.
17363           //
17364           if ( distance != 0.0 && (float)distance <= emergency_range )
17365           {
17366 
17367             if ( (strncmp(call_sign, last_emergency_callsign, strlen(call_sign)) != 0)
17368                  || ((last_emergency_time + 60*30) < sec_now()) )
17369             {
17370 
17371               char temp[50];
17372               char temp2[150];
17373               char temp3[300];
17374               char timestring[101];
17375 
17376               // Callsign is different or enough time has
17377               // passed
17378 
17379               last_emergency_time = sec_now();
17380               xastir_snprintf(last_emergency_callsign,
17381                               sizeof(last_emergency_callsign),
17382                               "%s",
17383                               call_sign);
17384 
17385               // Bring up the Find Station dialog so that the
17386               // operator can go to the location quickly
17387               xastir_snprintf(locate_station_call,
17388                               sizeof(locate_station_call),
17389                               "%s",
17390                               call_sign);
17391 
17392               Locate_station( (Widget)NULL, (XtPointer)NULL, (XtPointer)1 );
17393 
17394               // Bring up another dialog with the
17395               // callsign plus distance/bearing to the
17396               // station.
17397               xastir_snprintf(temp,
17398                               sizeof(temp),
17399                               "%0.1f",
17400                               distance);
17401               xastir_snprintf(temp2,
17402                               sizeof(temp2),
17403                               langcode("WPUPSTI022"),
17404                               temp,
17405                               course_deg);
17406               get_timestamp(timestring);
17407               xastir_snprintf(temp3,
17408                               sizeof(temp3),
17409                               "%s  %s",
17410                               timestring,
17411                               temp2);
17412               popup_message_always(call_sign, temp3);
17413             }
17414           }
17415         }
17416         break;
17417 
17418       default:
17419         strncat(new_info,
17420                 "Off Duty",
17421                 sizeof(new_info) - 1 - strlen(new_info));
17422     }
17423   }
17424   else
17425   {
17426     xastir_snprintf(temp, sizeof(temp), "Custom%d",msg);
17427     strncat(new_info,
17428             temp,
17429             sizeof(new_info) - 1 - strlen(new_info));
17430   }
17431 
17432   if (info[offset] != '\0')
17433   {
17434     /* Append the rest of the message to the expanded MIC-E message */
17435     for (ii=offset; ii<info_size; ii++)
17436     {
17437       temp[ii-offset] = info[ii];
17438     }
17439 
17440     temp[info_size-offset] = '\0';
17441     strncat(new_info,
17442             " ",
17443             sizeof(new_info) - 1 - strlen(new_info));
17444     strncat(new_info,
17445             temp,
17446             sizeof(new_info) - 1 - strlen(new_info));
17447   }
17448 
17449   if (debug_level & 1)
17450   {
17451     fprintf(stderr,"decode_Mic_E:  Done decoding MIC-E\n");
17452     fprintf(stderr,"APRS_MICE, %s, %s, %s, %d, %d, NULL, %d\n",call_sign,path,new_info,from,port,third_party);
17453     // type:        APRS_MICE,
17454     // callsign:    N0EST-9,
17455     // path:        TTPQ9P,W0MXW-1,WIDE,N0QK-1*,
17456     // new_info:    4401.90N/09228.79W>278/007 /A=-05685 TM-D700 Mic-E Off Duty N0EST  ,
17457     // from:        70,
17458     // port:        -1,
17459     //              NULL,
17460     // third_party: 0
17461   }
17462 
17463   // We don't transmit Mic-E protocol from Xastir, so we know it's
17464   // not our station's packets or our object/item packets,
17465   // therefore the last two parameters here are both zero.
17466   //
17467   ok = data_add(APRS_MICE,call_sign,path,new_info,from,port,NULL,third_party, 0, 0);
17468 
17469   if (debug_level & 1)
17470   {
17471     fprintf(stderr,"Returned from data_add, end of function\n");
17472   }
17473 
17474   return(ok);
17475 }   // End of decode_Mic_E()
17476 
17477 
17478 
17479 
17480 
17481 /*
17482  *  Directed Station Query (query to my station)   [APRS Reference, chapter 15]
17483  */
process_directed_query(char * call,char * path,char * message,char from)17484 int process_directed_query(char *call,char *path,char *message,char from)
17485 {
17486   DataRow *p_station;
17487   char from_call[MAX_CALLSIGN+1];
17488   char temp[100];
17489   int ok = 0;
17490 
17491 
17492   if (debug_level & 1)
17493   {
17494     fprintf(stderr,"process_directed_query: %s\n",message);
17495   }
17496 
17497   // Check for proper usage of the APRSD query
17498   if (!ok && strncmp(message,"APRSD",5) == 0 && from != 'F')    // stations heard direct
17499   {
17500     pad_callsign(from_call,call);
17501     xastir_snprintf(temp, sizeof(temp), ":%s:Directs=",from_call);
17502     p_station = n_first;
17503     while (p_station != NULL)
17504     {
17505       if ((p_station->flag & ST_ACTIVE) != 0)         // ignore deleted objects
17506       {
17507         if ( ((p_station->flag & ST_VIATNC) != 0)   // test "via TNC" flag
17508              && ((p_station->flag & ST_DIRECT) != 0) // And "direct" flag
17509              && sec_now() < (p_station->direct_heard + st_direct_timeout) // Within the last hour
17510              //                     && !is_my_call(p_station->call_sign,1) ) { // and not me (checks SSID too)
17511              && !(is_my_station(p_station)) )   // and not me (checks SSID too)
17512         {
17513           if (strlen(temp)+strlen(p_station->call_sign) < 65)
17514           {
17515             strncat(temp,
17516                     " ",
17517                     sizeof(temp) - 1 - strlen(temp));
17518             strncat(temp,
17519                     p_station->call_sign,
17520                     sizeof(temp) - 1 - strlen(temp));
17521           }
17522           else
17523           {
17524 
17525             // Nice to return via the reverse path here?  No!  Better to use the
17526             // default paths instead of a calculated reverse path.
17527 
17528             transmit_message_data(call,temp,NULL);
17529             xastir_snprintf(temp, sizeof(temp),
17530                             ":%s:Directs=",from_call);
17531             strncat(temp,
17532                     " ",
17533                     sizeof(temp) - 1 - strlen(temp));
17534             strncat(temp,
17535                     p_station->call_sign,
17536                     sizeof(temp) - 1 - strlen(temp));
17537           }
17538         }
17539       }
17540       p_station = p_station->n_next;
17541     }
17542 
17543     // Nice to return via the reverse path here?  No!  Better to use the
17544     // default paths instead of a calculated reverse path.
17545 
17546     transmit_message_data(call,temp,NULL);
17547     ok = 1;
17548   }
17549   // Check for illegal case for the APRSD query
17550   if (!ok && strncasecmp(message,"APRSD",5) == 0 && from != 'F')    // stations heard direct
17551   {
17552     fprintf(stderr,
17553             "%s just queried us with an illegal query: %s\n",
17554             call,
17555             message),
17556             fprintf(stderr,
17557                     "Consider sending a message, asking them to follow the spec\n");
17558     ok = 1;
17559   }
17560 
17561 
17562   // NOT IMPLEMENTED YET
17563   // Check for proper usage of the APRSH query
17564   if (!ok && strncmp(message,"APRSH",5)==0)
17565   {
17566     ok = 1;
17567   }
17568   // Check for illegal case for the APRSH query
17569   if (!ok && strncasecmp(message,"APRSH",5)==0)
17570   {
17571     //        fprintf(stderr,
17572     //            "%s just queried us with an illegal query: %s\n",
17573     //            call,
17574     //            message),
17575     //        fprintf(stderr,
17576     //            "Consider sending a message, asking them to follow the spec\n");
17577     ok = 1;
17578   }
17579 
17580 
17581   // NOT IMPLEMENTED YET
17582   // Check for proper usage of the APRSM query
17583   if (!ok && strncmp(message,"APRSM",5)==0)
17584   {
17585     ok = 1;
17586   }
17587   // Check for illegal case for the APRSM query
17588   if (!ok && strncasecmp(message,"APRSM",5)==0)
17589   {
17590     //        fprintf(stderr,
17591     //            "%s just queried us with an illegal query: %s\n",
17592     //            call,
17593     //            message),
17594     //        fprintf(stderr,
17595     //            "Consider sending a message, asking them to follow the spec\n");
17596     ok = 1;
17597   }
17598 
17599 
17600   // NOT IMPLEMENTED YET
17601   // Check for proper usage of the APRSO query
17602   if (!ok && strncmp(message,"APRSO",5)==0)
17603   {
17604     ok = 1;
17605   }
17606   // Check for illegal case for the APRSO query
17607   if (!ok && strncasecmp(message,"APRSO",5)==0)
17608   {
17609     //        fprintf(stderr,
17610     //            "%s just queried us with an illegal query: %s\n",
17611     //            call,
17612     //            message),
17613     //        fprintf(stderr,
17614     //            "Consider sending a message, asking them to follow the spec\n");
17615     ok = 1;
17616   }
17617 
17618 
17619   // Check for proper usage of the APRSP query
17620   if (!ok && strncmp(message,"APRSP",5) == 0 && from != 'F')
17621   {
17622     transmit_now = 1;       //send position
17623     ok = 1;
17624   }
17625   // Check for illegal case for the APRSP query
17626   if (!ok && strncasecmp(message,"APRSP",5) == 0 && from != 'F')
17627   {
17628     fprintf(stderr,
17629             "%s just queried us with an illegal query: %s\n",
17630             call,
17631             message),
17632             fprintf(stderr,
17633                     "Consider sending a message, asking them to follow the spec\n");
17634     ok = 1;
17635   }
17636 
17637 
17638   // NOT IMPLEMENTED YET
17639   // Check for proper usage of the APRSS query
17640   if (!ok && strncmp(message,"APRSS",5)==0)
17641   {
17642     ok = 1;
17643   }
17644   // Check for illegal case for the APRSS query
17645   if (!ok && strncasecmp(message,"APRSS",5)==0)
17646   {
17647     //        fprintf(stderr,
17648     //            "%s just queried us with an illegal query: %s\n",
17649     //            call,
17650     //            message),
17651     //        fprintf(stderr,
17652     //            "Consider sending a message, asking them to follow the spec\n");
17653     ok = 1;
17654   }
17655 
17656 
17657   // Check for proper usage of the APRST/PING? queries
17658   if (!ok && (strncmp(message,"APRST",5)==0
17659               ||  strncmp(message,"PING?",5)==0) && from != 'F')
17660   {
17661     pad_callsign(from_call,call);
17662     xastir_snprintf(temp, sizeof(temp), ":%s:PATH= %s>%s",from_call,call,path);    // correct format ?????
17663 
17664     // Nice to return via the reverse path here?  No!  Better to use the
17665     // default paths instead of a calculated reverse path.
17666 
17667     transmit_message_data(call,temp,NULL);
17668     ok = 1;
17669   }
17670 
17671 
17672   // Check for illegal case for the APRST/PING? queries
17673   if (!ok && (strncasecmp(message,"APRST",5)==0
17674               ||  strncasecmp(message,"PING?",5)==0) && from != 'F')
17675   {
17676     fprintf(stderr,
17677             "%s just queried us with an illegal query: %s\n",
17678             call,
17679             message),
17680             fprintf(stderr,
17681                     "Consider sending a message, asking them to follow the spec\n");
17682     ok = 1;
17683   }
17684 
17685 
17686   // Check for proper usage of the VER query (either case?)
17687   if (!ok && strncasecmp("VER",message,3) == 0 && from != 'F')   // not in Reference !???
17688   {
17689     pad_callsign(from_call,call);
17690     xastir_snprintf(temp, sizeof(temp), ":%s:%s",from_call,VERSIONLABEL);
17691 
17692     // Nice to return via the reverse path here?  No!  Better to use the
17693     // default paths instead of a calculated reverse path.
17694 
17695     transmit_message_data(call,temp,NULL);
17696     if (debug_level & 1)
17697     {
17698       fprintf(stderr,"Sent to %s:%s\n",call,temp);
17699     }
17700     ok = 1;
17701   }
17702 
17703   return(ok);
17704 }
17705 
17706 
17707 
17708 
17709 
17710 /*
17711  *  Station Capabilities, Queries and Responses      [APRS Reference, chapter 15]
17712  */
17713 //
17714 // According to Bob Bruninga we should wait a random time between 0
17715 // and 120 seconds before responding to a general query.  We use the
17716 // delayed-ack mechanism to add this randomness.
17717 //
17718 // NOTE:  We may end up sending these to RF when the query came in
17719 // over the internet.  We should check that.
17720 //
process_query(char * call_sign,char * UNUSED (path),char * message,char from,int port,int UNUSED (third_party))17721 int process_query( char *call_sign, char * UNUSED(path), char *message,char from,int port, int UNUSED(third_party) )
17722 {
17723   char temp[100];
17724   int ok = 0;
17725   float randomize;
17726 
17727 
17728   // Generate a random number between 0.0 and 1.0
17729   randomize = rand() / (float)RAND_MAX;
17730 
17731   // Convert to between 0 and 120 seconds
17732   randomize = randomize * 120.0;
17733   //fprintf(stderr,"Randomize:%f\n", randomize);
17734 
17735 
17736 
17737   // Check for proper usage of the ?APRS? query
17738   //
17739   // NOTE:  We need to add support in here for the radius circle as
17740   // listed in the spec for general queries.  Right now we respond to
17741   // all queries, whether we're inside the circle or not.  Spec says
17742   // this:
17743   //
17744   // ?Query?Lat,Long,Radius
17745   // 1  n  1 n 1 n  1  4 Bytes
17746   //
17747   // i.e. ?APRS? 34.02,-117.15,0200
17748   //
17749   // Note leading space in latitude as its value is positive.
17750   // Lat/long are floating point degrees.  N/E are positive, indicated
17751   // by a leading space.  S/W are negative.  Radius is in miles
17752   // expressed as a fixed 4-digit number in whole miles.  All stations
17753   // inside the specified circle should respond with a position report
17754   // and a status report.
17755   //
17756   if (!ok && strncmp(message,"APRS?",5)==0)
17757   {
17758     //
17759     // Initiate a delayed transmit of our own posit.
17760     // UpdateTime() uses posit_next_time to decide when to
17761     // transmit, so we'll just muck with that.
17762     //
17763     if ( posit_next_time - sec_now() < randomize )
17764     {
17765       // Skip setting it, as we'll transmit soon anyway
17766     }
17767     else
17768     {
17769       posit_next_time = (size_t)(sec_now() + randomize);
17770     }
17771     ok = 1;
17772   }
17773   // Check for illegal case for the ?APRS? query
17774   if (!ok && strncasecmp(message,"APRS?",5)==0)
17775   {
17776     ok = 1;
17777     //        fprintf(stderr,
17778     //            "%s just queried us with an illegal query: %s\n",
17779     //            call_sign,
17780     //            message),
17781     //        fprintf(stderr,
17782     //            "Consider sending a message, asking them to follow the spec\n");
17783   }
17784 
17785 
17786 
17787   // Check for proper usage of the ?IGATE? query
17788   if (!ok
17789       && strncmp(message,"IGATE?",6)==0
17790       && port != -1)      // Not from a log file
17791   {
17792 
17793     if (operate_as_an_igate && from != 'F')
17794     {
17795       xastir_snprintf(temp, sizeof(temp), "<IGATE,MSG_CNT=%d,LOC_CNT=%d",(int)igate_msgs_tx,stations_types(3));
17796 
17797       // OLD:
17798       //output_my_data(temp,port,0,0,0,NULL);    // Not igating
17799       // NEW:
17800       transmit_message_data_delayed("ALL", temp, NULL, (time_t)(sec_now() + randomize) );
17801     }
17802     ok = 1;
17803   }
17804   // Check for illegal case for the ?IGATE? query
17805   if (!ok
17806       && strncasecmp(message,"IGATE?",6)==0
17807       && port != -1)      // Not from a log file
17808   {
17809 
17810     if (operate_as_an_igate && from != 'F')
17811     {
17812       fprintf(stderr,
17813               "%s just queried us with an illegal query: %s\n",
17814               call_sign,
17815               message),
17816               fprintf(stderr,
17817                       "Consider sending a message, asking them to follow the spec\n");
17818     }
17819     ok = 1;
17820   }
17821 
17822 
17823 
17824   // Check for proper usage of the ?WX? query
17825   if (!ok && strncmp(message,"WX?",3)==0)
17826   {
17827 
17828     // NOT IMPLEMENTED YET
17829 
17830     // Here we should check whether we are a weather station, and if so,
17831     // send out a delayed posit/weather string.
17832     //        if (we're a weather_station) {
17833     //            //
17834     //            // Initiate a delayed transmit of our own posit.
17835     //            // UpdateTime() uses posit_next_time to decide when to
17836     //            // transmit, so we'll just muck with that.
17837     //            //
17838     //            if ( posit_next_time - sec_now() < randomize ) {
17839     //                // Skip setting it, as we'll transmit soon anyway
17840     //            }
17841     //            else {
17842     //                posit_next_time = (size_t)(sec_now() + randomize);
17843     //            }
17844     //        }
17845     ok = 1;
17846   }
17847   // Check for illegal case for the ?WX? query
17848   if (!ok && strncasecmp(message,"WX?",3)==0)
17849   {
17850     ok = 1;
17851     //        fprintf(stderr,
17852     //            "%s just queried us with an illegal query: %s\n",
17853     //            call_sign,
17854     //            message),
17855     //        fprintf(stderr,
17856     //            "Consider sending a message, asking them to follow the spec\n");
17857   }
17858 
17859   return(ok);
17860 }
17861 
17862 
17863 
17864 
17865 
17866 /*
17867  *  Status Reports                              [APRS Reference, chapter 16]
17868  */
process_status(char * UNUSED (call_sign),char * UNUSED (path),char * UNUSED (message),char UNUSED (from),int UNUSED (port),int UNUSED (third_party))17869 int process_status( char * UNUSED(call_sign), char * UNUSED(path), char * UNUSED(message), char UNUSED(from), int UNUSED(port), int UNUSED(third_party) )
17870 {
17871 
17872   //    popup_message(langcode("POPEM00018"),message);  // What is it ???
17873   return(1);
17874 }
17875 
17876 
17877 
17878 
17879 
17880 /*
17881  *  shorten_path
17882  *
17883  * What to do with this one?
17884  *      APW250,TCPIP*,ZZ2RMV-5*
17885  * We currently convert it to:
17886  *      APW250
17887  * It's a packet that went across the 'net, then to RF, then back to
17888  * the 'net.  We should probably drop it altogether?
17889  *
17890  *  Gets rid of unused digipeater fields (after the asterisk) and the
17891  *  TCPIP field if it exists.  Used for creating the third-party
17892  *  headers for igating purposes.  Note that for TRACEn-N and WIDEn-N
17893  *  digi's, it's impossible to tell via the '*' character whether that
17894  *  part of the path was used, but we can tell by the difference of
17895  *  'n' and 'N'.  If they're different, then that part of the path was
17896  *  used.  If it has counted down to just a TRACE or a WIDE (or TRACE7
17897  *  or WIDE5), then it should have a '*' after it like normal.
17898  */
shorten_path(char * path,char * short_path,int short_path_size)17899 void shorten_path( char *path, char *short_path, int short_path_size )
17900 {
17901   int i,j,found_trace_wide,found_asterisk;
17902   char *ptr;
17903 
17904 
17905   if ( (path != NULL) && (strlen(path) >= 1) )
17906   {
17907 
17908     xastir_snprintf(short_path,
17909                     short_path_size,
17910                     "%s",
17911                     path);
17912 
17913     // Terminate the path at the end of the last used digipeater
17914     // This is trickier than it seems due to WIDEn-N and TRACEn-N
17915     // digipeaters.
17916 
17917     // Take a run through the entire path string looking for unused
17918     // TRACE/WIDE paths.
17919     for ( i = (strlen(path)-1); i >= 0; i-- )   // Count backwards
17920     {
17921       // If we find ",WIDE3-3" or ",TRACE7-7" (numbers match),
17922       // jam '\0' in at the comma.  These are unused digipeaters.
17923       if (   (strstr(&short_path[i],",WIDE7-7") != NULL)
17924              || (strstr(&short_path[i],",WIDE6-6") != NULL)
17925              || (strstr(&short_path[i],",WIDE5-5") != NULL)
17926              || (strstr(&short_path[i],",WIDE4-4") != NULL)
17927              || (strstr(&short_path[i],",WIDE3-3") != NULL)
17928              || (strstr(&short_path[i],",WIDE2-2") != NULL)
17929              || (strstr(&short_path[i],",WIDE1-1") != NULL)
17930              || (strstr(&short_path[i],",TRACE7-7") != NULL)
17931              || (strstr(&short_path[i],",TRACE6-6") != NULL)
17932              || (strstr(&short_path[i],",TRACE5-5") != NULL)
17933              || (strstr(&short_path[i],",TRACE4-4") != NULL)
17934              || (strstr(&short_path[i],",TRACE3-3") != NULL)
17935              || (strstr(&short_path[i],",TRACE2-2") != NULL)
17936              || (strstr(&short_path[i],",TRACE1-1") != NULL) )
17937       {
17938         short_path[i] = '\0';
17939       }
17940     }
17941 
17942 
17943     // Take another run through short_string looking for used
17944     // TRACE/WIDE paths.  Also look for '*' characters and flag
17945     // if we see any.  If no '*' found, but a used TRACE/WIDE
17946     // path found, chop the path after the used TRACE/WIDE.  This
17947     // is to modify paths like this:
17948     //     APRS,PY1AYH-15*,RELAY,WIDE3-2,PY1EU-1
17949     // to this:
17950     //     APRS,PY1AYH-15*,RELAY,WIDE3-2
17951     j = 0;
17952     found_trace_wide = 0;
17953     found_asterisk = 0;
17954     for ( i = (strlen(short_path)-1); i >= 0; i-- )   // Count backwards
17955     {
17956 
17957       if (short_path[i] == '*')
17958       {
17959         found_asterisk++;
17960       }
17961 
17962       // Search for TRACEn/WIDEn.  If found (N!=n is guaranteed
17963       // by the previous loop) set the lower increment for the next
17964       // loop just past the last TRACEn/WIDEn found.  The used part
17965       // of the TRACEn/WIDEn will still remain in our shorter path.
17966       if (   (strstr(&short_path[i],"WIDE7") != NULL)
17967              || (strstr(&short_path[i],"WIDE6") != NULL)
17968              || (strstr(&short_path[i],"WIDE5") != NULL)
17969              || (strstr(&short_path[i],"WIDE4") != NULL)
17970              || (strstr(&short_path[i],"WIDE3") != NULL)
17971              || (strstr(&short_path[i],"WIDE2") != NULL)
17972              || (strstr(&short_path[i],"WIDE1") != NULL)
17973              || (strstr(&short_path[i],"TRACE7") != NULL)
17974              || (strstr(&short_path[i],"TRACE6") != NULL)
17975              || (strstr(&short_path[i],"TRACE5") != NULL)
17976              || (strstr(&short_path[i],"TRACE4") != NULL)
17977              || (strstr(&short_path[i],"TRACE3") != NULL)
17978              || (strstr(&short_path[i],"TRACE2") != NULL)
17979              || (strstr(&short_path[i],"TRACE1") != NULL) )
17980       {
17981         j = i;
17982         found_trace_wide++;
17983         break;  // We only want to find the right-most one.
17984         // We've found a used digipeater!
17985       }
17986     }
17987 
17988 
17989     // Chop off any unused digi's after a used TRACEn/WIDEn
17990     if (!found_asterisk && found_trace_wide)
17991     {
17992       for ( i = (strlen(short_path)-1); i >= j; i-- )   // Count backwards
17993       {
17994         if (short_path[i] == ',')
17995         {
17996           short_path[i] = '\0';   // Terminate the string
17997         }
17998       }
17999     }
18000 
18001 
18002     // At this point, if we found a TRACEn or WIDEn, the "j"
18003     // variable will be non-zero.  If not then it'll be zero and
18004     // we'll run completely through the shorter path converting
18005     // '*' characters to '\0'.
18006     found_asterisk = 0;
18007     for ( i = (strlen(short_path)-1); i >= j; i-- )   // Count backwards
18008     {
18009       if (short_path[i] == '*')
18010       {
18011         short_path[i] = '\0';   // Terminate the string
18012         found_asterisk++;
18013       }
18014     }
18015 
18016 
18017     // Check for TCPIP or TCPXX as the last digipeater.  If present,
18018     // remove them.  TCPXX means that the packet came from an unregistered
18019     // user, and those packets will be rejected in igate.c before they're
18020     // sent to RF anyway.  igate.c will check for its presence in path,
18021     // not in short_path, so we're ok here to get rid of it in short_path.
18022     if (strlen(short_path) >= 5)    // Get rid of "TCPIP" & "TCPXX"
18023     {
18024 
18025       ptr = &short_path[strlen(short_path) - 5];
18026       if (   (strcasecmp(ptr,"TCPIP") == 0)
18027              || (strcasecmp(ptr,"TCPXX") == 0) )
18028       {
18029         *ptr = '\0';
18030       }
18031       if ( (strlen(short_path) >= 1)  // Get rid of possible ending comma
18032            && (short_path[strlen(short_path) - 1] == ',') )
18033       {
18034         short_path[strlen(short_path) - 1] = '\0';
18035       }
18036     }
18037 
18038 
18039     // We might have a string with zero used digipeaters.  In this case
18040     // we will have no '*' characters and no WIDEn-N/TRACEn-N digis.
18041     // Get rid of everything except the destination call.  These packets
18042     // must have been heard directly by an igate station.
18043     if (!found_trace_wide && !found_asterisk)
18044     {
18045       for ( i = (strlen(short_path)-1); i >= j; i-- )   // Count backwards
18046       {
18047         if (short_path[i] == ',')
18048         {
18049           short_path[i] = '\0';   // Terminate the string
18050         }
18051       }
18052     }
18053 
18054 
18055     // The final step:  Remove any asterisks in the path.
18056     // We'll insert our own on the way out to RF again.
18057     for ( i = 0; i < (int)(strlen(short_path) - 1); i++ )
18058     {
18059       if (short_path[i] == '*')
18060       {
18061         for (j = i; j <= (int)(strlen(short_path) - 1); j++ )
18062         {
18063           short_path[j] = short_path[j+1];  // Shift left by one char
18064         }
18065       }
18066     }
18067 
18068 
18069   }
18070   else
18071   {
18072     short_path[0] = '\0';   // We were passed an empty string or a NULL.
18073   }
18074 
18075   if (debug_level & 1)
18076   {
18077     fprintf(stderr,"%s\n",path);
18078     fprintf(stderr,"%s\n\n",short_path);
18079   }
18080 }
18081 
18082 
18083 
18084 
18085 
18086 // TODO:
18087 // *) Use the valid_call(call) function here?
18088 // *) Add a "Tactical Call Disable" togglebutton.  Default =
18089 //    disabled.
18090 // *) Send out TAC assignments as they are created via an APRS
18091 //    message?
18092 // *) Add "Send All Tactical Calls" menu entry.  Another entry to
18093 //    send them out repetitively?
18094 // *) Create a public/private distinction for TAC calls?
18095 // *) Add public/private toggle to the Tactical Callsign box, and
18096 //    have it send an APRS Message if public when changed?
18097 // *) Add a method to list the public/private TAC calls we currently
18098 //    have assigned.
18099 // *) Create an easier method to remove one or more TAC calls?
18100 //    Currently we have to send a blank assignment ("we7u-12=").
18101 // *) Log TAC calls and date/time for each assignment, including
18102 //    NULL assignments.
18103 //
18104 // From Bob:
18105 // *) Range filter - won't accept tactical assignments without a
18106 //    position within X miles of source.
18107 // *) Change filter - won't accept changes from others for locally
18108 //    created tac assignment.  Kind of implies two tables - local
18109 //    and remote Button/menu item to send local, or send all - each
18110 //    a manual operation, as we discussed.
18111 // *) Perhaps repeat messages fewer times if sent to TACTICAL than
18112 //    for a normal message?  This is so that more than one
18113 //    controller can manipulate them without having to wait for the
18114 //    timeout of the first message.
18115 //
18116 //
fill_in_tactical_callsign(char * call,char * tactical_call)18117 int fill_in_tactical_callsign(char *call, char *tactical_call)
18118 {
18119   DataRow *p_station;
18120 
18121 
18122   // Convert callsign to upper-case
18123   (void)to_upper(call);
18124 
18125   // Get rid of white space on either end
18126   (void)remove_leading_spaces(call);
18127   (void)remove_trailing_spaces(call);
18128   (void)remove_leading_spaces(tactical_call);
18129   (void)remove_trailing_spaces(tactical_call);
18130 
18131   // Find the station record.
18132   if (!search_station_name(&p_station, call, 1))
18133   {
18134     // Station not found.
18135 
18136     // Add the TAC call to the tactical hash for future
18137     // application to a callsign via the log_tactical_call()
18138     // function call below...
18139   }
18140 
18141   else    // Found it!  Assign the new tactical call.  Some code
18142   {
18143     // here borrowed from db.c:Change_tactical_change_data()
18144 
18145     // Check for blank incoming tactical call.
18146     if (tactical_call[0] == '\0')
18147     {
18148       // Blank tactical call string.  Free space and null
18149       // pointer.
18150       free(p_station->tactical_call_sign);
18151       p_station->tactical_call_sign = NULL;
18152     }
18153 
18154     else    // Non-blank incoming tactical call string
18155     {
18156 
18157       if (p_station->tactical_call_sign == NULL)
18158       {
18159         // Malloc some memory to hold it.
18160         p_station->tactical_call_sign = (char *)malloc(MAX_TACTICAL_CALL+1);
18161       }
18162       if (p_station->tactical_call_sign == NULL)
18163       {
18164         fprintf(stderr,
18165                 "Couldn't malloc space for tactical callsign\n");
18166         return -1;
18167       }
18168 
18169       xastir_snprintf(p_station->tactical_call_sign,
18170                       MAX_TACTICAL_CALL+1,
18171                       "%s",
18172                       tactical_call);
18173     }
18174     redraw_on_new_data = 2;  // redraw now
18175   }
18176 
18177   // Log the change in the tactical_calls.log file.  Also adds it
18178   // to the tactical callsign hash.
18179   log_tactical_call(call, tactical_call);
18180 
18181   return(0);
18182 }
18183 
18184 
18185 
18186 
18187 
18188 //
18189 // Assign tactical callsigns based on messages sent to "TACTICAL"
18190 //
18191 //  *) To set your own tactical callsign and send it to others,
18192 //     send an APRS message to "TACTICAL" with your callsign in
18193 //     the message text.
18194 //
18195 //  *) To send multiple tactical calls to others, send an APRS
18196 //     message to "TACTICAL" and enter:
18197 //     "CALL1=TAC1;CALL2=TAC2;CALL3=TAC3" in the message text.
18198 //
18199 //  '=' or ';' characters can not be in the TAC callsign.
18200 //
tactical_data_add(char * call,char * message,char UNUSED (from))18201 int tactical_data_add(char *call, char *message, char UNUSED(from) )
18202 {
18203   char *temp_ptr;
18204 
18205 
18206   if (strlen(message) <= 1)
18207   {
18208     return -1;
18209   }
18210 
18211   // Check whether we're dealing with one or multiple tactical
18212   // callsign assignments.  Look for a '=' character.
18213   temp_ptr = strrchr(message,'=');
18214 
18215   if (temp_ptr == NULL)
18216   {
18217     // No '=' character was found.  We're dealing with a single
18218     // tactical assignment for the "call" callsign.  Extract the
18219     // tactical call and assign it to the station data record
18220     // for the station.
18221 
18222     if (debug_level & 2)
18223     {
18224       fprintf(stderr, "One tactical assignment.\n");
18225     }
18226 
18227     fill_in_tactical_callsign(call, message);
18228   }
18229 
18230   else    // We're dealing with multiple tactical assignments.
18231   {
18232     int ii;
18233     const int max = 50;
18234     char *Substring[max];
18235     char *Call_Tac[2];
18236 
18237 
18238     if (debug_level & 2)
18239     {
18240       fprintf(stderr, "Possibly multiple tactical assignments.\n");
18241     }
18242 
18243     // Split the message first on ';' characters to get the
18244     // callsign=tactical pairs separated from each other.
18245     split_string( message, Substring, max, ';' );
18246 
18247     // Check whether we found more than one pair.
18248     if (Substring[0] == NULL)   // No ';' chars were found.
18249     {
18250       // We might still have a single tactical definition in
18251       // the message.  Assign "message" to Substring[0] for
18252       // further processing below.
18253 
18254       if (debug_level & 2)
18255       {
18256         fprintf(stderr, "No semicolons found.\n");
18257       }
18258 
18259       Substring[0] = message;
18260     }
18261 
18262     ii = 0;
18263 
18264     while (Substring[ii] != NULL)
18265     {
18266       // Split each string and process.  The results of each
18267       // split will be in:
18268       //   Call_Tac[0]    (Callsign)
18269       //   Call_Tac[1]    (Tactical Callsign)
18270       //
18271       split_string( Substring[ii], Call_Tac, 2, '=' );
18272 
18273       if (Call_Tac[0] != NULL)   // Found '=' char.
18274       {
18275 
18276         if (debug_level & 2)
18277         {
18278           fprintf(stderr, "Found a tactical pair:  %s->%s\n",
18279                   Call_Tac[0],
18280                   Call_Tac[1]);
18281         }
18282 
18283         fill_in_tactical_callsign(Call_Tac[0], Call_Tac[1]);
18284       }
18285       ii++;
18286     }
18287   }
18288 
18289   return 0;
18290 }
18291 
18292 
18293 
18294 
18295 
18296 //
18297 //  Messages, Bulletins and Announcements         [APRS Reference, chapter 14]
18298 //
18299 //
18300 // Returns 1 if successful
18301 //         0 if not successful
18302 //
decode_message(char * call,char * path,char * message,char from,int port,int third_party)18303 int decode_message(char *call,char *path,char *message,char from,int port,int third_party)
18304 {
18305   char *temp_ptr;
18306   char ipacket_message[300];
18307   char message_plus_acks[MAX_MESSAGE_LENGTH + 10];
18308   char from_call[MAX_CALLSIGN+1];
18309   char ack[20];
18310   int ok, len;
18311   char addr[9+1];
18312   char addr9[9+1];
18313   char msg_id[5+1];
18314   char orig_msg_id[5+1];
18315   char ack_string[6];
18316   int done;
18317   int reply_ack = 0;
18318   int to_my_call = 0;
18319   int to_my_base_call = 0;
18320   int from_my_call = 0;
18321 
18322 
18323   // :xxxxxxxxx:____0-67____             message              printable, except '|', '~', '{'
18324   // :TACTICAL :text                     Tactical definition for sending station
18325   // :TACTICAL :CALL1=TAC1;CALL2=TAC2    Tactical definitions for multiple stations
18326   // :BLNn     :____0-67____             general bulletin     printable, except '|', '~'
18327   // :BLNnxxxxx:____0-67____           + Group Bulletin
18328   // :BLNX     :____0-67____             Announcement
18329   // :NWS-xxxxx:____0-67____             NWS Service Bulletin
18330   // :NWS_xxxxx:____0-67____             NWS Service Bulletin
18331   // :BOM-xxxxx:____0-67____             BOM Service Bulletin (AU Wx)
18332   // :BOM_xxxxx:____0-67____             BOM Service Bulletin (AU Wx)
18333   // :xxxxxxxxx:ackn1-5n               + ack
18334   // :xxxxxxxxx:rejn1-5n               + rej
18335   // :xxxxxxxxx:____0-67____{n1-5n     + message
18336   // :NTS....
18337   //  01234567890123456
18338   // 01234567890123456    old
18339   // we get message with already extracted data ID
18340 
18341   if (debug_level & 1)
18342   {
18343     fprintf(stderr,"decode_message: start\n");
18344   }
18345 
18346   if (debug_level & 1)
18347   {
18348     if ( (message != NULL) && (strlen(message) > (MAX_MESSAGE_LENGTH + 10) ) )
18349     {
18350       //
18351       // Overly long message.  Throw it away.  We're done.
18352       //
18353       fprintf(stderr,"decode_message: LONG message.  Dumping it.\n");
18354       return(0);
18355     }
18356   }
18357 
18358   if (is_my_call(call, 1) )   // Check SSID also
18359   {
18360     from_my_call++;
18361   }
18362 
18363   ack_string[0] = '\0';   // Clear out the Reply/Ack result string
18364 
18365   len = (int)strlen(message);
18366   ok = (int)(len > 9 && message[9] == ':');
18367 
18368   if (ok)
18369   {
18370 
18371     substr(addr9,message,9); // extract addressee
18372     xastir_snprintf(addr,
18373                     sizeof(addr),
18374                     "%s",
18375                     addr9);
18376     (void)remove_trailing_spaces(addr);
18377 
18378     if (is_my_call(addr,1))   // Check includes SSID
18379     {
18380       to_my_call++;
18381     }
18382 
18383     if (is_my_call(addr,0))   // Check ignores SSID.  We use
18384     {
18385       // this to catch messages to some
18386       // of our other SSID's
18387       to_my_base_call++;
18388     }
18389 
18390     message = message + 10; // pointer to message text
18391 
18392     // Save the message text and the acks/reply-acks before we
18393     // extract the acks below.
18394     xastir_snprintf(message_plus_acks,
18395                     sizeof(message_plus_acks),
18396                     "%s",
18397                     message);
18398 
18399     temp_ptr = strrchr(message,'{'); // look for message ID after
18400     //*last* { in message.
18401     msg_id[0] = '\0';
18402     if (temp_ptr != NULL)
18403     {
18404       substr(msg_id,temp_ptr+1,5); // extract message ID, could be non-digit
18405       temp_ptr[0] = '\0';          // adjust message end (chops off message ID)
18406     }
18407 
18408     // Save the original msg_id away.
18409     xastir_snprintf(orig_msg_id,
18410                     sizeof(orig_msg_id),
18411                     "%s",
18412                     msg_id);
18413 
18414     // Check for Reply/Ack protocol in msg_id, which looks like
18415     // this:  "{XX}BB", where XX is the sequence number for the
18416     // message, and BB is the ack for the previous message from
18417     // my station.  I've also seen this from APRS+: "{XX}B", so
18418     // perhaps this is also possible "{X}B" or "{X}BB}".  We can
18419     // also get auto-reply responses from APRS+ that just have
18420     // "}X" or "}XX" at the end.  We decode those as well.
18421     //
18422 
18423     temp_ptr = strstr(msg_id,"}"); // look for Reply Ack in msg_id
18424 
18425     if (temp_ptr != NULL)   // Found Reply/Ack protocol!
18426     {
18427 
18428       reply_ack++;
18429 
18430       //            if ( (debug_level & 1) && (is_my_call(addr,1)) ) { // Check SSID also
18431       if ( (debug_level & 1) && to_my_call)   // Check SSID also
18432       {
18433         fprintf(stderr,"1Found Reply/Ack:%s\n",message);
18434         fprintf(stderr,"Orig_msg_id:%s\t",msg_id);
18435       }
18436 
18437       // Put this code into the UI message area as well (if applicable).
18438 
18439       // Separate out the extra ack so that we can deal with
18440       // it properly.
18441       xastir_snprintf(ack_string,
18442                       sizeof(ack_string),
18443                       "%s",
18444                       temp_ptr+1); // After the '}' character!
18445 
18446       // Terminate it here so that rest of decode works
18447       // properly.  We can get duplicate messages
18448       // otherwise.
18449       //
18450       // Note that we modify msg_id here.  Use orig_msg_id if we need the
18451       // unmodified version (full REPLY-ACK version) later.
18452       //
18453       temp_ptr[0] = '\0'; // adjust msg_id end
18454 
18455       //            if ( (debug_level & 1) && (is_my_call(addr,1)) ) { // Check SSID also
18456       if ( (debug_level & 1) && to_my_call)   // Check SSID also
18457       {
18458         fprintf(stderr,"New_msg_id:%s\tReply_ack:%s\n\n",
18459                 msg_id,ack_string);
18460       }
18461 
18462     }
18463     else    // Look for Reply Ack in message without sequence
18464     {
18465       // number
18466       temp_ptr = strstr(message,"}");
18467 
18468       if (temp_ptr != NULL)
18469       {
18470         int yy = 0;
18471 
18472 
18473         reply_ack++;
18474 
18475         //                if ( (debug_level & 1) && (is_my_call(addr,1)) ) { // Check SSID also
18476         if ( (debug_level & 1) && to_my_call)   // Check SSID also
18477         {
18478           fprintf(stderr,"2Found Reply/Ack:%s\n",message);
18479         }
18480 
18481         // Put this code into the UI message area as well (if applicable).
18482         xastir_snprintf(ack_string,
18483                         sizeof(ack_string),
18484                         "%s",
18485                         temp_ptr+1);    // After the '}' character!
18486 
18487         ack_string[yy] = '\0';  // Terminate the string
18488 
18489         // Terminate it here so that rest of decode works
18490         // properly.  We can get duplicate messages
18491         // otherwise.
18492         temp_ptr[0] = '\0'; // adjust message end
18493 
18494         //                if ( (debug_level & 1) && (is_my_call(addr,1)) ) { // Check SSID also
18495         if ( (debug_level & 1) && to_my_call)   // Check SSID also
18496         {
18497           fprintf(stderr,"Reply_ack:%s\n\n",ack_string);
18498         }
18499       }
18500     }
18501 
18502     done = 0;
18503   }
18504   else
18505   {
18506     done = 1;                               // fall through...
18507   }
18508 
18509   if (debug_level & 1)
18510   {
18511     fprintf(stderr,"1\n");
18512   }
18513   len = (int)strlen(message);
18514   //--------------------------------------------------------------------------
18515   if (!done && len > 3 && strncmp(message,"ack",3) == 0)                // ACK
18516   {
18517 
18518     // Received an ACK packet.  Note that these can carry the
18519     // REPLY-ACK protocol or a single ACK sequence number plus
18520     // perhaps an extra '}' on the end.  They should have one of
18521     // these formats:
18522     //      ack1        Normal ACK
18523     //      ackY        Normal ACK
18524     //      ack23       Normal ACK
18525     //      ackfH       Normal ACK
18526     //      ack23{      REPLY-ACK Protocol
18527     //      ack2Q}3d    REPLY-ACK Protocol
18528 
18529     substr(msg_id,message+3,5);
18530     // fprintf(stderr,"ACK: %s: |%s| |%s|\n",call,addr,msg_id);
18531     //        if (is_my_call(addr,1)) { // Check SSID also
18532     if (to_my_call)   // Check SSID also
18533     {
18534 
18535       // Note:  This function handles REPLY-ACK protocol just
18536       // fine, stripping off the 2nd ack if present.  It uses
18537       // only the first sequence number.
18538       clear_acked_message(call,addr,msg_id);  // got an ACK for me
18539 
18540       // This one also handles REPLY-ACK protocol just fine.
18541       msg_record_ack(call,addr,msg_id,0,0);   // Record the ack for this message
18542     }
18543     else    // ACK is for another station
18544     {
18545       // Now if I have Igate on and I allow to retransmit station data
18546       // check if this message is to a person I have heard on my TNC within an X
18547       // time frame. If if is a station I heard and all the conditions are ok
18548       // spit the ACK out on the TNC -FG
18549       if (operate_as_an_igate>1
18550           && from==DATA_VIA_NET
18551           //                    && !is_my_call(call,1) // Check SSID also
18552           && !from_my_call     // Check SSID also
18553           && port != -1)      // Not from a log file
18554       {
18555         char short_path[100];
18556 
18557         //fprintf(stderr,"Igate check o:%d f:%c myc:%s cf:%s ct:%s\n",
18558         //    operate_as_an_igate,
18559         //    from,
18560         //    my_callsign,
18561         //    call,
18562         //    addr);
18563         shorten_path(path,short_path,sizeof(short_path));
18564 
18565         // Only send '}' and the ack_string if it's not
18566         // empty, else just end the packet with the message
18567         // string.  This keeps us from appending a '}' when
18568         // it's not called for.
18569         xastir_snprintf(ipacket_message,
18570                         sizeof(ipacket_message),
18571                         //                    "}%s>%s,TCPIP,%s*::%s:%s%s%s",
18572                         "}%s>%s,TCPIP,%s*::%s:%s",
18573 
18574                         call,
18575                         short_path,
18576                         my_callsign,
18577                         addr9,
18578                         //                    message,
18579                         message_plus_acks);
18580         //                    (ack_string[0] == '\0') ? "" : "}",
18581         //                    ack_string);
18582 
18583         if (reply_ack)   // For debugging, so we only have reply-ack
18584         {
18585           // messages and acks scrolling across the screen.
18586           //    fprintf(stderr,"Attempting to send ACK to RF:  %s\n", ipacket_message);
18587         }
18588 
18589         output_igate_rf(call,
18590                         addr,
18591                         path,
18592                         ipacket_message,
18593                         port,
18594                         third_party,
18595                         NULL);
18596 
18597         igate_msgs_tx++;
18598       }
18599     }
18600     done = 1;
18601   }
18602   if (debug_level & 1)
18603   {
18604     fprintf(stderr,"2\n");
18605   }
18606   //--------------------------------------------------------------------------
18607   if (!done && len > 3 && strncmp(message,"rej",3) == 0)                // REJ
18608   {
18609 
18610     substr(msg_id,message+3,5);
18611 
18612     //        if ( is_my_call(addr,1) ) { // Check SSID also
18613     if (to_my_call)     // Check SSID also
18614     {
18615 
18616       // REJ is for me!
18617       //            fprintf(stderr,"Received a REJ packet from %s: |%s| |%s|\n",call,addr,msg_id);
18618 
18619       // Note:  This function handles REPLY-ACK protocol just
18620       // fine, stripping off the 2nd ack if present.  It uses
18621       // only the first sequence number.
18622       clear_acked_message(call,addr,msg_id);  // got an REJ for me
18623 
18624       // This one also handles REPLY-ACK protocol just fine.
18625       msg_record_rej(call,addr,msg_id);   // Record the REJ for this message
18626     }
18627     else    // REJ is for another station
18628     {
18629       /* Now if I have Igate on and I allow to retransmit station data           */
18630       /* check if this message is to a person I have heard on my TNC within an X */
18631       /* time frame. If if is a station I heard and all the conditions are ok    */
18632       /* spit the REJ out on the TNC                                             */
18633       if (operate_as_an_igate>1
18634           && from==DATA_VIA_NET
18635           //                    && !is_my_call(call,1) // Check SSID also
18636           && !from_my_call    // Check SSID also
18637           && port != -1)      // Not from a log file
18638       {
18639         char short_path[100];
18640 
18641         //fprintf(stderr,"Igate check o:%d f:%c myc:%s cf:%s ct:%s\n",
18642         //    operate_as_an_igate,
18643         //    from,
18644         //    my_callsign,
18645         //    call,
18646         //    addr);
18647         shorten_path(path,short_path,sizeof(short_path));
18648 
18649         // Only send '}' and the rej_string if it's not
18650         // empty, else just end the packet with the message
18651         // string.  This keeps us from appending a '}' when
18652         // it's not called for.
18653         xastir_snprintf(ipacket_message,
18654                         sizeof(ipacket_message),
18655                         //                    "}%s>%s,TCPIP,%s*::%s:%s%s%s",
18656                         "}%s>%s,TCPIP,%s*::%s:%s",
18657 
18658                         call,
18659                         short_path,
18660                         my_callsign,
18661                         addr9,
18662                         //                    message,
18663                         message_plus_acks);
18664         //                    (ack_string[0] == '\0') ? "" : "}",
18665         //                    ack_string);
18666 
18667         if (reply_ack)   // For debugging, so we only have reply-ack
18668         {
18669           // messages and acks scrolling across the screen.
18670           //    fprintf(stderr,"Attempting to send REJ to RF:  %s\n", ipacket_message);
18671         }
18672 
18673         output_igate_rf(call,
18674                         addr,
18675                         path,
18676                         ipacket_message,
18677                         port,
18678                         third_party,
18679                         NULL);
18680 
18681         igate_msgs_tx++;
18682       }
18683     }
18684 
18685     done = 1;
18686   }
18687   if (debug_level & 1)
18688   {
18689     fprintf(stderr,"3\n");
18690   }
18691   //--------------------------------------------------------------------------
18692   if (!done && strncmp(addr,"TACTICAL",8) == 0)                    // Tactical definition
18693   {
18694 
18695     if (debug_level & 2)
18696     {
18697       fprintf(stderr,"found TACTICAL: |%s| |%s|\n",call,message);
18698     }
18699 
18700     tactical_data_add(call,message,from);
18701     done = 1;
18702   }
18703   if (debug_level & 1)
18704   {
18705     fprintf(stderr,"TAC\n");
18706   }
18707   //--------------------------------------------------------------------------
18708   if (!done && strncmp(addr,"BLN",3) == 0)                         // Bulletin
18709   {
18710     // fprintf(stderr,"found BLN: |%s| |%s|\n",addr,message);
18711     bulletin_data_add(addr,call,message,"",MESSAGE_BULLETIN,from);
18712     done = 1;
18713   }
18714   if (debug_level & 1)
18715   {
18716     fprintf(stderr,"4\n");
18717   }
18718 
18719   //--------------------------------------------------------------------------
18720   //    if (!done && strlen(msg_id) > 0 && is_my_call(addr,1)) { // Message for me (including SSID check)
18721   if (!done && strlen(msg_id) > 0 && to_my_call)           // Message for me (including SSID check)
18722   {
18723     // with msg_id (sequence number)
18724     time_t last_ack_sent;
18725     long record;
18726 
18727 
18728     // Remember to put this code into the UI message area as well (if
18729     // applicable).
18730 
18731     // Check for Reply/Ack
18732     if (reply_ack && strlen(ack_string) != 0)   // Have a free-ride ack to deal with
18733     {
18734 
18735       //fprintf(stderr, "reply-ack: clear_acked_message()\n");
18736       clear_acked_message(call,addr,ack_string);  // got an ACK for me
18737 
18738       //fprintf(stderr, "reply-ack: msg_record_ack()\n");
18739       msg_record_ack(call,addr,ack_string,0,0);   // Record the ack for this message
18740     }
18741 
18742     // Save the ack 'cuz we might need it while talking to this
18743     // station.  We need it to implement Reply/Ack protocol.
18744 
18745     // Note that msg_id has already been truncated by this point.
18746     // orig_msg_id contains the full REPLY-ACK text.
18747 
18748     //fprintf(stderr, "store_most_recent_ack()\n");
18749     store_most_recent_ack(call,msg_id);
18750 
18751     // fprintf(stderr,"found Msg w line to me: |%s| |%s|\n",message,msg_id);
18752     last_ack_sent = msg_data_add(addr,
18753                                  call,
18754                                  message,
18755                                  msg_id,
18756                                  MESSAGE_MESSAGE,
18757                                  from,
18758                                  &record); // id_fixed
18759 
18760     // Here we need to know if it is a new message or an old.
18761     // If we've already received it, we don't want to kick off
18762     // the alerts or pop up the Send Message dialog again.  If
18763     // last_ack_sent == (time_t)0, then it is a new message.
18764     //
18765     if (last_ack_sent == (time_t)0l && record == -1l)   // Msg we've never received before
18766     {
18767 
18768       new_message_data += 1;
18769 
18770       // Note that the check_popup_window() function will
18771       // re-create a Send Message dialog if one doesn't exist
18772       // for this QSO.  Only call it for the first message
18773       // line or the first ack, not for any repeats.
18774       //
18775       //fprintf(stderr,"***check_popup_window 1\n");
18776       (void)check_popup_window(call, 2);  // Calls update_messages()
18777 
18778       //update_messages(1); // Force an update
18779 
18780       if (sound_play_new_message)
18781       {
18782         play_sound(sound_command,sound_new_message);
18783       }
18784 
18785 #ifdef HAVE_FESTIVAL
18786       /* I re-use ipacket_message as my string buffer */
18787       if (festival_speak_new_message_alert)
18788       {
18789         xastir_snprintf(ipacket_message,
18790                         sizeof(ipacket_message),
18791                         "You have a new message from %s.",
18792                         call);
18793         SayText(ipacket_message);
18794       }
18795       if (festival_speak_new_message_body)
18796       {
18797         xastir_snprintf(ipacket_message,
18798                         sizeof(ipacket_message),
18799                         " %s",
18800                         message);
18801         SayText(ipacket_message);
18802       }
18803 
18804 #endif  // HAVE_FESTIVAL
18805 
18806     }
18807 
18808     // Try to only send an ack out once per 30 seconds at the
18809     // fastest.
18810     //WE7U
18811     // Does this 30-second check work?
18812     //
18813     if ( from != 'F'  // Not from a log file
18814          && (last_ack_sent != (time_t)-1l)   // Not an error
18815          && (last_ack_sent + 30 ) < sec_now()
18816          && !satellite_ack_mode // Disable separate ack's for satellite work
18817          && port != -1 )     // Not from a log file
18818     {
18819 
18820       char path[MAX_LINE_SIZE+1];
18821 
18822 
18823       //fprintf(stderr,"Sending ack: %ld %ld %ld\n",last_ack_sent,sec_now(),record);
18824 
18825       // Update the last_ack_sent field for the message
18826       msg_update_ack_stamp(record);
18827 
18828       pad_callsign(from_call,call);         /* ack the message */
18829 
18830 
18831       // Attempt to snag a custom path out of the Send Message
18832       // dialog, if set.  If not set, path will contain '\0';
18833       get_send_message_path(call, path, MAX_LINE_SIZE+1);
18834       //fprintf(stderr,"Path: %s\n", path);
18835 
18836 
18837       // In this case we want to send orig_msg_id back, not
18838       // the (possibly) truncated msg_id.  This is per Bob B's
18839       // Reply/Ack spec, sent to xastir-dev on Nov 14, 2001.
18840       xastir_snprintf(ack, sizeof(ack), ":%s:ack%s",from_call,orig_msg_id);
18841 
18842       //WE7U
18843       // Need to figure out the reverse path for this one instead of
18844       // passing a NULL for the path?  Probably not, as auto-calculation
18845       // of paths isn't a good idea.
18846       //
18847       // What we need to do here is check whether we have a custom path
18848       // set for this QSO.  If so, pass that path along as the transmit
18849       // path.  messages.h:Message_Window struct has the send_message_path
18850       // variable in it.  If a Message_Window still exists for this QSO
18851       // then we can snag the user-entered path from there.  If the struct
18852       // has already been destroyed then we have nowhere to snag the
18853       // custom path from and have to rely on the default paths in each
18854       // interface properties dialog instead.  Then again, we _could_ snag
18855       // the path out of the last received message in the message database
18856       // for that case.  Might be better to disable the Close button, or
18857       // warn the user that the custom path will be lost if they close the
18858       // Send Message dialog.
18859 
18860 
18861       // Send out the immediate ACK
18862       if (path[0] == '\0')
18863       {
18864         transmit_message_data(call,ack,NULL);
18865       }
18866       else
18867       {
18868         transmit_message_data(call,ack,path);
18869       }
18870 
18871 
18872       if (record != -1l)   // Msg we've received before
18873       {
18874 
18875         // It's a message that we've received before,
18876         // consider sending an extra ACK in about 30 seconds
18877         // to try to get it to the remote station.  Perhaps
18878         // another one in 60 seconds as well.
18879 
18880         //                fprintf(stderr,
18881         //                    "We've received this message before.\n");
18882         //                fprintf(stderr,
18883         //                    "Sending a couple of delayed ack's.\n");
18884 
18885         if (path[0] == '\0')
18886         {
18887           transmit_message_data_delayed(call,ack,NULL,sec_now()+30);
18888           transmit_message_data_delayed(call,ack,NULL,sec_now()+60);
18889           transmit_message_data_delayed(call,ack,NULL,sec_now()+120);
18890         }
18891         else
18892         {
18893           transmit_message_data_delayed(call,ack,path,sec_now()+30);
18894           transmit_message_data_delayed(call,ack,path,sec_now()+60);
18895           transmit_message_data_delayed(call,ack,path,sec_now()+120);
18896         }
18897       }
18898 
18899 
18900       if (auto_reply == 1)
18901       {
18902 
18903         xastir_snprintf(ipacket_message,
18904                         sizeof(ipacket_message), "AA:%s", auto_reply_message);
18905 
18906         if (debug_level & 2)
18907           fprintf(stderr,"Send autoreply to <%s> from <%s> :%s\n",
18908                   call, my_callsign, ipacket_message);
18909 
18910         //                if (!is_my_call(call,1)) // Check SSID also
18911         if (!from_my_call) // Check SSID also
18912         {
18913           output_message(my_callsign, call, ipacket_message, "");
18914         }
18915       }
18916     }
18917 
18918     else
18919     {
18920       //fprintf(stderr,"Skipping ack: %ld %ld\n",last_ack_sent,sec_now());
18921     }
18922 
18923     done = 1;
18924   }
18925   if (debug_level & 1)
18926   {
18927     fprintf(stderr,"5a\n");
18928   }
18929 
18930   //--------------------------------------------------------------------------
18931   if (!done && strlen(msg_id) == 0 && to_my_call)     // Message for me (including SSID check)
18932   {
18933     // but without message-ID.
18934     // These should appear in a Send Message dialog and should
18935     // NOT get ack'ed.  Kenwood radios send this message type as
18936     // an auto-answer or a buffer-full message.  They look
18937     // something like:
18938     //
18939     //      :WE7U-13 :Not at keyboard.
18940     //
18941 
18942     time_t last_ack_sent;
18943     long record;
18944 
18945 
18946     if (len > 2
18947         && message[0] == '?'
18948         && port != -1   // Not from a log file
18949         && to_my_call)   // directed query (check SSID also)
18950     {
18951       // Smallest query known is "?WX".
18952       if (debug_level & 1)
18953       {
18954         fprintf(stderr,"Received a directed query\n");
18955       }
18956       done = process_directed_query(call,path,message+1,from);
18957     }
18958 
18959     // fprintf(stderr,"found Msg w line to me: |%s| |%s|\n",message,msg_id);
18960     last_ack_sent = msg_data_add(addr,
18961                                  call,
18962                                  message,
18963                                  msg_id,
18964                                  MESSAGE_MESSAGE,
18965                                  from,
18966                                  &record); // id_fixed
18967 
18968     // Here we need to know if it is a new message or an old.
18969     // If we've already received it, we don't want to kick off
18970     // the alerts or pop up the Send Message dialog again.  If
18971     // last_ack_sent == (time_t)0, then it is a new message.
18972     //
18973     if (last_ack_sent == (time_t)0l && record == -1l)   // Msg we've never received before
18974     {
18975 
18976       new_message_data += 1;
18977 
18978       // Note that the check_popup_window() function will
18979       // re-create a Send Message dialog if one doesn't exist
18980       // for this QSO.  Only call it for the first message
18981       // line or the first ack, not for any repeats.
18982       //
18983       //fprintf(stderr,"***check_popup_window 1\n");
18984       (void)check_popup_window(call, 2);  // Calls update_messages()
18985 
18986       //update_messages(1); // Force an update
18987 
18988       if (sound_play_new_message)
18989       {
18990         play_sound(sound_command,sound_new_message);
18991       }
18992 
18993 #ifdef HAVE_FESTIVAL
18994       /* I re-use ipacket_message as my string buffer */
18995       if (festival_speak_new_message_alert)
18996       {
18997         xastir_snprintf(ipacket_message,
18998                         sizeof(ipacket_message),
18999                         "You have a new message from %s.",
19000                         call);
19001         SayText(ipacket_message);
19002       }
19003       if (festival_speak_new_message_body)
19004       {
19005         xastir_snprintf(ipacket_message,
19006                         sizeof(ipacket_message),
19007                         " %s",
19008                         message);
19009         SayText(ipacket_message);
19010       }
19011 
19012 #endif  // HAVE_FESTIVAL
19013 
19014     }
19015 
19016     // Update the last_ack_sent field for the message, even
19017     // though we won't be sending an ack in response.
19018     msg_update_ack_stamp(record);
19019 
19020 
19021     //fprintf(stderr,"Received msg for me w/o ack\n");
19022 
19023     done = 1;
19024   }
19025   if (debug_level & 1)
19026   {
19027     fprintf(stderr,"5b\n");
19028   }
19029 
19030   //--------------------------------------------------------------------------
19031   if (!done
19032       && ( (strncmp(addr,"NWS-",4) == 0)          // NWS weather alert
19033            || (strncmp(addr,"NWS_",4) == 0)          // NWS weather alert compressed
19034            || (strncmp(addr,"BOM-",4) == 0)          // BOM (AU) weather alert
19035            || (strncmp(addr,"BOM_",4) == 0) ) )      // BOM (AU) weather alert compressed
19036   {
19037 
19038     // could have sort of line number
19039     //fprintf(stderr,"found NWS: |%s| |%s| |%s|\n",addr,message,msg_id);
19040 
19041     (void)alert_data_add(addr,
19042                          call,
19043                          message,
19044                          msg_id,
19045                          MESSAGE_NWS,
19046                          from);
19047 
19048     done = 1;
19049     if (operate_as_an_igate>1
19050         && from==DATA_VIA_NET
19051         //                && !is_my_call(call,1) // Check SSID also
19052         && !from_my_call // Check SSID also
19053         && port != -1)   // Not from a log file
19054     {
19055       char short_path[100];
19056 
19057       shorten_path(path,short_path,sizeof(short_path));
19058 
19059       xastir_snprintf(ipacket_message,
19060                       sizeof(ipacket_message),
19061                       "}%s>%s,TCPIP,%s*::%s:%s",
19062                       call,
19063                       short_path,
19064                       my_callsign,
19065                       addr9,
19066                       message);
19067 
19068       output_nws_igate_rf(call,
19069                           path,
19070                           ipacket_message,
19071                           port,
19072                           third_party);
19073     }
19074   }
19075   if (debug_level & 1)
19076   {
19077     fprintf(stderr,"6a\n");
19078   }
19079   //--------------------------------------------------------------------------
19080   if (!done && strncmp(addr,"SKY",3) == 0)    // NWS weather alert additional info
19081   {
19082 
19083     // could have sort of line number
19084     //fprintf(stderr,"found SKY: |%s| |%s| |%s|\n",addr,message,msg_id);
19085 
19086     /*
19087       (void)alert_data_add(addr,
19088       call,
19089       message,
19090       msg_id,
19091       MESSAGE_NWS,
19092       from);
19093     */
19094 
19095     // We don't wish to record these in memory.  They cause an infinite
19096     // loop in the current code and a massive memory leak.
19097     return(1);  // Tell the calling program that the packet was ok so
19098     // that it doesn't add it with data_add() itself!
19099 
19100 
19101     done = 1;
19102     if (operate_as_an_igate>1
19103         && from==DATA_VIA_NET
19104         //                && !is_my_call(call,1) // Check SSID also
19105         && !from_my_call    // Check SSID also
19106         && port != -1)   // Not from a log file
19107     {
19108       char short_path[100];
19109 
19110       shorten_path(path,short_path,sizeof(short_path));
19111 
19112       xastir_snprintf(ipacket_message,
19113                       sizeof(ipacket_message),
19114                       "}%s>%s,TCPIP,%s*::%s:%s",
19115                       call,
19116                       short_path,
19117                       my_callsign,
19118                       addr9,
19119                       message);
19120 
19121       output_nws_igate_rf(call,
19122                           path,
19123                           ipacket_message,
19124                           port,
19125                           third_party);
19126     }
19127   }
19128   if (debug_level & 1)
19129   {
19130     fprintf(stderr,"6b\n");
19131   }
19132   //--------------------------------------------------------------------------
19133   if (!done && strlen(msg_id) > 0)    // Other message with linenumber.  This
19134   {
19135     // is either a message for someone else
19136     // or a message for another one of my
19137     // SSID's.
19138     long record_out;
19139     time_t last_ack_sent;
19140     char message_plus_note[MAX_MESSAGE_LENGTH + 30];
19141 
19142 
19143     if (debug_level & 2)
19144       fprintf(stderr,"found Msg w/line: |%s| |%s| |%s|\n",
19145               addr,
19146               message,
19147               orig_msg_id);
19148 
19149     if (to_my_base_call && !from_my_call)
19150     {
19151       // Special case:  We saw a message w/msg_id that was to
19152       // one of our other SSID's, but it was not from
19153       // ourselves.  That last bit (!from_my_call) is
19154       // important in the case where we're working an event
19155       // with several stations using the same callsign.
19156       //
19157       // Store as if it came to my callsign, with a zeroed-out
19158       // msg_id so we can't try to ack it.  We also need some
19159       // other indication in the "Send Message" dialog as to
19160       // what's happening.  Perhaps add the original callsign
19161       // to the message itself in a note at the start?
19162       //
19163       xastir_snprintf(message_plus_note,
19164                       sizeof(message_plus_note),
19165                       "(Sent to:%s) %s",
19166                       addr,
19167                       message);
19168       last_ack_sent = msg_data_add(my_callsign,
19169                                    call,
19170                                    message_plus_note,
19171                                    "",
19172                                    MESSAGE_MESSAGE,
19173                                    from,
19174                                    &record_out);
19175     }
19176     else    // Normal case, messaging between other people
19177     {
19178       last_ack_sent = msg_data_add(addr,
19179                                    call,
19180                                    message,
19181                                    msg_id,
19182                                    MESSAGE_MESSAGE,
19183                                    from,
19184                                    &record_out);
19185     }
19186 
19187     new_message_data += look_for_open_group_data(addr);
19188 
19189     // Note that the check_popup_window() function will
19190     // re-create a Send Message dialog if one doesn't exist for
19191     // this QSO.  Only call it for the first message line or the
19192     // first ack, not for any repeats.
19193     //
19194     if (last_ack_sent == (time_t)0l && record_out == -1l)   // Msg we've never received before
19195     {
19196       //fprintf(stderr,"***check_popup_window 2\n");
19197 
19198       // Callsign check here also checks SSID for exact match
19199       //            if ((is_my_call(call,1) && check_popup_window(addr, 2) != -1)
19200       //            if ((from_my_call && check_popup_window(addr, 2) != -1)
19201       // We need to do an SSID-non-specific check here so that we can pick
19202       // up messages intended for other stations of ours.
19203       //            if ((to_my_base_call && check_popup_window(addr, 2) != -1)
19204       if ((to_my_base_call && check_popup_window(call, 2) != -1)
19205           || check_popup_window(call, 0) != -1
19206           || check_popup_window(addr, 1) != -1)
19207       {
19208         //fprintf(stderr,"Matches my base call\n");
19209         update_messages(1); // Force an update
19210       }
19211     }
19212 
19213     /* Now if I have Igate on and I allow to retransmit station data           */
19214     /* check if this message is to a person I have heard on my TNC within an X */
19215     /* time frame. If if is a station I heard and all the conditions are ok    */
19216     /* spit the message out on the TNC -FG                                     */
19217     if (operate_as_an_igate>1
19218         && last_ack_sent != (time_t)-1l
19219         && from==DATA_VIA_NET
19220         //                && !is_my_call(call,1) // Check SSID also
19221         && !from_my_call        // Check SSID also
19222         //                && !is_my_call(addr,1) // Check SSID also
19223         && !to_my_call          // Check SSID also
19224         && port != -1)      // Not from a log file
19225     {
19226       char short_path[100];
19227 
19228       //fprintf(stderr,"Igate check o:%d f:%c myc:%s cf:%s ct:%s\n",
19229       //    operate_as_an_igate,
19230       //    from,
19231       //    my_callsign,
19232       //    call,
19233       //    addr);
19234 
19235       shorten_path(path,short_path,sizeof(short_path));
19236       xastir_snprintf(ipacket_message,
19237                       sizeof(ipacket_message),
19238                       //                "}%s>%s,TCPIP,%s*::%s:%s{%s",
19239                       "}%s>%s,TCPIP,%s*::%s:%s",
19240                       call,
19241                       short_path,
19242                       my_callsign,
19243                       addr9,
19244                       message_plus_acks);
19245       //                message,
19246       //                orig_msg_id);
19247 
19248       if (reply_ack)   // For debugging, so we only have reply-ack
19249       {
19250         // messages and acks scrolling across the screen.
19251         //    fprintf(stderr,"Attempting to send message to RF: %s\n", ipacket_message);
19252       }
19253 
19254       output_igate_rf(call,
19255                       addr,
19256                       path,
19257                       ipacket_message,
19258                       port,
19259                       third_party,
19260                       NULL);
19261 
19262       igate_msgs_tx++;
19263     }
19264     done = 1;
19265   }
19266   if (debug_level & 1)
19267   {
19268     fprintf(stderr,"7\n");
19269   }
19270   //--------------------------------------------------------------------------
19271 
19272   if (!done)                                     // message without line number
19273   {
19274     long record_out;
19275     time_t last_ack_sent;
19276 
19277 
19278     if (debug_level & 4)
19279     {
19280       fprintf(stderr,"found Msg: |%s| |%s|\n",addr,message);
19281     }
19282     //found Msg: |WE7U-13| |?APRSD|
19283     //found Msg: |WE7U-14| |Directs=|
19284 
19285 
19286     last_ack_sent = msg_data_add(addr,
19287                                  call,
19288                                  message,
19289                                  "",
19290                                  MESSAGE_MESSAGE,
19291                                  from,
19292                                  &record_out);
19293 
19294     new_message_data++;      // ??????
19295 
19296     // Note that the check_popup_window() function will
19297     // re-create a Send Message dialog if one doesn't exist for
19298     // this QSO.  Only call it for the first message line or the
19299     // first ack, not for any repeats.
19300     //
19301     if (last_ack_sent == (time_t)0l && record_out == -1l)   // Msg we've never received before
19302     {
19303       //fprintf(stderr,"***check_popup_window 3\n");
19304       if (check_popup_window(addr, 1) != -1)
19305       {
19306         //update_messages(1); // Force an update
19307       }
19308     }
19309 
19310     // Could be response to a query.  Popup a messsage.
19311 
19312     // Check addr for my_call and !third_party, then check later in the
19313     // packet for my_call if it is a third_party message?  Depends on
19314     // what the packet looks like by this point.
19315     if ( last_ack_sent != (time_t)-1l
19316          && (message[0] != '?')
19317          //                && is_my_call(addr,1) ) { // Check SSID also
19318          && to_my_call )   // Check SSID also
19319     {
19320 
19321       // We no longer wish to have both popups and the Send
19322       // Group Message dialogs come up for every query
19323       // response, so we use popup_message() here instead of
19324       // popup_message_always() so that by default we'll see
19325       // the below message in STDERR.  If --with-errorpopups
19326       // has been configured in, we'll get a popup as well.
19327       // Send Group Message dialogs work well for multi-line
19328       // query responses, so we'll leave it that way.
19329       //
19330       popup_message(langcode("POPEM00018"),message);
19331 
19332       // Check for Reply/Ack.  APRS+ sends an AA: response back
19333       // for auto-reply, with an embedded free-ride Ack.
19334       if (strlen(ack_string) != 0)    // Have an extra ack to deal with
19335       {
19336 
19337         clear_acked_message(call,addr,ack_string);  // got an ACK for me
19338 
19339         msg_record_ack(call,addr,ack_string,0,0);   // Record the ack for this message
19340       }
19341     }
19342 
19343     // done = 1;
19344   }
19345   if (debug_level & 1)
19346   {
19347     fprintf(stderr,"9\n");
19348   }
19349   //--------------------------------------------------------------------------
19350 
19351   if (ok)
19352     (void)data_add(STATION_CALL_DATA,
19353                    call,
19354                    path,
19355                    message,
19356                    from,
19357                    port,
19358                    NULL,
19359                    third_party,
19360                    0,  // Not a packet from my station
19361                    0); // Not my object/item
19362 
19363   if (debug_level & 1)
19364   {
19365     fprintf(stderr,"decode_message: finish\n");
19366   }
19367 
19368   return(ok);
19369 }
19370 
19371 
19372 
19373 
19374 
19375 /*
19376  *  UI-View format messages, not relevant for APRS, format is not specified in APRS Reference
19377  *
19378  * This function is not currently called anywhere in the code.
19379  */
decode_UI_message(char * call,char * path,char * message,char from,int port,int third_party)19380 int decode_UI_message(char *call,char *path,char *message,char from,int port,int third_party)
19381 {
19382   char *temp_ptr;
19383   char from_call[MAX_CALLSIGN+1];
19384   char ack[20];
19385   char addr[9+1];
19386   int ok, len;
19387   char msg_id[5+1];
19388   int done;
19389   int from_my_call = 0;
19390   int to_my_call = 0;
19391 
19392 
19393   if (is_my_call(call, 1) )   // Check SSID also
19394   {
19395     from_my_call++;
19396   }
19397 
19398   // I'm not sure, but I think they use 2 digit line numbers only
19399   // extract addr from path
19400   substr(addr,path,9);
19401   ok = (int)(strlen(addr) > 0);
19402   if (ok)
19403   {
19404     temp_ptr = strstr(addr,",");         // look for end of first call
19405     if (temp_ptr != NULL)
19406     {
19407       temp_ptr[0] = '\0';  // adjust addr end
19408     }
19409     ok = (int)(strlen(addr) > 0);
19410   }
19411 
19412   if (is_my_call(addr, 1) )   // Check SSID also
19413   {
19414     to_my_call++;
19415   }
19416 
19417   len = (int)strlen(message);
19418   ok = (int)(len >= 2);
19419   if (ok)
19420   {
19421     temp_ptr = strstr(message,"~");         // look for message ID
19422     msg_id[0] = '\0';
19423     if (temp_ptr != NULL)
19424     {
19425       substr(msg_id,temp_ptr+1,2);        // extract message ID, could be non-digit
19426       temp_ptr[0] = '\0';                 // adjust message end
19427     }
19428     done = 0;
19429   }
19430   else
19431   {
19432     done = 1;  // fall through...
19433   }
19434   len = (int)strlen(message);
19435   //--------------------------------------------------------------------------
19436   // Callsign check here checks SSID as well
19437   //    if (!done && msg_id[0] != '\0' && is_my_call(addr,1)) {      // message for me
19438   if (!done && msg_id[0] != '\0' && to_my_call)        // message for me
19439   {
19440     time_t last_ack_sent;
19441     long record;
19442 
19443     last_ack_sent = msg_data_add(addr,
19444                                  call,
19445                                  message,
19446                                  msg_id,
19447                                  MESSAGE_MESSAGE,
19448                                  from,
19449                                  &record);
19450 
19451     new_message_data += 1;
19452 
19453     // Note that the check_popup_window() function will
19454     // re-create a Send Message dialog if one doesn't exist for
19455     // this QSO.  Only call it for the first message line or the
19456     // first ack, not for any repeats.
19457     //
19458     if (last_ack_sent == (time_t)0l && record == -1l)   // Msg we've never received before
19459     {
19460       //fprintf(stderr,"***check_popup_window 4\n");
19461       (void)check_popup_window(call, 2);
19462       //update_messages(1); // Force an update
19463     }
19464 
19465     if (last_ack_sent != (time_t)-1l)
19466     {
19467 
19468       if (sound_play_new_message)
19469       {
19470         play_sound(sound_command,sound_new_message);
19471       }
19472 
19473       // Only send an ack or autoresponse once per 30 seconds
19474       if ( (from != 'F')
19475            && ( (last_ack_sent + 30) < sec_now()) )
19476       {
19477 
19478         //fprintf(stderr,"Sending ack: %ld %ld %ld\n",last_ack_sent,sec_now(),record);
19479 
19480         // Record the fact that we're sending an ack now
19481         msg_update_ack_stamp(record);
19482 
19483         pad_callsign(from_call,call);         /* ack the message */
19484         xastir_snprintf(ack, sizeof(ack), ":%s:ack%s",from_call,msg_id);
19485 
19486         // Nice to return via the reverse path here?  No!  Better to use the
19487         // default paths instead of a calculated reverse path.
19488 
19489         transmit_message_data(call,ack,NULL);
19490         if (auto_reply == 1)
19491         {
19492           char temp[300];
19493 
19494           xastir_snprintf(temp, sizeof(temp), "AA:%s", auto_reply_message);
19495 
19496           if (debug_level & 2)
19497             fprintf(stderr,"Send autoreply to <%s> from <%s> :%s\n",
19498                     call, my_callsign, temp);
19499 
19500           //                    if (!is_my_call(call,1)) // Check SSID also
19501           if (!from_my_call) // Check SSID also
19502           {
19503             output_message(my_callsign, call, temp, "");
19504           }
19505         }
19506       }
19507     }
19508     done = 1;
19509   }
19510   //--------------------------------------------------------------------------
19511   if (!done && len == 2 && msg_id[0] == '\0')                  // ACK
19512   {
19513     substr(msg_id,message,5);
19514     //        if (is_my_call(addr,1)) { // Check SSID also
19515     if (to_my_call)   // Check SSID also
19516     {
19517 
19518       clear_acked_message(call,addr,msg_id); // got an ACK for me
19519 
19520       msg_record_ack(call,addr,msg_id,0,0);  // Record the ack for this message
19521     }
19522     //        else {                                          // ACK for other station
19523     /* Now if I have Igate on and I allow to retransmit station data           */
19524     /* check if this message is to a person I have heard on my TNC within an X */
19525     /* time frame. If if is a station I heard and all the conditions are ok    */
19526     /* spit the ACK out on the TNC -FG                                         */
19527     ////            if (operate_as_an_igate>1 && from==DATA_VIA_NET && !is_my_call(call,1)) {
19528     //            if (operate_as_an_igate>1 && from==DATA_VIA_NET && !from_my_call) {
19529     //                char short_path[100];
19530     //fprintf(stderr,"Igate check o:%d f:%c myc:%s cf:%s ct:%s\n",operate_as_an_igate,from,my_callsign,call,addr); {
19531 
19532     //                shorten_path(path,short_path,sizeof(short_path));
19533     //sprintf(ipacket_message,"}%s>%s:%s:%s",call,path,addr9,message);
19534     //                sprintf(ipacket_message,"}%s>%s,TCPIP,%s*::%s:%s",call,short_path,my_callsign,addr9,message);
19535     //                output_igate_rf(call,addr,path,ipacket_message,port,third_party,NULL);
19536     //                igate_msgs_tx++;
19537     //            }
19538     //        }
19539     done = 1;
19540   }
19541   //--------------------------------------------------------------------------
19542   if (ok)
19543   {
19544     (void)data_add(STATION_CALL_DATA,
19545                    call,
19546                    path,
19547                    message,
19548                    from,
19549                    port,
19550                    NULL,
19551                    third_party,
19552                    0,  // Not a packet from my station
19553                    0); // Not my object/item
19554   }
19555 
19556   return(ok);
19557 }
19558 
19559 
19560 
19561 
19562 
19563 /*
19564  *  Decode APRS Information Field and dispatch it depending on the Data Type ID
19565  *
19566  *         call = Callsign or object/item name string
19567  *         path = Path string
19568  *      message = Info field (corrupted already if object/item packet)
19569  *       origin = Originating callsign if object/item, otherwise NULL
19570  *         from = DATA_VIA_LOCAL/DATA_VIA_TNC/DATA_VIA_NET/DATA_VIA_FILE
19571  *         port = Port number
19572  *  third_party = Set to one if third-party packet
19573  * orig_message = Unmodified info field
19574  *
19575  */
decode_info_field(char * call,char * path,char * message,char * origin,char from,int port,int third_party,char * orig_message)19576 void decode_info_field(char *call,
19577                        char *path,
19578                        char *message,
19579                        char *origin,
19580                        char from, int port,
19581                        int third_party,
19582                        char *orig_message)
19583 {
19584 
19585   char line[MAX_LINE_SIZE+1];
19586   int  ok_igate_net;
19587   int  ok_igate_rf;
19588   int  done, ignore;
19589   char data_id;
19590   int station_is_mine = 0;
19591   int object_is_mine = 0;
19592   char user_base_dir[MAX_VALUE];
19593 
19594   /* remember fixed format starts with ! and can be up to 24 chars in the message */ // ???
19595   if (debug_level & 1)
19596   {
19597     fprintf(stderr,"decode_info_field: c:%s p:%s m:%s f:%c o:%s\n",call,path,message,from,origin);
19598   }
19599   if (debug_level & 1)
19600   {
19601     fprintf(stderr,"decode_info_field: Past check\n");
19602   }
19603 
19604   done         = 0;       // if 1, packet was decoded
19605   ignore       = 0;       // if 1, don't treat undecoded packets as status text
19606   ok_igate_net = 0;       // if 1, send packet to internet
19607   ok_igate_rf  = 0;       // if 1, igate packet to RF if "from" is in nws-stations.txt
19608 
19609   if ( is_my_call(call, 1) )
19610   {
19611     station_is_mine++; // Station is controlled by me
19612   }
19613 
19614   if ( (message != NULL) && (strlen(message) > MAX_LINE_SIZE) )   // Overly long message, throw it away.
19615   {
19616     if (debug_level & 1)
19617     {
19618       fprintf(stderr,"decode_info_field: Overly long message.  Throwing it away.\n");
19619     }
19620     done = 1;
19621   }
19622   else if (message == NULL || strlen(message) == 0)        // we could have an empty message
19623   {
19624     (void)data_add(STATION_CALL_DATA,call,path,NULL,from,port,origin,third_party, station_is_mine, 0);
19625     done = 1;                                       // don't report it to internet
19626   }
19627 
19628   // special treatment for objects/items.
19629   if (!done && origin[0] != '\0')
19630   {
19631 
19632     // If station/object/item is owned by me (including SSID)
19633     if ( is_my_call(origin, 1) )
19634     {
19635       object_is_mine++;
19636     }
19637 
19638     if (message[0] == '*')      // set object
19639     {
19640       (void)data_add(APRS_OBJECT,call,path,message+1,from,port,origin,third_party, station_is_mine, object_is_mine);
19641       if (strlen(origin) > 0 && strncmp(origin,"INET",4)!=0)
19642       {
19643         ok_igate_net = 1;   // report it to internet
19644       }
19645       ok_igate_rf = 1;
19646       done = 1;
19647     }
19648 
19649     else if (message[0] == '!')     // set item
19650     {
19651       (void)data_add(APRS_ITEM,call,path,message+1,from,port,origin,third_party, station_is_mine, object_is_mine);
19652       if (strlen(origin) > 0 && strncmp(origin,"INET",4)!=0)
19653       {
19654         ok_igate_net = 1;   // report it to internet
19655       }
19656       ok_igate_rf = 1;
19657       done = 1;
19658     }
19659 
19660     else if (message[0] == '_')     // delete object/item
19661     {
19662       DataRow *p_station;
19663 
19664       delete_object(call);    // ?? does not vanish from map immediately !!???
19665 
19666       // If object was owned by me but another station is
19667       // transmitting it now, write entries into the
19668       // object.log file showing that we don't own this object
19669       // anymore.
19670       p_station = NULL;
19671       if (search_station_name(&p_station,call,1))
19672       {
19673         //                if ( (is_my_call(p_station->origin,1))  // If station was owned by me (including SSID)
19674         //                        && (!is_my_call(origin,1)) ) {  // But isn't now
19675         if (is_my_object_item(p_station)    // If station was owned by me (including SSID)
19676             && (!object_is_mine) )    // But isn't now
19677         {
19678           disown_object_item(call,origin);
19679         }
19680       }
19681       if (strlen(origin) > 0 && strncmp(origin,"INET",4)!=0)
19682       {
19683         ok_igate_net = 1;   // report it to internet
19684       }
19685       ok_igate_rf = 1;
19686       done = 1;
19687     }
19688   }
19689 
19690   if (!done)
19691   {
19692 
19693     data_id = message[0];           // look at the APRS Data Type ID (first char in information field)
19694     message += 1;                   // extract data ID from information field
19695     ok_igate_net = 1;               // as default report packet to internet
19696 
19697     if (debug_level & 1)
19698     {
19699       if (ok_igate_net)
19700       {
19701         fprintf(stderr,"decode_info_field: ok_igate_net can be read\n");
19702       }
19703     }
19704 
19705     switch (data_id)
19706     {
19707       case '=':   // Position without timestamp (with APRS messaging)
19708         if (debug_level & 1)
19709         {
19710           fprintf(stderr,"decode_info_field: = (position w/o timestamp)\n");
19711         }
19712 
19713         //WE7U
19714         // Need to check for weather info in this packet type as well?
19715 
19716         done = data_add(APRS_MSGCAP,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19717         ok_igate_rf = done;
19718         break;
19719 
19720       case '!':   // Position without timestamp (no APRS messaging) or Ultimeter 2000 WX
19721         if (debug_level & 1)
19722         {
19723           fprintf(stderr,"decode_info_field: ! (position w/o timestamp or Ultimeter 2000 WX)\n");
19724         }
19725         if (message[0] == '!' && is_xnum_or_dash(message+1,40))   // Ultimeter 2000 WX
19726         {
19727           done = data_add(APRS_WX3,call,path,message+1,from,port,origin,third_party, station_is_mine, 0);
19728         }
19729         else
19730         {
19731           done = data_add(APRS_FIXED,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19732         }
19733         ok_igate_rf = done;
19734         break;
19735 
19736       case '/':   // Position with timestamp (no APRS messaging)
19737         if (debug_level & 1)
19738         {
19739           fprintf(stderr,"decode_info_field: / (position w/timestamp)\n");
19740         }
19741 
19742         //WE7U
19743         // Need weather decode in this section similar to the '@' section
19744         // below.
19745 
19746         if ((toupper(message[14]) == 'N' || toupper(message[14]) == 'S') &&
19747             (toupper(message[24]) == 'W' || toupper(message[24]) == 'E'))   // uncompressed format
19748         {
19749           if (debug_level & 1)
19750           {
19751             fprintf(stderr,"decode_info_field: / (uncompressed position w/timestamp no messaging)\n");
19752           }
19753           if (message[29] == '/')
19754           {
19755             if (message[33] == 'g' && message[37] == 't')
19756             {
19757               done = data_add(APRS_WX1,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19758             }
19759             else
19760             {
19761               done = data_add(APRS_MOBILE,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19762             }
19763           }
19764           else
19765           {
19766             done = data_add(APRS_DF,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19767           }
19768         }
19769         else                                                  // compressed format
19770         {
19771           if (debug_level & 1)
19772           {
19773             fprintf(stderr,"decode_info_field: / (compressed position w/timestamp no messaging)\n");
19774           }
19775           if (message[16] >= '!' && message[16] <= 'z')       // csT is speed/course
19776           {
19777             if (message[20] == 'g' && message[24] == 't')   // Wx data
19778             {
19779               done = data_add(APRS_WX1,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19780             }
19781             else
19782             {
19783               done = data_add(APRS_MOBILE,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19784             }
19785           }
19786           else
19787           {
19788             done = data_add(APRS_DF,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19789           }
19790         }
19791         //                done = data_add(APRS_DOWN,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19792         ok_igate_rf = done;
19793         break;
19794 
19795       case '@':   // Position with timestamp (with APRS messaging)
19796         // DK7IN: could we need to test the message length first?
19797         if ((toupper(message[14]) == 'N' || toupper(message[14]) == 'S') &&
19798             (toupper(message[24]) == 'W' || toupper(message[24]) == 'E'))         // uncompressed format
19799         {
19800           if (debug_level & 1)
19801           {
19802             fprintf(stderr,"decode_info_field: @ (uncompressed position w/timestamp)\n");
19803           }
19804           if (message[29] == '/')
19805           {
19806             if (message[33] == 'g' && message[37] == 't')
19807             {
19808               done = data_add(APRS_WX1,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19809             }
19810             else
19811             {
19812               done = data_add(APRS_MOBILE,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19813             }
19814           }
19815           else
19816           {
19817             done = data_add(APRS_DF,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19818           }
19819         }
19820         else                                                  // compressed format
19821         {
19822           if (debug_level & 1)
19823           {
19824             fprintf(stderr,"decode_info_field: @ (compressed position w/timestamp)\n");
19825           }
19826           if (message[16] >= '!' && message[16] <= 'z')       // csT is speed/course
19827           {
19828             if (message[20] == 'g' && message[24] == 't')   // Wx data
19829             {
19830               done = data_add(APRS_WX1,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19831             }
19832             else
19833             {
19834               done = data_add(APRS_MOBILE,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19835             }
19836           }
19837           else
19838           {
19839             done = data_add(APRS_DF,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19840           }
19841         }
19842         ok_igate_rf = done;
19843         break;
19844 
19845       case '[':   // Maidenhead grid locator beacon (obsolete- but used for meteor scatter)
19846         done = data_add(APRS_GRID,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19847         ok_igate_rf = done;
19848         break;
19849       case 0x27:  // Mic-E  Old GPS data (or current GPS data in Kenwood TM-D700)
19850       case 0x60:  // Mic-E  Current GPS data (but not used in Kennwood TM-D700)
19851         //case 0x1c:// Mic-E  Current GPS data (Rev. 0 beta units only)
19852         //case 0x1d:// Mic-E  Old GPS data (Rev. 0 beta units only)
19853         if (debug_level & 1)
19854         {
19855           fprintf(stderr,"decode_info_field: 0x27 or 0x60 (Mic-E)\n");
19856         }
19857         done = decode_Mic_E(call,path,message,from,port,third_party);
19858         ok_igate_rf = done;
19859         break;
19860 
19861       case '_':   // Positionless weather data                [APRS Reference, chapter 12]
19862         if (debug_level & 1)
19863         {
19864           fprintf(stderr,"decode_info_field: _ (positionless wx data)\n");
19865         }
19866         done = data_add(APRS_WX2,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19867         ok_igate_rf = done;
19868         break;
19869 
19870       case '#':   // Peet Bros U-II Weather Station (km/h)    [APRS Reference, chapter 12]
19871         if (debug_level & 1)
19872         {
19873           fprintf(stderr,"decode_info_field: # (peet bros u-II wx station)\n");
19874         }
19875         if (is_xnum_or_dash(message,13))
19876         {
19877           done = data_add(APRS_WX4,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19878         }
19879         ok_igate_rf = done;
19880         break;
19881 
19882       case '*':   // Peet Bros U-II Weather Station (mph)
19883         if (debug_level & 1)
19884         {
19885           fprintf(stderr,"decode_info_field: * (peet bros u-II wx station)\n");
19886         }
19887         if (is_xnum_or_dash(message,13))
19888         {
19889           done = data_add(APRS_WX6,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19890         }
19891         ok_igate_rf = done;
19892         break;
19893 
19894       case '$':   // Raw GPS data or Ultimeter 2000
19895         if (debug_level & 1)
19896         {
19897           fprintf(stderr,"decode_info_field: $ (raw gps or ultimeter 2000)\n");
19898         }
19899         if (strncmp("ULTW",message,4) == 0 && is_xnum_or_dash(message+4,44))
19900         {
19901           done = data_add(APRS_WX5,call,path,message+4,from,port,origin,third_party, station_is_mine, 0);
19902         }
19903         else if (strncmp("GPGGA",message,5) == 0)
19904         {
19905           done = data_add(GPS_GGA,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19906         }
19907         else if (strncmp("GPRMC",message,5) == 0)
19908         {
19909           done = data_add(GPS_RMC,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19910         }
19911         else if (strncmp("GPGLL",message,5) == 0)
19912         {
19913           done = data_add(GPS_GLL,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19914         }
19915         else
19916         {
19917           // handle VTG and WPT too  (APRS Ref p.25)
19918         }
19919         ok_igate_rf = done;
19920         break;
19921 
19922       case ':':   // Message
19923         if (debug_level & 1)
19924         {
19925           fprintf(stderr,"decode_info_field: : (message)\n");
19926         }
19927 
19928         // Do message logging if that feature is enabled.
19929         if (log_message_data && from != DATA_VIA_FILE)
19930         {
19931           char temp_msg[MAX_MESSAGE_LENGTH+1];
19932 
19933           xastir_snprintf(temp_msg,
19934                           sizeof(temp_msg),
19935                           "%s>%s:%s",
19936                           call,
19937                           path,
19938                           orig_message);
19939           log_data( get_user_base_dir(LOGFILE_MESSAGE, user_base_dir,
19940                                       sizeof(user_base_dir)),
19941                     temp_msg );
19942         }
19943 
19944         //fprintf(stderr,"Calling decode_message\n");
19945         done = decode_message(call,path,message,from,port,third_party);
19946         //fprintf(stderr,"Back from decode_message\n");
19947         // there could be messages I should not retransmit to internet... ??? Queries to me...
19948         break;
19949 
19950       case '>':   // Status                                   [APRS Reference, chapter 16]
19951         if (debug_level & 1)
19952         {
19953           fprintf(stderr,"decode_info_field: > (status)\n");
19954         }
19955         done = data_add(APRS_STATUS,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19956         ok_igate_rf = done;
19957         break;
19958 
19959       case '?':   // Query
19960         if (debug_level & 1)
19961         {
19962           fprintf(stderr,"decode_info_field: ? (query)\n");
19963         }
19964         done = process_query(call,path,message,from,port,third_party);
19965         ignore = 1;     // don't treat undecoded packets as status text
19966         break;
19967 
19968       case 'T':   // Telemetry data                           [APRS Reference, chapter 13]
19969         // We treat these as status packets currently.
19970         ok_igate_rf = 1;
19971         if (debug_level & 1)
19972         {
19973           fprintf(stderr,"decode_info_field: T (telem)\n");
19974         }
19975         done = data_add(APRS_STATUS,call,path,message,from,port,origin,third_party, station_is_mine, 0);
19976         break;
19977 
19978       case '{':   // User-defined APRS packet format     //}
19979         // We treat these as status packets currently.
19980         ok_igate_rf = 1;
19981         break;
19982 
19983       case '<':   // Station capabilities                     [APRS Reference, chapter 15]
19984         if (debug_level & 1)
19985         {
19986           fprintf(stderr,"decode_info_field: ~,<\n");
19987         }
19988         //
19989         // We could tweak the Incoming Data dialog to add
19990         // filter togglebuttons.  One such toggle could be
19991         // "Station Capabilities".  We'd then have a usable
19992         // dialog for displaying things like ?IGATE?
19993         // responses.  In this case we wouldn't have to do
19994         // anything special with the packet for decoding,
19995         // just let it hit the default block below for
19996         // putting them into the status field of the record.
19997         // One downside is that we'd only be able to catch
19998         // new station capability records in that dialog.
19999         // The only way to look at past capability records
20000         // would be the Station Info dialog for each
20001         // station.
20002         //
20003         //fprintf(stderr,"%10s:  %s\n", call, message);
20004 
20005         // Don't set "done" as we want these to appear in
20006         // the status text for the record.
20007         break;
20008 
20009       case '%':   // Agrelo DFJr / MicroFinder Radio Direction Finding
20010 
20011         if (debug_level & 1)
20012         {
20013           fprintf(stderr,"decode_info_field: %%\n");
20014         }
20015 
20016         // Here is where we'd add a call to an RDF decode
20017         // function so that we could display vectors on the
20018         // map for each RDF position.
20019 
20020         //
20021         // Agrelo format:  "%XXX/Q<cr>"
20022         //
20023         // "XXX" is relative bearing to the signal (000-359).  Careful here:
20024         // At least one unit reports in magnetic instead of relative
20025         // degrees.  "000" means no direction info available, 360 means true
20026         // north.
20027         //
20028         // "Q" is bearing quality (0-9).  0 = unsuitable.  9 = manually
20029         // entered.  1-8 = varying quality with 8 being the best.
20030         //
20031         // I've also seen these formats, which may not be Agrelo compatible:
20032         //
20033         //      "%136.0/9"
20034         //      "%136.0/8/158.0" (That last number is magnetic bearing)
20035         //
20036         // These sentences may be sent MULTIPLE times per second, like 20 or
20037         // more!  If we decide to average readings, we'll need to dump our
20038         // averages and start over if our course changes.
20039         //
20040 
20041         // Check for Agrelo format:
20042         if (    strlen(message) >= 5
20043                 && is_num_chr(message[0])   // "%136/9"
20044                 && is_num_chr(message[1])
20045                 && is_num_chr(message[2])
20046                 && message[3] == '/'
20047                 && is_num_chr(message[4]) )
20048         {
20049 
20050           fprintf(stderr,
20051                   "Type 1 RDF packet from call: %s\tBearing: %c%c%c\tQuality: %c\n",
20052                   call,
20053                   message[0],
20054                   message[1],
20055                   message[2],
20056                   message[4]);
20057 
20058         }
20059 
20060         // Check for extended formats (not
20061         // Agrelo-compatible):
20062         else if (strlen(message) >= 13
20063                  && is_num_chr(message[0])   // "%136.0/8/158.0"
20064                  && is_num_chr(message[1])
20065                  && is_num_chr(message[2])
20066                  && message[3] == '.'
20067                  && is_num_chr(message[4])
20068                  && message[5] == '/'
20069                  && is_num_chr(message[6])
20070                  && message[7] == '/'
20071                  && is_num_chr(message[8])
20072                  && is_num_chr(message[9])
20073                  && is_num_chr(message[10])
20074                  && message[11] == '.'
20075                  && is_num_chr(message[12]) )
20076         {
20077 
20078           fprintf(stderr,
20079                   "Type 3 RDF packet from call: %s\tBearing: %c%c%c%c%c\tQuality: %c\tMag Bearing: %c%c%c%c%c\n",
20080                   call,
20081                   message[0],
20082                   message[1],
20083                   message[2],
20084                   message[3],
20085                   message[4],
20086                   message[6],
20087                   message[8],
20088                   message[9],
20089                   message[10],
20090                   message[11],
20091                   message[12]);
20092         }
20093 
20094         // Check for extended formats (not
20095         // Agrelo-compatible):
20096         else if (strlen(message) >= 7
20097                  && is_num_chr(message[0])   // "%136.0/9"
20098                  && is_num_chr(message[1])
20099                  && is_num_chr(message[2])
20100                  && message[3] == '.'
20101                  && is_num_chr(message[4])
20102                  && message[5] == '/'
20103                  && is_num_chr(message[6]) )
20104         {
20105 
20106           fprintf(stderr,
20107                   "Type 2 RDF packet from call: %s\tBearing: %c%c%c%c%c\tQuality: %c\n",
20108                   call,
20109                   message[0],
20110                   message[1],
20111                   message[2],
20112                   message[3],
20113                   message[4],
20114                   message[6]);
20115         }
20116 
20117         // Don't set "done" as we want these to appear in
20118         // the status text for the record until we get the
20119         // full decoding for this type of packet coded up.
20120         break;
20121 
20122       case '~':   // UI-format messages, not relevant for APRS ("Do not use" in Reference)
20123       case ',':   // Invalid data or test packets             [APRS Reference, chapter 19]
20124       case '&':   // Reserved -- Map Feature
20125         if (debug_level & 1)
20126         {
20127           fprintf(stderr,"decode_info_field: ~,&\n");
20128         }
20129         ignore = 1;     // Don't treat undecoded packets as status text
20130         break;
20131     }
20132 
20133     if (debug_level & 1)
20134     {
20135       if (done)
20136       {
20137         fprintf(stderr,"decode_info_field: done = 1\n");
20138       }
20139       else
20140       {
20141         fprintf(stderr,"decode_info_field: done = 0\n");
20142       }
20143       if (ok_igate_net)
20144       {
20145         fprintf(stderr,"decode_info_field: ok_igate_net can be read 2\n");
20146       }
20147     }
20148 
20149     if (debug_level & 1)
20150     {
20151       fprintf(stderr,"decode_info_field: done with big switch\n");
20152     }
20153 
20154     // Add most remaining data to the station record as status
20155     // info
20156     //
20157     if (!done && !ignore)           // Other Packets        [APRS Reference, chapter 19]
20158     {
20159       done = data_add(OTHER_DATA,call,path,message-1,from,port,origin,third_party, station_is_mine, 0);
20160       if (debug_level & 1)
20161       {
20162         fprintf(stderr,"decode_info_field: done with data_add(OTHER_DATA)\n");
20163       }
20164     }
20165 
20166     if (!done)                      // data that we do ignore...
20167     {
20168       //fprintf(stderr,"decode_info_field: not decoding info: Call:%s ID:%c Msg:|%s|\n",call,data_id,message);
20169       ok_igate_net = 0;           // don't put data on internet
20170       if (debug_level & 1)
20171       {
20172         fprintf(stderr,"decode_info_field: done with ignored data\n");
20173       }
20174     }
20175   }
20176 
20177   if (third_party)
20178   {
20179     ok_igate_net = 0;  // don't put third party traffic on internet
20180   }
20181 
20182   //    if (is_my_call(call,1)) // Check SSID as well
20183   if (station_is_mine)
20184   {
20185     ok_igate_net = 0;  // don't put my data on internet     ???
20186   }
20187 
20188   if (ok_igate_net)
20189   {
20190 
20191     if (debug_level & 1)
20192     {
20193       fprintf(stderr,"decode_info_field: ok_igate_net start\n");
20194     }
20195 
20196     if ( (from == DATA_VIA_TNC) // Came in via a TNC
20197          && (strlen(orig_message) > 0) )   // Not empty
20198     {
20199 
20200       // Here's where we inject our own callsign like this:
20201       // "WE7U-15,I" in order to provide injection ID for our
20202       // igate.
20203       xastir_snprintf(line,
20204                       sizeof(line),
20205                       "%s>%s,%s,I:%s",
20206                       (strlen(origin)) ? origin : call,
20207                       path,
20208                       my_callsign,
20209                       orig_message);
20210 
20211       //fprintf(stderr,"decode_info_field: IGATE>NET %s\n",line);
20212       output_igate_net(line, port, third_party);
20213     }
20214   }
20215 
20216   // Attempt to gate to RF only if the following conditions are
20217   // met:
20218   //
20219   //   *) ok_igate_rf flag is set.
20220   //   *) Not my exact callsign.
20221   //   *) Packet was from the INET, not local RF
20222   //   *) The "from" call matches a line in data/nws-stations.txt,
20223   //      verified by igate.c:check_NWS_stations().
20224   //
20225   // The output_igate_rf() function will also do some checks on
20226   // the packet before allowing it to be igated, including a
20227   // dupe-check.
20228   //
20229   // Callsign check here checks SSID as well
20230   //    if (ok_igate_rf && !is_my_call(call,1) && from == DATA_VIA_NET) {
20231   if (ok_igate_rf
20232       && !station_is_mine
20233       && from == DATA_VIA_NET)
20234   {
20235 
20236     char ipacket_message[300];
20237     char short_path[100];
20238 
20239     shorten_path(path,short_path,sizeof(short_path));
20240 
20241     xastir_snprintf(ipacket_message,
20242                     sizeof(ipacket_message),
20243                     "}%s>%s,TCPIP,%s*:%s",
20244                     (strlen(origin)) ? origin : call,
20245                     short_path,
20246                     my_callsign,
20247                     orig_message);
20248 
20249     // If origin, pass "call" to output_igate_rf() as the last
20250     // parameter.  This would be the object/item name.
20251     output_igate_rf((strlen(origin)) ? origin : call,
20252                     (strlen(origin)) ? origin : call,
20253                     path,
20254                     ipacket_message,
20255                     port,
20256                     third_party,
20257                     (strlen(origin)) ? call : NULL);
20258 
20259     //fprintf(stderr,"decode_info_field: IGATE>RF %s\n",ipacket_message);
20260   }
20261 
20262   if (debug_level & 1)
20263   {
20264     fprintf(stderr,"decode_info_field: done\n");
20265   }
20266 }
20267 
20268 
20269 
20270 
20271 
20272 /*
20273  *  Extract object or item data from information field before processing
20274  *
20275  *  Returns 1 if valid object found, else returns 0.
20276  *
20277  */
extract_object(char * call,char ** info,char * origin)20278 int extract_object(char *call, char **info, char *origin)
20279 {
20280   int ok, i;
20281 
20282   // Object and Item Reports     [APRS Reference, chapter 11]
20283   ok = 0;
20284   // todo: add station originator to database
20285   if ((*info)[0] == ';')                      // object
20286   {
20287     // fixed 9 character object name with any printable ASCII character
20288     if (strlen((*info)) > 1+9)
20289     {
20290       substr(call,(*info)+1,9);           // extract object name
20291       (*info) = (*info) + 10;
20292       // Remove leading spaces ? They look bad, but are allowed by the APRS Reference ???
20293       (void)remove_trailing_spaces(call);
20294       if (valid_object(call))
20295       {
20296         // info length is at least 1
20297         ok = 1;
20298       }
20299     }
20300   }
20301   else if ((*info)[0] == ')')               // item
20302   {
20303     // 3 - 9 character item name with any printable ASCII character
20304     if (strlen((*info)) > 1+3)
20305     {
20306       for (i = 1; i <= 9; i++)
20307       {
20308         if ((*info)[i] == '!' || (*info)[i] == '_')
20309         {
20310           call[i-1] = '\0';
20311           break;
20312         }
20313         call[i-1] = (*info)[i];
20314       }
20315       call[9] = '\0';  // In case we never saw '!' || '_'
20316       (*info) = &(*info)[i];
20317       // Remove leading spaces ? They look bad, but are allowed by the APRS Reference ???
20318       //(void)remove_trailing_spaces(call);   // This statement messed up our searching!!! Don't use it!
20319       if (valid_object(call))
20320       {
20321         // info length is at least 1
20322         ok = 1;
20323       }
20324     }
20325   }
20326   else
20327   {
20328     fprintf(stderr,"Not an object, nor an item!!! call=%s, info=%s, origin=%s.\n",
20329             call, *info, origin);
20330   }
20331   return(ok);
20332 }
20333 
20334 
20335 
20336 
20337 
20338 /*
20339  *  Extract third-party traffic from information field before processing
20340  */
extract_third_party(char * call,char * path,int path_size,char ** info,char * origin,int origin_size)20341 int extract_third_party(char *call,
20342                         char *path,
20343                         int path_size,
20344                         char **info,
20345                         char *origin,
20346                         int origin_size)
20347 {
20348   int ok;
20349   char *p_call;
20350   char *p_path;
20351 
20352   p_call = NULL;                              // to make the compiler happy...
20353   p_path = NULL;                              // to make the compiler happy...
20354   ok = 0;
20355   if (!is_my_call(call,1))   // Check SSID also
20356   {
20357     // todo: add reporting station call to database ??
20358     //       but only if not identical to reported call
20359     (*info) = (*info) +1;                   // strip '}' character
20360     p_call = strtok((*info),">");           // extract call
20361     if (p_call != NULL)
20362     {
20363       p_path = strtok(NULL,":");          // extract path
20364       if (p_path != NULL)
20365       {
20366         (*info) = strtok(NULL,"");      // rest is information field
20367         if ((*info) != NULL)            // the above looks dangerous, but works on same string
20368           if (strlen(p_path) < 100)
20369           {
20370             ok = 1;  // we have found all three components
20371           }
20372       }
20373     }
20374   }
20375 
20376   if ((debug_level & 1) && !ok)
20377   {
20378     fprintf(stderr,"extract_third_party: invalid format from %s\n",call);
20379   }
20380 
20381   if (ok)
20382   {
20383 
20384     xastir_snprintf(path,
20385                     path_size,
20386                     "%s",
20387                     p_path);
20388 
20389     ok = valid_path(path);                  // check the path and convert it to TAPR format
20390     // Note that valid_path() also removes igate injection identifiers
20391 
20392     if ((debug_level & 1) && !ok)
20393     {
20394       char filtered_data[MAX_LINE_SIZE + 1];
20395 
20396       xastir_snprintf(filtered_data,
20397                       sizeof(filtered_data),
20398                       "%s",
20399                       path);
20400       makePrintable(filtered_data);
20401       fprintf(stderr,"extract_third_party: invalid path: %s\n",filtered_data);
20402     }
20403   }
20404 
20405   if (ok)                                           // check callsign
20406   {
20407     (void)remove_trailing_asterisk(p_call);       // is an asterisk valid here ???
20408     if (valid_inet_name(p_call,(*info),origin,origin_size))   // accept some of the names used in internet
20409     {
20410       // Treat it as object with special origin
20411       xastir_snprintf(call,
20412                       MAX_CALLSIGN+1,
20413                       "%s",
20414                       p_call);
20415     }
20416     else if (valid_call(p_call))                // accept real AX.25 calls
20417     {
20418       xastir_snprintf(call,
20419                       MAX_CALLSIGN+1,
20420                       "%s",
20421                       p_call);
20422     }
20423     else
20424     {
20425       ok = 0;
20426       if (debug_level & 1)
20427       {
20428         char filtered_data[MAX_LINE_SIZE + 1];
20429 
20430         xastir_snprintf(filtered_data,
20431                         sizeof(filtered_data),
20432                         "%s",
20433                         p_call);
20434         makePrintable(filtered_data);
20435         fprintf(stderr,"extract_third_party: invalid call: %s\n",filtered_data);
20436       }
20437     }
20438   }
20439   return(ok);
20440 }
20441 
20442 
20443 
20444 
20445 
20446 /*
20447  *  Extract text inserted by TNC X-1J4 from start of info line
20448  */
extract_TNC_text(char * info)20449 void extract_TNC_text(char *info)
20450 {
20451   int i,j,len;
20452 
20453   if (strncasecmp(info,"thenet ",7) == 0)     // 1st match
20454   {
20455     len = strlen(info)-1;
20456     for (i=7; i<len; i++)
20457     {
20458       if (info[i] == ')')
20459       {
20460         break;
20461       }
20462     }
20463     len++;
20464     if (i>7 && info[i] == ')' && info[i+1] == ' ')          // found
20465     {
20466       i += 2;
20467       for (j=0; i<=len; i++,j++)
20468       {
20469         info[j] = info[i];
20470       }
20471     }
20472   }
20473 }
20474 
20475 
20476 
20477 
20478 
20479 //WE7U2
20480 // We feed a raw 7-byte string into this routine.  It decodes the
20481 // callsign-SSID and tells us whether there are more callsigns after
20482 // this.  If the "asterisk" input parameter is nonzero it'll add an
20483 // asterisk to the callsign if it has been digipeated.  This
20484 // function is called by the decode_ax25_header() function.
20485 //
20486 // Inputs:  string          Raw input string
20487 //          asterisk        1 = add "digipeated" asterisk
20488 //
20489 // Outputs: callsign        Processed string
20490 //          returned int    1=more callsigns follow, 0=end of address field
20491 //
decode_ax25_address(char * string,char * callsign,int asterisk)20492 int decode_ax25_address(char *string, char *callsign, int asterisk)
20493 {
20494   int i,j;
20495   char ssid;
20496   char t;
20497   int more = 0;
20498   int digipeated = 0;
20499 
20500   // Shift each of the six callsign characters right one bit to
20501   // convert to ASCII.  We also get rid of the extra spaces here.
20502   j = 0;
20503   for (i = 0; i < 6; i++)
20504   {
20505     t = ((unsigned char)string[i] >> 1) & 0x7f;
20506     if (t != ' ')
20507     {
20508       callsign[j++] = t;
20509     }
20510   }
20511 
20512   // Snag out the SSID byte to play with.  We need more than just
20513   // the 4 SSID bits out of it.
20514   ssid = (unsigned char)string[6];
20515 
20516   // Check the digipeat bit
20517   if ( (ssid & 0x80) && asterisk)
20518   {
20519     digipeated++;  // Has been digipeated
20520   }
20521 
20522   // Check whether it is the end of the address field
20523   if ( !(ssid & 0x01) )
20524   {
20525     more++;  // More callsigns to come after this one
20526   }
20527 
20528   // Snag the four SSID bits
20529   ssid = (ssid >> 1) & 0x0f;
20530 
20531   // Construct the SSID number and add it to the end of the
20532   // callsign if non-zero.  If it's zero we don't add it.
20533   if (ssid)
20534   {
20535     callsign[j++] = '-';
20536     if (ssid > 9)
20537     {
20538       callsign[j++] = '1';
20539     }
20540     ssid = ssid % 10;
20541     callsign[j++] = '0' + ssid;
20542   }
20543 
20544   // Add an asterisk if the packet has been digipeated through
20545   // this callsign
20546   if (digipeated)
20547   {
20548     callsign[j++] = '*';
20549   }
20550 
20551   // Terminate the string
20552   callsign[j] = '\0';
20553 
20554   return(more);
20555 }
20556 
20557 
20558 
20559 
20560 
20561 // Function which receives raw AX.25 packets from a KISS interface and
20562 // converts them to a printable TAPR-2 (more or less) style string.
20563 // We receive the packet with a KISS Frame End character at the
20564 // beginning and a "\0" character at the end.  We can end up with
20565 // multiple asterisks, one for each callsign that the packet was
20566 // digipeated through.  A few other TNC's put out this same sort of
20567 // format.
20568 //
20569 // Note about KISS & CRC's:  The TNC checks the CRC.  If bad, it
20570 // drops the packet.  If good, it sends it to the computer WITHOUT
20571 // the CRC bytes.  There's no way at the computer end to check
20572 // whether the packet was corrupted over the serial channel between
20573 // the TNC and the computer.  Upon sending a KISS packet to the TNC,
20574 // the TNC itself adds the CRC bytes back on before sending it over
20575 // the air.  In Xastir we can just assume that we're getting
20576 // error-free packets from the TNC, ignoring possible corruption
20577 // over the serial line.
20578 //
20579 // Some versions of KISS can encode the radio channel (for
20580 // multi-port TNC's) in the command byte.  How do we know we're
20581 // running those versions of KISS though?  Here are the KISS
20582 // variants that I've been able to discover to date:
20583 //
20584 // KISS               No CRC, one radio port
20585 //
20586 // SMACK              16-bit CRC, multiport TNC's
20587 //
20588 // KISS-CRC
20589 //
20590 // 6-PACK
20591 //
20592 // KISS Multi-drop (Kantronics) 8-bit XOR Checksum, multiport TNC's (AGWPE compatible)
20593 // BPQKISS (Multi-drop)         8-bit XOR Checksum, multiport TNC's
20594 // XKISS (Kantronics)           8-bit XOR Checksum, multiport TNC's
20595 //
20596 // JKISS              (AGWPE and BPQ32 compatible)
20597 //
20598 // MKISS              Linux driver which supports KISS/BPQ and
20599 //                    hardware handshaking?  Also Paccomm command to
20600 //                    immediately enter KISS mode.
20601 //
20602 // FlexKISS           -,
20603 // FlexCRC            -|-- These are all the same!
20604 // RMNC-KISS          -|
20605 // CRC-RMNC           -'
20606 //
20607 //
20608 // It appears that none of the above protocols implement any form of
20609 // hardware flow control.
20610 //
20611 //
20612 // Compare this function with interface.c:process_ax25_packet() to
20613 // see if we're missing anything important.
20614 //
20615 //
20616 // Inputs:  data_string         Raw string (must be MAX_LINE_SIZE or bigger)
20617 //          length              Length of raw string (may get changed here)
20618 //
20619 // Outputs: int                 0 if it is a bad packet,
20620 //                              1 if it is good
20621 //          data_string         Processed string
20622 //
decode_ax25_header(unsigned char * data_string,int * length)20623 int decode_ax25_header(unsigned char *data_string, int *length)
20624 {
20625   char temp[20];
20626   char result[MAX_LINE_SIZE+100];
20627   char dest[15];
20628   int i, ptr;
20629   char callsign[15];
20630   char more;
20631   char num_digis = 0;
20632 
20633 
20634   // Do we have a string at all?
20635   if (data_string == NULL)
20636   {
20637     return(0);
20638   }
20639 
20640   // Drop the packet if it is too long.  Note that for KISS packets
20641   // we can't use strlen() as there can be 0x00 bytes in the
20642   // data itself.
20643   if (*length > 1024)
20644   {
20645     data_string[0] = '\0';
20646     *length = 0;
20647     return(0);
20648   }
20649 
20650   // Start with an empty string for the result
20651   result[0] = '\0';
20652 
20653   ptr = 0;
20654 
20655   // Process the destination address
20656   for (i = 0; i < 7; i++)
20657   {
20658     temp[i] = data_string[ptr++];
20659   }
20660   temp[7] = '\0';
20661   more = decode_ax25_address(temp, callsign, 0); // No asterisk
20662   xastir_snprintf(dest,sizeof(dest),"%s",callsign);
20663 
20664   // Process the source address
20665   for (i = 0; i < 7; i++)
20666   {
20667     temp[i] = data_string[ptr++];
20668   }
20669   temp[7] = '\0';
20670   more = decode_ax25_address(temp, callsign, 0); // No asterisk
20671 
20672   // Store the two callsigns we have into "result" in the correct
20673   // order
20674   xastir_snprintf(result,sizeof(result),"%s>%s",callsign,dest);
20675 
20676   // Process the digipeater addresses (if any)
20677   num_digis = 0;
20678   while (more && num_digis < 8)
20679   {
20680     for (i = 0; i < 7; i++)
20681     {
20682       temp[i] = data_string[ptr++];
20683     }
20684     temp[7] = '\0';
20685 
20686     more = decode_ax25_address(temp, callsign, 1); // Add asterisk
20687     strncat(result,
20688             ",",
20689             sizeof(result) - 1 - strlen(result));
20690     strncat(result,
20691             callsign,
20692             sizeof(result) - 1 - strlen(result));
20693     num_digis++;
20694   }
20695 
20696   strncat(result,
20697           ":",
20698           sizeof(result) - 1 - strlen(result));
20699 
20700 
20701   // Check the Control and PID bytes and toss packets that are
20702   // AX.25 connect/disconnect or information packets.  We only
20703   // want to process UI packets in Xastir.
20704 
20705 
20706   // Control byte should be 0x03 (UI Frame).  Strip the poll-bit
20707   // from the PID byte before doing the comparison.
20708   if ( (data_string[ptr++] & (~0x10)) != 0x03)
20709   {
20710     return(0);
20711   }
20712 
20713 
20714   // PID byte should be 0xf0 (normal AX.25 text)
20715   if (data_string[ptr++] != 0xf0)
20716   {
20717     return(0);
20718   }
20719 
20720 
20721   // WE7U:  We get multiple concatenated KISS packets sometimes.  Look
20722   // for that here and flag when it happens (so we know about it and
20723   // can fix it someplace earlier in the process).  Correct the
20724   // current packet so we don't get the extra garbage tacked onto the
20725   // end.
20726   for (i = ptr; i < *length; i++)
20727   {
20728     if (data_string[i] == KISS_FEND)
20729     {
20730       fprintf(stderr,"***Found concatenated KISS packets:***\n");
20731       data_string[i] = '\0';    // Truncate the string
20732       break;
20733     }
20734   }
20735 
20736   // Add the Info field to the decoded header info
20737   strncat(result,
20738           (char *)(&data_string[ptr]),
20739           sizeof(result) - 1 - strlen(result));
20740 
20741   // Copy the result onto the top of the input data.  Note that
20742   // the length can sometimes be longer than the input string, so
20743   // we can't just use the "length" variable here or we'll
20744   // truncate our string.  Make sure the data_string variable is
20745   // MAX_LINE_SIZE or bigger.
20746   //
20747   memcpy(data_string, result, MAX_LINE_SIZE);
20748   data_string[MAX_LINE_SIZE-1] = '\0';  // Terminate string
20749 
20750   // Write out the new length
20751   *length = strlen(result);
20752 
20753   //fprintf(stderr,"%s\n",data_string);
20754 
20755   return(1);
20756 }
20757 
20758 
20759 
20760 
20761 
20762 // RELAY the packet back out onto RF if received on a port with
20763 // digipeat enabled and the packet header has a non-digipeated RELAY
20764 // or my_callsign entry.  This is for AX.25 kernel networking ports
20765 // or Serial KISS TNC ports only:  Regular serial TNC's have these
20766 // features enabled/disabled through the startup/shutdown files.
20767 //
20768 // Adding asterisks:
20769 // Keep whatever digipeated fields have already been set.  If
20770 // there's a "RELAY", "WIDE1-1", or "my_callsign" entry that hasn't
20771 // been digipeated yet, change it to "my_callsign*".
20772 //
20773 // This might be much easier to code into the routine that first
20774 // receives the packet (for Serial KISS TNC's).  There we'd have
20775 // access to every digipeated bit directly instead of parsing
20776 // asterisks out of a string.
20777 //
20778 // NOTE:  We don't handle this case properly:  Multiple
20779 // RELAY's/WIDE1-1's or my_callsign's in the path, where one of the
20780 // earlier matching callsigns has been digipeated, but a later one
20781 // has not.  We'll find the first matching callsign and the last
20782 // digi, and we won't relay the packet.  This probably won't happen
20783 // much in the real world.
20784 //
20785 // We could also do premptive digipeating here and skip over
20786 // callsigns that haven't been digipeated yet.  Should we set the
20787 // digipeated bits on everything before it?  Probably.  Either that
20788 // or remove the callsigns ahead of it in the list that weren't
20789 // digipeated.
20790 //
relay_digipeat(char * call,char * path,char * info,int port)20791 void relay_digipeat(char *call, char *path, char *info, int port)
20792 {
20793   char new_path[110+1];
20794   char new_digi[MAX_CALLSIGN+2];  // Need extra for '*' storage
20795   int  ii, jj;
20796   int  done;
20797   char destination[MAX_CALLSIGN+1];
20798 
20799 #define MAX_RELAY_SUBSTRINGS 10
20800   char *Substring[MAX_RELAY_SUBSTRINGS];  // Pointers to substrings parsed by split_string()
20801 
20802   // Pointers to all of the possible calls we with to digipeat by
20803   char *Relay_Calls[MAX_RELAY_DIGIPEATER_CALLS];
20804 
20805   char temp_string[MAX_LINE_SIZE+1];
20806 
20807   // Check whether transmits are disabled globally
20808   if (transmit_disable)
20809   {
20810     return;
20811   }
20812 
20813   // Check whether relay_digipeat has been enabled for this interface.
20814   // If not, get out while the gettin's good.
20815   if (devices[port].relay_digipeat != 1)
20816   {
20817     return;
20818   }
20819 
20820   // Check whether transmit has been enabled for this interface.
20821   // If not, get out while the gettin's good.
20822   if (devices[port].transmit_data != 1)
20823   {
20824     return;
20825   }
20826 
20827   // Check for the only four types of interfaces where we might
20828   // want to do RELAY digipeating.  If not one of these, go
20829   // bye-bye.
20830   if (       (devices[port].device_type != DEVICE_SERIAL_KISS_TNC)
20831              && (devices[port].device_type != DEVICE_SERIAL_MKISS_TNC)
20832              && (devices[port].device_type != DEVICE_AX25_TNC)
20833              && (devices[port].device_type != DEVICE_NET_AGWPE) )
20834   {
20835     return;
20836   }
20837 
20838 
20839   // Check to see if this is my own transmitted packet (in some
20840   // cases you get your own packets back from interfaces)
20841   if (!strcasecmp(call, my_callsign))
20842   {
20843     //fprintf(stderr,"relay_digipeat: packet was mine, don't digipeat it!\n");
20844     return;
20845   }
20846 
20847 
20848   // Make a copy of the incoming path.  The string passed to
20849   // split_string() gets destroyed.
20850   xastir_snprintf(temp_string,
20851                   sizeof(temp_string),
20852                   "%s",
20853                   path);
20854   split_string(temp_string, Substring, MAX_RELAY_SUBSTRINGS, ',');
20855   // Each element in the path is now pointed to by a char ptr in
20856   // the Substring array.  If a NULL is found in the array, that's
20857   // the end of the path.
20858 
20859 
20860   if (Substring[0] == NULL)
20861   {
20862     // Something's wrong!  Couldn't find anything in the path
20863     // string, not even a destination callsign?
20864     //fprintf(stderr, "\t\tNo path: %s\n", path);
20865     return;
20866   }
20867   else    // Save the destination callsign away
20868   {
20869     xastir_snprintf(destination,
20870                     sizeof(destination),
20871                     "%s",
20872                     Substring[0]);
20873     //fprintf(stderr,"Destination: %s\n",destination);
20874   }
20875   // We'll skip the first call in the path (pointed to by
20876   // Substring[0]) in the loops below.  That's the destination
20877   // call and we don't want to look for RELAY or my_callsign
20878   // there.
20879 
20880 
20881   // Check to see if we just ran out of path
20882   if (Substring[1] == NULL)   // No digipeaters listed
20883   {
20884     //fprintf(stderr,"relay_digipeat: ran out of path, don't digipeat it!\n");
20885     //fprintf(stderr, "\t\tNo digi's listed: %s\n", path);
20886     return;
20887   }
20888 
20889 
20890   //fprintf(stderr,"      Path: %s\n",path);
20891   // We could also loop through the array and dump them out until
20892   // we hit a NULL if necessary.
20893 
20894 
20895   // Find the first digipeater callsign _after_ any digis that
20896   // have asterisks.  Run through the array in reverse, looking
20897   // for the digi callsign with an asterisk after it that's
20898   // closest to the end of the path.
20899   ii = MAX_RELAY_SUBSTRINGS - 1;
20900   done = 0;
20901   while (!done && ii > 0)
20902   {
20903 
20904     if (Substring[ii] != NULL)
20905     {
20906 
20907       if (strstr(Substring[ii],"*"))
20908       {
20909         ii++;   // Found an asterisk:  Used digi.  Point to
20910         // the digi _after_ this one.
20911         done++; // We found what we're looking for!
20912       }
20913       else    // No asterisk found yet.
20914       {
20915         ii--;
20916       }
20917     }
20918     else    // No filled-in digipeater field found yet.
20919     {
20920       ii--;
20921     }
20922   }
20923 
20924 
20925   if (ii == 0)    // No asterisks found.  Entire path unused?
20926   {
20927     // Set ii to first actual digi field instead of the
20928     // destination callsign.
20929     ii = 1;
20930   }
20931   else    // ii points to first unused digi field.
20932   {
20933   }
20934 
20935 
20936   if (Substring[ii] == NULL)      // No unused digi's found.
20937   {
20938     // We're done here.
20939     //fprintf(stderr, "\t\tPath used up: %s\n", path);
20940     return;
20941   }
20942 
20943 
20944   //fprintf(stderr,"\t\tUnused digi: %s\tPath: %s\n", Substring[ii], path);
20945 
20946 
20947   // Split the relay digipeater calls into separate substrings.
20948   // Split on comma delimiters.  We get rid of extra spaces at the
20949   // point where we read the string in from the config file
20950   // (xa_config.c), so spaces between the calls are ok (but not
20951   // tabs).
20952   split_string(relay_digipeater_calls, Relay_Calls, MAX_RELAY_DIGIPEATER_CALLS, ',');
20953 
20954   // Check for match against my_callsign in this digipeater slot
20955   done = 0;
20956   if (strcmp(Substring[ii], my_callsign) == 0)
20957   {
20958     // It's our callsign.  Digipeat using this call slot.
20959     done++;
20960   }
20961   else    // Not my_callsign.  Check every non-empty string in
20962   {
20963     // Relay_Calls[] for a match.
20964 
20965     jj = 0;
20966     while (!done && jj < MAX_RELAY_DIGIPEATER_CALLS)
20967     {
20968 
20969       // Check for ending conditions
20970       if (Relay_Calls[jj] == NULL || Relay_Calls[jj][0] == '\0')
20971       {
20972         // We hit the end of the array of possible
20973         // digipeater calls and had no match.  Exit from
20974         // this routine as we're not going to digipeat on
20975         // this callsign slot.
20976 
20977         // Later we could add the option of "preemptive digipeating", where
20978         // we look further down the path for a possible match.  We're not
20979         // doing that now.
20980 
20981         //                fprintf(stderr,"End of Relay_Calls array: %d\n",jj);
20982         return;
20983       }
20984 
20985       // If we made it to here, we should have a valid
20986       // digipeater callsign in the Relay_Calls[jj] slot to
20987       // compare against.
20988 
20989       if (debug_level & 1)
20990       {
20991         fprintf(stderr,"\tComparing %s to %s\n",
20992                 Substring[ii],
20993                 Relay_Calls[jj]);
20994       }
20995 
20996       if (strcmp(Substring[ii], Relay_Calls[jj]) == 0)
20997       {
20998         done++;
20999         //                fprintf(stderr,"match, done++\n");
21000       }
21001       else
21002       {
21003         jj++;
21004         //                fprintf(stderr,"incrementing jj: %d\n", jj);
21005       }
21006     }
21007   }
21008   if (!done)
21009   {
21010     // No valid digipeating callsign found in this slot, exit
21011     // this routine as we're not going to digipeat this packet.
21012     return;
21013   }
21014 
21015 
21016   /*
21017     OLD CODE:
21018     // Check for RELAY, WIDE1-1 (the new relay) or my_callsign in
21019     // this digipeater slot.  If none of these found then exit this
21020     // routine.
21021     if (       (strcmp(Substring[ii], "RELAY")     != 0)
21022     && (strcmp(Substring[ii], "WIDE1-1")   != 0)
21023     && (strcmp(Substring[ii], my_callsign) != 0) ) {
21024     // Some other callsign found in this digi field.  Don't
21025     // relay the packet.
21026     //fprintf(stderr,"Not relay, wide1-1, or %s, skipping\n", my_callsign);
21027     return;
21028     }
21029   */
21030 
21031 
21032   // Ok, we made it!  We have RELAY, WIDE1-1, or my_callsign that
21033   // hasn't been digipeated through, and we wish to change that
21034   // fact.  Put in our callsign for all three cases and add an
21035   // asterisk to the end of the call.  Also had to fix up the KISS
21036   // transmit routine so that it'll set the digipeated bit for
21037   // each callsign that has an asterisk.
21038 
21039   // Contruct the new digi call, with the trailing asterisk
21040   xastir_snprintf(new_digi,
21041                   sizeof(new_digi),
21042                   "%s*",
21043                   my_callsign);
21044   Substring[ii] = new_digi; // Point to new digi string instead of old
21045   //fprintf(stderr,"*** new_digi: %s\tSubstring: %s\n",
21046   //    new_digi,
21047   //    Substring[ii]);
21048 
21049   // Construct the new path, substituting the correct portion.
21050   // Start with the first digi and a comma:
21051   xastir_snprintf(new_path,
21052                   sizeof(new_path),
21053                   "%s,",
21054                   Substring[1]);
21055 
21056   ii = 2;
21057   while ( (Substring[ii] != NULL)
21058           && (ii < MAX_RELAY_SUBSTRINGS) )
21059   {
21060     strncat(new_path,
21061             Substring[ii],
21062             sizeof(new_path) - 1 - strlen(new_path));
21063     ii++;
21064     if (Substring[ii] != NULL)  // Add a comma if more to come
21065       strncat(new_path,
21066               ",",
21067               sizeof(new_path) - 1 - strlen(new_path));
21068   }
21069 
21070   //fprintf(stderr,"*** New Path: %s,%s\n", destination, new_path);
21071 
21072 
21073   if (       (devices[port].device_type == DEVICE_SERIAL_KISS_TNC)
21074              || (devices[port].device_type == DEVICE_SERIAL_MKISS_TNC) )
21075   {
21076 
21077 
21078 #ifdef SERIAL_KISS_RELAY_DIGI
21079     //        fprintf(stderr,"KISS RELAY short_path: %s\n", short_path);
21080     //        fprintf(stderr,"KISS RELAY   new_path: %s\n", new_path);
21081     send_ax25_frame(port, call, destination, new_path, info);
21082 #endif
21083 
21084   }
21085   else if (devices[port].device_type == DEVICE_AX25_TNC)
21086   {
21087     char header_txt[MAX_LINE_SIZE+5];
21088 
21089     //fprintf(stderr,"AX25 RELAY   new_path: %s\n", new_path);
21090 
21091     // set from call
21092     xastir_snprintf(header_txt, sizeof(header_txt), "%c%s %s\r", '\3', "MYCALL", call);
21093     if (port_data[port].status == DEVICE_UP)
21094     {
21095       port_write_string(port, header_txt);
21096     }
21097     // set path
21098     xastir_snprintf(header_txt, sizeof(header_txt), "%c%s %s VIA %s\r", '\3', "UNPROTO",
21099                     destination, new_path);
21100     if (port_data[port].status == DEVICE_UP)
21101     {
21102       port_write_string(port, header_txt);
21103     }
21104     // set converse mode
21105     xastir_snprintf(header_txt, sizeof(header_txt), "%c%s\r", '\3', "CONV");
21106     if (port_data[port].status == DEVICE_UP)
21107     {
21108       port_write_string(port, header_txt);
21109     }
21110     // send packet
21111     if (port_data[port].status == DEVICE_UP)
21112     {
21113       port_write_string(port, info);
21114     }
21115   }
21116   else if (devices[port].device_type == DEVICE_NET_AGWPE)
21117   {
21118     send_agwpe_packet(port, // Xastir interface port
21119                       atoi(devices[port].device_host_filter_string), // AGWPE RadioPort
21120                       '\0',                         // Type of frame (data)
21121                       (unsigned char *)call,        // source
21122                       (unsigned char *)destination, // destination
21123                       (unsigned char *)new_path,    // Path,
21124                       (unsigned char *)info,
21125                       strlen(info));
21126   }
21127 }
21128 
21129 
21130 
21131 
21132 
21133 /*
21134  *  Decode AX.25 line
21135  *  \r and \n should already be stripped from end of line
21136  *  line should not be NULL
21137  *
21138  * If dbadd is set, add to database.  Otherwise, just return true/false
21139  * to indicate whether input is valid AX25 line.
21140  */
21141 //
21142 // Note that the length of "line" can be up to MAX_DEVICE_BUFFER,
21143 // which is currently set to 4096.
21144 //
decode_ax25_line(char * line,char from,int port,int dbadd)21145 int decode_ax25_line(char *line, char from, int port, int dbadd)
21146 {
21147   char *call_sign;
21148   char *path0;
21149   char path[100+1];           // new one, we may add an '*'
21150   char *info;
21151   char info_copy[MAX_LINE_SIZE+1];
21152   char call[MAX_CALLSIGN+1];
21153   char origin[MAX_CALLSIGN+1];
21154   int ok;
21155   int third_party;
21156   char backup[MAX_LINE_SIZE+1];
21157   char tmp_line[MAX_LINE_SIZE+1];
21158   char tmp_line2[630];
21159   char tmp_path[100+1];
21160   char *ViaCalls[10];
21161 
21162 
21163   // Check guard band around pointers.  Make sure it's pristine.
21164   if ( check_guard_band() )
21165   {
21166     fprintf(stderr, "WARNING:  Guard band around global pointers was corrupted!\n");
21167   }
21168 
21169   xastir_snprintf(backup,
21170                   sizeof(backup),
21171                   "%s",
21172                   line);
21173 
21174   // This is a good one to enable for debugging without getting too
21175   // many other types of messages to the xterm.  It will enable the
21176   // block below.
21177   //#define WE7U_DEBUG
21178 
21179 #ifndef WE7U_DEBUG
21180   if (debug_level & 1)
21181 #endif
21182   {
21183     char filtered_data[MAX_LINE_SIZE+1];
21184 
21185     xastir_snprintf(filtered_data,
21186                     sizeof(filtered_data),
21187                     "%s",
21188                     line);
21189     filtered_data[MAX_LINE_SIZE] = '\0';    // Terminate it
21190 
21191     makePrintable(filtered_data);
21192     fprintf(stderr,"decode_ax25_line: start parsing %s\n", filtered_data);
21193   }
21194 
21195   if (line == NULL)
21196   {
21197     fprintf(stderr,"decode_ax25_line: line == NULL.\n");
21198     return(FALSE);
21199   }
21200 
21201   if ( (line != NULL) && (strlen(line) > MAX_LINE_SIZE) )   // Overly long message, throw it away.  We're done.
21202   {
21203     if (debug_level & 1)
21204     {
21205       fprintf(stderr,"\ndecode_ax25_line: LONG packet.  Dumping it:\n%s\n",line);
21206     }
21207     return(FALSE);
21208   }
21209 
21210   if (line[strlen(line)-1] == '\n')           // better: look at other places,
21211     // so that we don't get it here...
21212   {
21213     line[strlen(line)-1] = '\0';  // Wipe out '\n', to be sure
21214   }
21215   if (line[strlen(line)-1] == '\r')
21216   {
21217     line[strlen(line)-1] = '\0';  // Wipe out '\r'
21218   }
21219 
21220   call_sign   = NULL;
21221   path0       = NULL;
21222   info        = NULL;
21223   origin[0]   = '\0';
21224   call[0]     = '\0';
21225   path[0]     = '\0';
21226   third_party = 0;
21227 
21228   // CALL>PATH:APRS-INFO-FIELD                // split line into components
21229   //     ^    ^
21230   ok = 0;
21231   call_sign = strtok(line,">");               // extract call from AX.25 line
21232   if (call_sign != NULL)
21233   {
21234     path0 = strtok(NULL,":");               // extract path from AX.25 line
21235     if (path0 != NULL)
21236     {
21237       info = strtok(NULL,"");             // rest is info_field
21238       if (info != NULL)
21239       {
21240         if ((info - path0) < 100)       // check if path could be copied
21241         {
21242           ok = 1;                     // we have found all three components
21243         }
21244       }
21245     }
21246   }
21247 
21248   if (ok)
21249   {
21250 
21251     xastir_snprintf(path,
21252                     sizeof(path),
21253                     "%s",
21254                     path0);
21255 
21256     memset(info_copy, '\0', sizeof(info_copy));
21257     xastir_snprintf(info_copy,
21258                     sizeof(info_copy),
21259                     "%s",
21260                     info);
21261 
21262     ok = valid_path(path);                  // check the path and convert it to TAPR format
21263     // Note that valid_path() also removes igate injection identifiers
21264 
21265     if ((debug_level & 1) && !ok)
21266     {
21267       char filtered_data[MAX_LINE_SIZE + 1];
21268 
21269       xastir_snprintf(filtered_data,
21270                       sizeof(filtered_data),
21271                       "%s",
21272                       path);
21273       makePrintable(filtered_data);
21274       fprintf(stderr,"decode_ax25_line: invalid path: %s\n",filtered_data);
21275     }
21276   }
21277 
21278   if (ok)
21279   {
21280     // If it's not me transmitting it:
21281     if (strcmp(my_callsign,call_sign) != 0)
21282     {
21283 
21284       // Check for "EMERGENCY" anywhere in the line.
21285       // APRS+SA also supports any of these in the TO: field:
21286       //
21287       //      EMERGENCY
21288       //      ALARM
21289       //      ALERT
21290       //      WARNING
21291       //      WXALARM
21292       //      EM
21293       //
21294       // Snag just the TO: field from the path, used for most of the
21295       // comparisons below.  It will be pointed to by ViaCalls[0];
21296       xastir_snprintf(tmp_path,   // Make a temporary backup
21297                       sizeof(tmp_path),
21298                       "%s",
21299                       path);
21300       split_string(tmp_path, ViaCalls, 10, ',');
21301 
21302       if (       (strstr(backup,      "EMERGENCY"))    // Checks entire line
21303                  || (strcmp(ViaCalls[0], "ALARM") == 0)   // Checks to_field
21304                  || (strcmp(ViaCalls[0], "ALERT") == 0)   // Checks to_field
21305                  || (strcmp(ViaCalls[0], "WARNING") == 0) // Checks to_field
21306                  || (strcmp(ViaCalls[0], "WXALARM") == 0) // Checks to_field
21307                  || (strcmp(ViaCalls[0], "EM") == 0) )    // Checks to_field
21308       {
21309 
21310         double distance;    // miles or km
21311         char course_deg[5];
21312 
21313         // EMERGENCY
21314         if (emergency_distance_check)
21315         {
21316 
21317           distance = distance_from_my_station(call_sign, course_deg);
21318 
21319           // Because of the distance check we have to receive a valid position
21320           // from the station BEFORE we process the EMERGENCY portion and
21321           // check distance, doing the popups.  We need to figure out a way to
21322           // throw the packet back into the queue if it was an emergency
21323           // packet so that we process these packets twice each.  That way
21324           // only one packet from the emergency station is required to
21325           // generate the popups.
21326 
21327           if (distance == 0.0)
21328           {
21329             process_emergency_packet_again++;
21330           }
21331 
21332           // Check whether the station is near enough to
21333           // us to require that we alert on the packet.
21334           //
21335           // This may be slightly controversial, but if we
21336           // don't know WHERE a station is, we can't help
21337           // much in an emergency, can we?  The
21338           // zero-distance check helps in the case where
21339           // we haven't yet or never get a position packet
21340           // for a station.  As soon as we have a position
21341           // and it is within a reasonable range, we do
21342           // our emergency popups.
21343           //
21344           if ( distance != 0.0 && (float)distance <= emergency_range )
21345           {
21346 
21347             // Do the conversion for emergency_range to mi or km as needed.
21348             //                        if (english_units) {
21349             //                        }
21350             //                        else {
21351             //                        }
21352 
21353             // Do a popup to alert the operator to this
21354             // condition.  Make sure we haven't popped
21355             // up an emergency message for this station
21356             // within the last 30 minutes.  If we pop
21357             // these up constantly it gets quite
21358             // annoying.
21359             if ( (strncmp(call_sign, last_emergency_callsign, strlen(call_sign)) != 0)
21360                  || ((last_emergency_time + 60*30) < sec_now()) )
21361             {
21362 
21363               char temp[50];
21364               char temp2[150];
21365               char temp3[300];
21366               char timestring[101];
21367 
21368               // Callsign is different or enough time
21369               // has passed
21370 
21371               last_emergency_time = sec_now();
21372               xastir_snprintf(last_emergency_callsign,
21373                               sizeof(last_emergency_callsign),
21374                               "%s",
21375                               call_sign);
21376 
21377               // Bring up the Find Station dialog so
21378               // that the operator can go to the
21379               // location quickly.
21380               xastir_snprintf(locate_station_call,
21381                               sizeof(locate_station_call),
21382                               "%s",
21383                               call_sign);
21384 
21385               Locate_station( (Widget)NULL,
21386                               (XtPointer)NULL,
21387                               (XtPointer)1 );
21388 
21389               // Bring up an additional popup dialog
21390               // that shows the entire packet, so the
21391               // user can make a determination as to
21392               // whether the packet is or is not a
21393               // real emergency.
21394               //
21395               popup_message_always(langcode("POPEM00036"), backup);
21396 
21397               // Bring up another dialog with the
21398               // callsign plus distance/bearing to the
21399               // station.
21400               xastir_snprintf(temp,
21401                               sizeof(temp),
21402                               "%0.1f",
21403                               distance);
21404               xastir_snprintf(temp2,
21405                               sizeof(temp2),
21406                               langcode("WPUPSTI022"),
21407                               temp,
21408                               course_deg);
21409               get_timestamp(timestring);
21410               xastir_snprintf(temp3,
21411                               sizeof(temp3),
21412                               "%s  %s",
21413                               timestring,
21414                               temp2);
21415               popup_message_always(call_sign, temp3);
21416 
21417             }
21418           }
21419         }
21420       }
21421     }
21422   }
21423 
21424   if (ok)
21425   {
21426 
21427     // Attempt to digipeat this packet if we should.  If port=-2,
21428     // we received this packet from the x_spider server and we
21429     // should not attempt to digipeat it.  If port=-1, it's from
21430     // a log file.  Again, don't digipeat it.
21431     if (port >= 0)
21432     {
21433       relay_digipeat(call_sign, path, info, port);
21434     }
21435 
21436     extract_TNC_text(info);                 // extract leading text from TNC X-1J4
21437     if (strlen(info) > 256)                 // first check if information field conforms to APRS specs
21438     {
21439       ok = 0;  // drop packets too long
21440     }
21441     if ((debug_level & 1) && !ok)
21442     {
21443       char filtered_data[MAX_LINE_SIZE + 1];
21444 
21445       xastir_snprintf(filtered_data,
21446                       sizeof(filtered_data),
21447                       "%s",
21448                       info);
21449       makePrintable(filtered_data);
21450       fprintf(stderr,"decode_ax25_line: info field too long: %s\n",filtered_data);
21451     }
21452   }
21453 
21454   if (ok)                                                     // check callsign
21455   {
21456     (void)remove_trailing_asterisk(call_sign);              // is an asterisk valid here ???
21457     if (valid_inet_name(call_sign,info,origin,sizeof(origin)))   // accept some of the names used in internet
21458     {
21459       xastir_snprintf(call,
21460                       sizeof(call),
21461                       "%s",
21462                       call_sign);
21463     }
21464     else if (valid_call(call_sign))                       // accept real AX.25 calls
21465     {
21466       xastir_snprintf(call,
21467                       sizeof(call),
21468                       "%s",
21469                       call_sign);
21470     }
21471     else
21472     {
21473       ok = 0;
21474       if (debug_level & 1)
21475       {
21476         char filtered_data[MAX_LINE_SIZE + 1];
21477 
21478         xastir_snprintf(filtered_data,
21479                         sizeof(filtered_data),
21480                         "%s",
21481                         call_sign);
21482         makePrintable(filtered_data);
21483         fprintf(stderr,"decode_ax25_line: invalid call: %s\n",filtered_data);
21484       }
21485     }
21486   }
21487 
21488   if (!dbadd)
21489   {
21490     if (debug_level & 1)
21491     {
21492       fprintf(stderr,"decode_ax25_line: exiting\n");
21493     }
21494 
21495     return(ok);
21496   }
21497 
21498   if (ok && info[0] == '}')                                   // look for third-party traffic
21499   {
21500     ok = extract_third_party(call,path,sizeof(path),&info,origin,sizeof(origin)); // extract third-party data
21501     third_party = 1;
21502 
21503     // Add it to the HEARD queue for this interface.  We use this
21504     // for igating purposes.  If some other igate beat us to this
21505     // packet, we don't want to duplicate it over the air.  If
21506     // port=-2, we received it from the x_spider server and we
21507     // should not save it in the queue.  If port=-1, the packet
21508     // came from a log file and again we shouldn't save it to
21509     // the queue.
21510     if (port >= 0)
21511     {
21512       insert_into_heard_queue(port, backup);
21513     }
21514   }
21515 
21516   if (ok && (info[0] == ';' || info[0] == ')'))               // look for objects or items
21517   {
21518     xastir_snprintf(origin,
21519                     sizeof(origin),
21520                     "%s",
21521                     call);
21522     ok = extract_object(call,&info,origin);                 // extract object data
21523   }
21524 
21525   if (ok)
21526   {
21527     // decode APRS information field, always called with valid call and path
21528     // info is a string with 0 - 256 bytes
21529     // fprintf(stderr,"dec: %s (%s) %s\n",call,origin,info);
21530     if (debug_level & 1)
21531     {
21532       char filtered_data[MAX_LINE_SIZE+80];
21533       sprintf(filtered_data,
21534               "Registering data %s %s %s %s %c %d %d",
21535               call, path, info, origin, from, port, third_party);
21536       makePrintable(filtered_data);
21537       fprintf(stderr,"c/p/i/o fr pt tp: %s\n", filtered_data);
21538     }
21539     decode_info_field(call,
21540                       path,
21541                       info,
21542                       origin,
21543                       from,
21544                       port,
21545                       third_party,
21546                       info_copy);
21547   }
21548 
21549 
21550   if (port == -2)      // We received this packet from an x_spider
21551   {
21552     // server.  We need to dump it out all of our
21553     // transmit-enabled ports.
21554 
21555     // If the string starts with "user" or "pass", it's an
21556     // authentication string.  We need to send those through as
21557     // well so that the user gets logged into the internet
21558     // server and can send/receive packets/messages.  We also
21559     // dump it to our console so that we can see who logged in
21560     // to us.
21561     if (strncasecmp(line,"user",4) == 0
21562         || strncasecmp(line,"pass",4) == 0
21563         || strncasecmp(line,"filter",6) == 0)
21564     {
21565       fprintf(stderr,"\tLogged on: %s\n", line);
21566 
21567       // If the line has a "filter" parameter in it, we need to remove it,
21568       // else a client may change our filtering parameters.  Perhaps we
21569       // should skip the authentication stuff as well, as the servers
21570       // might get confused if we pass two different authentications on
21571       // the same socket?
21572 
21573     }
21574     else if (strlen(line) > 0)      // Not empty
21575     {
21576       // Send the packet unchanged out all of our
21577       // transmit-enabled ports.  We should send it as
21578       // third-party igated packets if we're sending to
21579       // servers, send it unchanged if sending through TNC?
21580 
21581       //fprintf(stderr,"Retransmitting x_spider packet: %s\n", line);
21582 
21583       // Here's where we inject our own callsign like this:
21584       // "WE7U-15,I" in order to provide injection ID for our
21585       // igate.
21586       xastir_snprintf(tmp_line2,
21587                       sizeof(tmp_line2),
21588                       "%s>%s,%s,I:%s",
21589                       call_sign,
21590                       path,
21591                       my_callsign,
21592                       info_copy);
21593       memcpy(tmp_line, tmp_line2, sizeof(tmp_line));
21594       tmp_line[sizeof(tmp_line)-1] = '\0';  // Terminate line
21595 
21596       //fprintf(stderr,"decode_ax25_line: IGATE>NET %s\n",tmp_line);
21597       //fprintf(stderr,"call: %s\tcall_sign: %s\n", call, call_sign);
21598 
21599       output_igate_net(tmp_line, port, 0); // 0="not third-party"
21600     }
21601   }
21602 
21603   // EMERGENCY
21604   // For emergency packets we need to process them twice, to try
21605   // to get a position before we do the distance check.
21606   //
21607   // This causes an infinite loop on packets that don't have a
21608   // distance!  Disabling it for now.
21609   if (process_emergency_packet_again)
21610   {
21611     process_emergency_packet_again = 0;
21612     //fprintf(stderr,"Again: %s\n", backup);
21613     //        decode_ax25_line(backup, from, port, dbadd);
21614   }
21615 
21616   if (debug_level & 1)
21617   {
21618     fprintf(stderr,"decode_ax25_line: exiting\n");
21619   }
21620 
21621   return(ok);
21622 }
21623 
21624 
21625 
21626 
21627 
21628 /*
21629  *  Read a line from file.  We use this to read in log files and to
21630  *  read in findu track files.  For findu track files we need to get
21631  *  rid of the <br> at the end of the lines, else it shows up in our
21632  *  comment lines in Station_info.
21633  */
read_file_line(FILE * f)21634 void  read_file_line(FILE *f)
21635 {
21636   char line[MAX_LINE_SIZE+1];
21637   char cin;
21638   int pos;
21639 
21640   pos = 0;
21641   line[0] = '\0';
21642   while (!feof(f))
21643   {
21644     if (fread(&cin,1,1,f) == 1)
21645     {
21646       if (cin != (char)10 && cin != (char)13)     // normal characters
21647       {
21648         if (pos < MAX_LINE_SIZE)
21649         {
21650           line[pos++] = cin;
21651         }
21652       }
21653       else                                      // CR or LF
21654       {
21655         if (cin == (char)10)                    // Found LF as EOL char
21656         {
21657           char *ptr;
21658 
21659           line[pos] = '\0';                   // Always add a terminating zero after last char
21660           pos = 0;                            // start next line
21661 
21662           // Get rid of <br> HTML tag at end of line here.
21663           // Findu track files have them.
21664           ptr = strstr(line, "<br>");
21665           if (ptr)    // Found one of them
21666           {
21667             *ptr = '\0';  // Terminate the line at that point
21668           }
21669 
21670           // Save backup copies of this string and the
21671           // previous string.  Used for debugging
21672           // purposes.  If we get a segfault, we can print
21673           // out the last two messages received.
21674           memcpy(incoming_data_copy_previous, incoming_data_copy, MAX_LINE_SIZE);
21675           incoming_data_copy_previous[MAX_LINE_SIZE-1] = '\0';  // Terminate string
21676 
21677           memcpy(incoming_data_copy, line, MAX_LINE_SIZE);
21678           incoming_data_copy[MAX_LINE_SIZE-1] = '\0'; // Terminate string
21679 
21680           if (line[0] != '#')
21681           {
21682             decode_ax25_line(line,'F',-1, 1);   // Decode the packet
21683           }
21684 
21685           return;                             // only read line by line
21686         }
21687       }
21688     }
21689   }
21690   if (feof(f))                                        // Close file if at the end
21691   {
21692     (void)fclose(f);
21693     read_file = 0;
21694     statusline(langcode("BBARSTA012"),0);           // File done..
21695     redraw_on_new_data = 2;                         // redraw immediately after finish
21696   }
21697 }
21698 
21699 
21700 
21701 
21702 
21703 /*
21704  *  Center map to new position
21705  */
set_map_position(Widget UNUSED (w),long lat,long lon)21706 void set_map_position(Widget UNUSED(w), long lat, long lon)
21707 {
21708   // see also map_pos() in location.c
21709 
21710   // Set interrupt_drawing_now because conditions have changed
21711   // (new map center).
21712   interrupt_drawing_now++;
21713 
21714   set_last_position();
21715   center_latitude  = lat;
21716   center_longitude = lon;
21717   setup_in_view();  // flag all stations in new screen view
21718 
21719   // Request that a new image be created.  Calls create_image,
21720   // XCopyArea, and display_zoom_status.
21721   request_new_image++;
21722 
21723   //    if (create_image(w)) {
21724   //        (void)XCopyArea(XtDisplay(w),pixmap_final,XtWindow(w),gc,0,0,(unsigned int)screen_width,(unsigned int)screen_height,0,0);
21725   //    }
21726 }
21727 
21728 
21729 
21730 
21731 
21732 /*
21733  *  Search for a station to be located (for Tracking and Find Station)
21734  */
locate_station(Widget w,char * call,int follow_case,int get_match,int center_map)21735 int locate_station(Widget w, char *call, int follow_case, int get_match, int center_map)
21736 {
21737   DataRow *p_station;
21738   char call_find[MAX_CALLSIGN+1];
21739   char call_find1[MAX_CALLSIGN+1];
21740   int ii;
21741 
21742   if (!follow_case)
21743   {
21744     for (ii=0; ii<(int)strlen(call); ii++)
21745     {
21746       if (isalpha((int)call[ii]))
21747       {
21748         call_find[ii] = toupper((int)call[ii]);  // Problem with lowercase objects/names!!
21749       }
21750       else
21751       {
21752         call_find[ii] = call[ii];
21753       }
21754     }
21755     call_find[ii] = '\0';
21756     xastir_snprintf(call_find1,
21757                     sizeof(call_find1),
21758                     "%s",
21759                     call_find);
21760   }
21761   else
21762     xastir_snprintf(call_find1,
21763                     sizeof(call_find1),
21764                     "%s",
21765                     call);
21766 
21767   if (search_station_name(&p_station,call_find1,get_match))
21768   {
21769     if (position_defined(p_station->coord_lat,p_station->coord_lon,0))
21770     {
21771       if (center_map || !position_on_inner_screen(p_station->coord_lat,p_station->coord_lon))
21772         // only change map if really neccessary
21773       {
21774         set_map_position(w, p_station->coord_lat, p_station->coord_lon);
21775       }
21776       return(1);                  // we found it
21777     }
21778   }
21779   return(0);
21780 }
21781 
21782 
21783 
21784 
21785 
21786 /*
21787  *  Look for other stations that the tracked one has gotten close to.
21788  *  and speak a proximity warning.
21789  *   TODO:
21790  *    - sort matches by distance
21791  *    - set upper bound on number of matches so we don't speak forever
21792  *    - use different proximity distances for different station types?
21793  *    - look for proximity to embedded map objects
21794  */
search_tracked_station(DataRow ** p_tracked)21795 void search_tracked_station(DataRow **p_tracked)
21796 {
21797   DataRow *t = (*p_tracked);
21798   DataRow *curr = NULL;
21799 
21800 
21801   if (debug_level & 1)
21802   {
21803     char lat[20],lon[20];
21804 
21805     convert_lat_l2s(t->coord_lat,
21806                     lat,
21807                     sizeof(lat),
21808                     CONVERT_HP_NORMAL);
21809     convert_lon_l2s(t->coord_lon,
21810                     lon,
21811                     sizeof(lat),
21812                     CONVERT_HP_NORMAL);
21813 
21814     fprintf(stderr,"Searching for stations close to tracked station %s at %s %s ...\n",
21815             t->call_sign,lat,lon);
21816   }
21817 
21818   while (next_station_time(&curr))
21819   {
21820     if (curr != t && curr->flag&ST_ACTIVE)
21821     {
21822 
21823       float distance; // Distance in whatever measurement
21824       // units we're currently using.
21825       char bearing[10];
21826       char station_id[600];
21827 
21828       distance =  (float)calc_distance_course(t->coord_lat,
21829                                               t->coord_lon,
21830                                               curr->coord_lat,
21831                                               curr->coord_lon,
21832                                               bearing,
21833                                               sizeof(bearing)) * cvt_kn2len;
21834 
21835       if (debug_level & 1)
21836         fprintf(stderr,"Looking at %s: distance %.3f bearing %s (%s)\n",
21837                 curr->call_sign,distance,bearing,convert_bearing_to_name(bearing,1));
21838 
21839       /* check ranges (copied from earlier prox alert code, above) */
21840       if ((distance > atof(prox_min)) && (distance < atof(prox_max)))
21841       {
21842         if (debug_level & 1)
21843         {
21844           fprintf(stderr," tracked station is near %s!\n",curr->call_sign);
21845         }
21846 
21847         if (sound_play_prox_message)
21848         {
21849           sprintf(station_id,"%s < %.3f %s from %s",t->call_sign, distance,
21850                   english_units?langcode("UNIOP00004"):langcode("UNIOP00005"),
21851                   curr->call_sign);
21852           statusline(station_id,0);
21853           play_sound(sound_command,sound_prox_message);
21854         }
21855 #ifdef HAVE_FESTIVAL
21856         if (festival_speak_tracked_proximity_alert)
21857         {
21858           if (english_units)
21859           {
21860             if (distance < 1.0)
21861               sprintf(station_id, langcode("SPCHSTR007"), t->call_sign,
21862                       (int)(distance * 1760), langcode("SPCHSTR004"),
21863                       convert_bearing_to_name(bearing,1), curr->call_sign);
21864             else if ((int)((distance * 10) + 0.5) % 10)
21865               sprintf(station_id, langcode("SPCHSTR008"), t->call_sign, distance,
21866                       langcode("SPCHSTR003"), convert_bearing_to_name(bearing,1),
21867                       curr->call_sign);
21868             else
21869               sprintf(station_id, langcode("SPCHSTR007"), t->call_sign, (int)(distance + 0.5),
21870                       langcode("SPCHSTR003"), convert_bearing_to_name(bearing,1),
21871                       curr->call_sign);
21872           }
21873           else                  /* metric */
21874           {
21875             if (distance < 1.0)
21876               sprintf(station_id, langcode("SPCHSTR007"), t->call_sign,
21877                       (int)(distance * 1000), langcode("SPCHSTR002"),
21878                       convert_bearing_to_name(bearing,1), curr->call_sign);
21879             else if ((int)((distance * 10) + 0.5) % 10)
21880               sprintf(station_id, langcode("SPCHSTR008"), t->call_sign, distance,
21881                       langcode("SPCHSTR001"),
21882                       convert_bearing_to_name(bearing,1), curr->call_sign);
21883             else
21884               sprintf(station_id, langcode("SPCHSTR007"), t->call_sign, (int)(distance + 0.5),
21885                       langcode("SPCHSTR001"), convert_bearing_to_name(bearing,1),
21886                       curr->call_sign);
21887           }
21888           if (debug_level & 1)
21889           {
21890             fprintf(stderr," %s\n",station_id);
21891           }
21892           SayText(station_id);
21893         }
21894 #endif  /* HAVE_FESTIVAL */
21895       }
21896     }
21897   }   // end of while
21898 }
21899 
21900 
21901 
21902 
21903 
21904 /*
21905  *  Change map position if neccessary while tracking a station
21906  *      we call it with defined station call and position
21907  */
track_station(Widget w,char * UNUSED (call_tracked),DataRow * p_station)21908 void track_station(Widget w, char * UNUSED(call_tracked), DataRow *p_station)
21909 {
21910   long x_ofs, y_ofs;
21911   long new_lat, new_lon;
21912 
21913   if ( is_tracked_station(p_station->call_sign) )     // We want to track this station
21914   {
21915     new_lat = p_station->coord_lat;                 // center map to station position as default
21916     new_lon = p_station->coord_lon;
21917     x_ofs = new_lon - center_longitude;            // current offset from screen center
21918     y_ofs = new_lat - center_latitude;
21919     if ((labs(x_ofs) > (screen_width*scale_x/3)) || (labs(y_ofs) > (screen_height*scale_y/3)))
21920     {
21921       // only redraw map if near border (margin 1/6 of screen at each side)
21922       if (labs(y_ofs) < (screen_height*scale_y/2))
21923       {
21924         new_lat  += y_ofs/2;  // give more space in driving direction
21925       }
21926       if (labs(x_ofs) < (screen_width*scale_x/2))
21927       {
21928         new_lon += x_ofs/2;
21929       }
21930 
21931       set_map_position(w, new_lat, new_lon);      // center map to new position
21932 
21933     }
21934     search_tracked_station(&p_station);
21935   }
21936 }
21937 
21938 
21939 
21940 
21941 
21942 // ********************************************************************
21943 // calc aloha_distance()
21944 // calculate and return alhoa circle radius in current distance units
21945 // The ALOHA radius is computed according to the algorithm described by
21946 // Bob Bruninga at http://web.usna.navy.mil/~bruninga/aprs/ALOHAcir.txt
21947 // with some clarification provided py private email.
21948 //
21949 // The gist of it is that we grab a list of all stations heard via TNC
21950 // and sort it b distance from our station.  We then accumulate a
21951 // count of how many theoretical packets would be introduced into the local
21952 // area in 30 minutes from these stations, and stop when we hit 1800
21953 // (the supposed limit of the channel capacity).  The distance to the last
21954 // station we counted is our ALOHA limit.  Per Bob B., this should be plotted
21955 // on the  map as a circle with no user-selectable way of turning it off.
21956 //
calc_aloha_distance(void)21957 double calc_aloha_distance(void)
21958 {
21959   DataRow *p_station = n_first;  // walk in alphabetical order
21960   aloha_entry *aloha_array;
21961   aloha_entry *temp_aloha_array;
21962 
21963   int num_aloha_alloc=1000;
21964   int num_aloha_entries=0;
21965   int digi_copies=1;
21966   char temp[10]; // needed for course_deg argument of
21967   // distance_from_my_station
21968 
21969   int sum;
21970   double distance;
21971   int ii;
21972 
21973   // This should be enough, though we'll realloc if necessary
21974   aloha_array = (aloha_entry *)malloc (num_aloha_alloc*sizeof(aloha_entry));
21975   CHECKMALLOC(aloha_array);
21976 
21977   // We need a list of all stations that were heard via tnc:
21978   while (p_station != NULL)
21979   {
21980     if (num_aloha_entries == num_aloha_alloc)
21981     {
21982       num_aloha_alloc *= 2;
21983       temp_aloha_array=realloc(aloha_array,num_aloha_alloc);
21984       if (temp_aloha_array)
21985       {
21986         aloha_array=temp_aloha_array;
21987       }
21988       else
21989       {
21990         fprintf(stderr,"***** Realloc failed *****\n");
21991         exit(1);
21992       }
21993     }
21994     if ( (p_station->flag & ST_VIATNC) != 0 &&
21995          (p_station->flag & ST_ACTIVE) != 0 )
21996     {
21997       if (position_defined(p_station->coord_lat,p_station->coord_lon,1))
21998       {
21999         xastir_snprintf(aloha_array[num_aloha_entries].call_sign,
22000                         MAX_CALLSIGN+1,
22001                         "%s",
22002                         p_station->call_sign);
22003         aloha_array[num_aloha_entries].is_digi =
22004           aloha_array[num_aloha_entries].is_mobile =
22005             aloha_array[num_aloha_entries].is_other_mobile =
22006               aloha_array[num_aloha_entries].is_home =
22007                 aloha_array[num_aloha_entries].is_wx = (char) FALSE;
22008         aloha_array[num_aloha_entries].distance =
22009           distance_from_my_station(p_station->call_sign,temp);
22010 
22011         if ( p_station->newest_trackpoint != NULL
22012              && strlen(p_station->speed) > 0)
22013         {
22014           // If the station has a track and a speed of any value
22015           // (even zero), it's a mobile.
22016           aloha_array[num_aloha_entries].is_mobile = (char) TRUE;
22017         }
22018         else if  ( (p_station->aprs_symbol.aprs_type=='/'
22019                     && (strchr("'<=>()*0COPRSUXY[^abefgjkpsuv",
22020                                p_station->aprs_symbol.aprs_symbol)
22021                         != NULL))
22022                    || (p_station->aprs_symbol.aprs_type=='\\'
22023                        && (strchr("/0>AKOS^knsuv",
22024                                   p_station->aprs_symbol.aprs_symbol)
22025                            != NULL)))
22026         {
22027           //
22028           // Per private email exchange with Bob Bruninga:
22029           // If the station has one of these symbols,
22030           //  it's "other mobile"
22031           // these are also listed on
22032           // web.usna.navy.mil/~bruninga/aprs/aprs11.html
22033           //
22034           aloha_array[num_aloha_entries].is_other_mobile =(char)TRUE;
22035         }
22036         else if ( p_station-> record_type == APRS_WX1 ||
22037                   p_station-> record_type == APRS_WX2 ||
22038                   p_station-> record_type == APRS_WX3 ||
22039                   p_station-> record_type == APRS_WX4 ||
22040                   p_station-> record_type == APRS_WX5 ||
22041                   p_station-> record_type == APRS_WX6 ||
22042                   p_station-> aprs_symbol.aprs_symbol=='_')
22043         {
22044           // Bob B. uses the station symbol "_" to select this, but
22045           // agrees that if we do it this way it's probably better
22046           // -- this says if we've gotten any WX data, it's a WX
22047           // station
22048           aloha_array[num_aloha_entries].is_wx = (char) TRUE;
22049         }
22050         else if (p_station->aprs_symbol.aprs_symbol=='#')
22051         {
22052           // Per Bob B., if it has "#" as its symbol, it's
22053           // assumed to be a digi.
22054           aloha_array[num_aloha_entries].is_digi = (char) TRUE;
22055         }
22056         else
22057         {
22058           // Anything that hasn't gotten selected yet is just a home
22059           aloha_array[num_aloha_entries].is_home = (char) TRUE;
22060         }
22061 
22062         num_aloha_entries++;
22063       }
22064     }
22065     p_station = p_station-> n_next;
22066   }
22067 
22068   if (debug_level & 2048)
22069   {
22070     fprintf (stderr,"aloha_distance: Found %d local stations\n",
22071              num_aloha_entries);
22072   }
22073 
22074   // we now have all the stations heard via TNC.  Now sort it by distance
22075   qsort((void *) aloha_array,num_aloha_entries,sizeof(aloha_entry),
22076         comp_by_dist);
22077 
22078   // Starting from the closest, working outward, accumulate
22079   sum=0;
22080   the_aloha_stats.digis=0;
22081   the_aloha_stats.wxs = 0;
22082   the_aloha_stats.other_mobiles = 0;
22083   the_aloha_stats.mobiles_in_motion = 0;
22084   the_aloha_stats.homes = 0;
22085   the_aloha_stats.total = 0;
22086 
22087   for (ii=0; (ii<num_aloha_entries && sum < 1800); ii++)
22088   {
22089     the_aloha_stats.total++;
22090     if (aloha_array[ii].is_digi)
22091     {
22092       sum += digi_copies*3;
22093       digi_copies++; // per Bob's web page.  Makes more distant
22094       // stations than this digi count for more, since
22095       // they have been digipeated.
22096       the_aloha_stats.digis++;
22097     }
22098     else if (aloha_array[ii].is_home)
22099     {
22100       sum += digi_copies*2;
22101       the_aloha_stats.homes++;
22102     }
22103     else if (aloha_array[ii].is_wx)
22104     {
22105       sum += digi_copies*6;
22106       the_aloha_stats.wxs++;
22107     }
22108     else if (aloha_array[ii].is_mobile)
22109     {
22110       sum += digi_copies*15;
22111       the_aloha_stats.mobiles_in_motion++;
22112     }
22113     else if (aloha_array[ii].is_other_mobile)
22114     {
22115       sum += digi_copies*7;
22116       the_aloha_stats.other_mobiles++;
22117     }
22118     if (debug_level & 2048)
22119     {
22120       fprintf(stderr,"  %d:%s: d=%f, digi=%c, mobile=%c, motion=%c, home=%c, wx=%c (cum=%d)\n",
22121               ii,
22122               aloha_array[ii].call_sign,
22123               aloha_array[ii].distance,
22124               (aloha_array[ii].is_digi)?'y':'n',
22125               (aloha_array[ii].is_other_mobile)?'y':'n',
22126               (aloha_array[ii].is_mobile)?'y':'n',
22127               (aloha_array[ii].is_home)?'y':'n',
22128               (aloha_array[ii].is_wx)?'y':'n',sum);
22129     }
22130   }
22131 
22132   if (ii>0 && ii < num_aloha_entries && sum >= 1800)   // we hit the limit
22133   {
22134     distance = aloha_array[ii-1].distance;
22135   }
22136   else
22137   {
22138     distance = -1; // indeterminate, not enough data yet
22139   }
22140 
22141   free (aloha_array); // make sure we don't leak
22142   return distance;
22143 
22144 }
22145 
22146 
22147 
22148 
22149 
22150 // Used by qsort to sort the aloha entries
comp_by_dist(const void * av,const void * bv)22151 int comp_by_dist(const void *av,const void *bv)
22152 {
22153   aloha_entry *a = (aloha_entry *) av;
22154   aloha_entry *b = (aloha_entry *) bv;
22155   if (a->distance < b->distance)
22156   {
22157     return -1;
22158   }
22159   if (a->distance > b->distance)
22160   {
22161     return 1;
22162   }
22163 
22164   return 0;
22165 }
22166 
22167 
22168 
22169 
22170 
22171 // Called periodically by UpdateTime, we calculate our aloha radius every
22172 // so often.  (Bob B. recommends every 30 minutes)
calc_aloha(int secs_now)22173 void calc_aloha(int secs_now)
22174 {
22175   char status_text[100];
22176 
22177   if (aloha_time == 0)   // first call
22178   {
22179     aloha_time = secs_now+ALOHA_CALC_INTERVAL;
22180     aloha_status_time = secs_now+ALOHA_STATUS_INTERVAL;
22181     aloha_radius = -1.0;
22182 
22183     // Debug:  Let's us play with/display aloha circles right away:
22184     //aloha_radius = 40.0;    // Miles
22185 
22186     the_aloha_stats.digis=0;
22187     the_aloha_stats.wxs = 0;
22188     the_aloha_stats.other_mobiles = 0;
22189     the_aloha_stats.mobiles_in_motion = 0;
22190     the_aloha_stats.homes = 0;
22191     the_aloha_stats.total = 0;
22192     //fprintf(stderr,"Initialized aloha radius time\n");
22193   }
22194   else
22195   {
22196     if (secs_now > aloha_time)
22197     {
22198       aloha_radius = calc_aloha_distance();
22199       aloha_time = secs_now + ALOHA_CALC_INTERVAL;
22200       if (debug_level & 2048)
22201       {
22202         if (aloha_radius < 0)
22203         {
22204           fprintf(stderr,"Aloha distance indeterminate\n");
22205         }
22206         else
22207         {
22208           fprintf(stderr,"Aloha distance is %f",aloha_radius);
22209           if (english_units)
22210           {
22211             fprintf(stderr," miles.\n");
22212           }
22213           else
22214           {
22215             fprintf(stderr," km.\n");
22216           }
22217         }
22218       }
22219     }
22220     if (secs_now > aloha_status_time)
22221     {
22222       if ( aloha_radius != -1 )
22223       {
22224         xastir_snprintf(status_text,
22225                         sizeof(status_text),
22226                         langcode("BBARSTA044"),
22227                         (english_units) ? (int)aloha_radius : (int)(aloha_radius * cvt_mi2len),
22228                         (english_units) ? " miles" : " km");
22229         statusline(status_text,1);
22230       }
22231       aloha_status_time = secs_now + ALOHA_STATUS_INTERVAL;
22232     }
22233   }
22234 }
22235 
22236 
22237 
22238 
22239 
22240 // popup window on menu request
Show_Aloha_Stats(Widget UNUSED (w),XtPointer UNUSED (clientData),XtPointer UNUSED (callData))22241 void Show_Aloha_Stats(Widget UNUSED(w), XtPointer UNUSED(clientData), XtPointer UNUSED(callData) )
22242 {
22243 
22244   char temp[2000];
22245   char format[1000];
22246 
22247   unsigned long time_since_aloha_update;
22248   int minutes, hours;
22249   char Hours[7];
22250   char Minutes[9];
22251 
22252   if (aloha_radius != -1)
22253   {
22254     // we've done at least one interval, and aloha_time is the time
22255     // for the *next* one.  We want the time since the last one.
22256     time_since_aloha_update = sec_now()-(aloha_time-ALOHA_CALC_INTERVAL);
22257 
22258 
22259     hours = time_since_aloha_update/3600;
22260     time_since_aloha_update -= hours*3600;
22261     minutes = time_since_aloha_update/60;
22262 
22263     if (hours == 1)
22264       xastir_snprintf(Hours,sizeof(Hours),"%s",
22265                       langcode("TIME003")); // Hour
22266     else
22267       xastir_snprintf(Hours,sizeof(Hours),"%s",
22268                       langcode("TIME004")); // Hours
22269 
22270 
22271     if (minutes == 1)
22272       xastir_snprintf(Minutes,sizeof(Minutes),"%s",
22273                       langcode("TIME005")); // Minute
22274     else
22275       xastir_snprintf(Minutes,sizeof(Minutes),"%s",
22276                       langcode("TIME006")); // Minutes
22277 
22278     // Build up the whole format string
22279     // "Aloha radius %d"
22280     xastir_snprintf(format,sizeof(format),"%s",langcode("WPUPALO001"));
22281     strncat(format,"\n",sizeof(format) - 1 - strlen(format));
22282     // "Stations inside...: %d"
22283     strncat(format,langcode("WPUPALO002"),sizeof(format) - 1 - strlen(format));
22284     strncat(format,"\n",sizeof(format) - 1 - strlen(format));
22285     //" Digis:               %d"
22286     strncat(format,langcode("WPUPALO003"),sizeof(format) - 1 - strlen(format));
22287     strncat(format,"\n",sizeof(format) - 1 - strlen(format));
22288     //" Mobiles (in motion): %d"
22289     strncat(format,langcode("WPUPALO004"),sizeof(format) - 1 - strlen(format));
22290     strncat(format,"\n",sizeof(format) - 1 - strlen(format));
22291     //" Mobiles (other):     %d"
22292     strncat(format,langcode("WPUPALO005"),sizeof(format) - 1 - strlen(format));
22293     strncat(format,"\n",sizeof(format) - 1 - strlen(format));
22294     //" WX stations:         %d"
22295     strncat(format,langcode("WPUPALO006"),sizeof(format) - 1 - strlen(format));
22296     strncat(format,"\n",sizeof(format) - 1 - strlen(format));
22297     //" Home stations:       %d"
22298     strncat(format,langcode("WPUPALO007"),sizeof(format) - 1 - strlen(format));
22299     strncat(format,"\n",sizeof(format) - 1 - strlen(format));
22300     //"Last calculated %s ago."
22301     strncat(format,langcode("WPUPALO008"),sizeof(format) - 1 - strlen(format));
22302     strncat(format,"\n",sizeof(format) - 1 - strlen(format));
22303 
22304     // We now have the whole format string, now print using it:
22305     xastir_snprintf(temp,sizeof(temp),format,
22306                     (english_units) ? (int)aloha_radius : (int)(aloha_radius * cvt_mi2len),
22307                     (english_units)?" miles":" km",
22308                     the_aloha_stats.total,
22309                     the_aloha_stats.digis,
22310                     the_aloha_stats.mobiles_in_motion,
22311                     the_aloha_stats.other_mobiles,
22312                     the_aloha_stats.wxs,
22313                     the_aloha_stats.homes,
22314                     hours, Hours,
22315                     minutes, Minutes);
22316 
22317     popup_message_always(langcode("PULDNVI016"),temp);
22318   }
22319   else
22320   {
22321     // Not calculated yet
22322     popup_message_always(langcode("PULDNVI016"),langcode("WPUPALO666"));
22323   }
22324 }
22325 
22326 // Debugging tool:
22327 // Check to see if time list contains any stations older than remove_time.
22328 // If the expire code did its job properly, there should be none.  If there
22329 // are none, we return NULL.  If there are any, we return the pointer to the
22330 // last one found (which should be the newest of them by virtue of how we
22331 // walk the list).
sanity_check_time_list(time_t remove_time)22332 DataRow * sanity_check_time_list(time_t remove_time)
22333 {
22334   DataRow *p_station, *p_station_t_newer, *retval;
22335   retval=NULL;
22336 
22337   for (p_station = t_oldest; p_station != NULL;
22338        p_station = p_station_t_newer)
22339   {
22340     p_station_t_newer = p_station->t_newer;
22341     // Don't count my station in this.
22342     if (!is_my_station(p_station) && p_station->sec_heard < remove_time)
22343     {
22344       retval=p_station;
22345     }
22346   }
22347 
22348   return (retval);
22349 }
22350 
22351 // Debugging tool
22352 // dump out the entire time-sorted list starting from oldest and proceeding
22353 // to newest
dump_time_sorted_list(void)22354 void dump_time_sorted_list(void)
22355 {
22356   DataRow *p_station, *p_station_t_newer;
22357   struct tm *time;
22358   fprintf(stderr,"\tTime-sorted list dump \n");
22359   fprintf(stderr, "\t Call Sign:\tsec_heard\tdate/time\n");
22360   for (p_station = t_oldest; p_station != NULL;
22361        p_station = p_station_t_newer)
22362   {
22363     p_station_t_newer = p_station->t_newer;
22364     time = localtime(&p_station->sec_heard);
22365 
22366     fprintf(stderr,"\t%s\t%ld\t%02d/%02d %02d:%02d:%02d\n",
22367             p_station->call_sign, (long int) p_station->sec_heard,
22368             time->tm_mon+1,time->tm_mday,
22369             time->tm_hour, time->tm_min, time->tm_sec);
22370   }
22371 }
22372 
22373 
22374