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