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 #ifdef HAVE_CONFIG_H
25   #include "config.h"
26 #endif  // HAVE_CONFIG_H
27 
28 #include "snprintf.h"
29 
30 #include <stdio.h>
31 #include <ctype.h>
32 #include <Xm/XmAll.h>
33 
34 // The following files support setting the system time from the GPS
35 #if TIME_WITH_SYS_TIME
36   // Define needed by some versions of Linux in order to define
37   // strptime()
38   #ifndef __USE_XOPEN
39     #define __USE_XOPEN
40   #endif
41   #include <sys/time.h>
42   #include <time.h>
43 #else   // TIME_WITH_SYS_TIME
44   #if HAVE_SYS_TIME_H
45     #include <sys/time.h>
46   #else  // HAVE_SYS_TIME_H
47     #include <time.h>
48   #endif // HAVE_SYS_TIME_H
49 #endif  // TIME_WITH_SYS_TIME
50 
51 #include <stdlib.h>
52 #include <unistd.h>
53 #include <sys/types.h>
54 
55 #include "gps.h"
56 #include "main.h"
57 #include "interface.h"
58 #include "lang.h"
59 #include "util.h"
60 
61 // Must be last include file
62 #include "leak_detection.h"
63 
64 
65 
66 char gps_gprmc[MAX_GPS_STRING+1];
67 char gps_gpgga[MAX_GPS_STRING+1];
68 char gps_sats[4] = "";
69 char gps_alt[8] = "";
70 char gps_spd[10] = "";
71 char gps_sunit[2] = "";
72 char gps_cse[10] = "";
73 int  gps_valid = 0; // 0=invalid, 1=valid, 2=2D Fix, 3=3D Fix
74 
75 int gps_stop_now;
76 
77 
78 
79 
80 
81 // This function is destructive to its first parameter
82 //
83 // GPRMC,UTC-Time,status(A/V),lat,N/S,lon,E/W,SOG,COG,UTC-Date,Mag-Var,E/W[*CHK]
84 // 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]
85 //
86 // The last field before the checksum is entirely optional, and in
87 // fact first appeared in NMEA 2.3 (fairly recently).  Most GPS's do
88 // not currently put out that field.  The field may be null or
89 // nonexistent including the comma.  Only "A" or "D" are considered
90 // to be active and reliable fixes if this field is present.
91 // Fix-Quality:
92 //  A: Autonomous
93 //  D: Differential
94 //  E: Estimated
95 //  N: Not Valid
96 //  S: Simulator
97 //
98 // $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
99 // $GPRMC,104748.821,A,4301.1492,N,08803.0374,W,0.085048,102.36,010605,,*1A
100 // $GPRMC,104749.821,A,4301.1492,N,08803.0377,W,0.054215,74.60,010605,,*2D
101 //
decode_gps_rmc(char * data,char * long_pos,int long_pos_length,char * lat_pos,int lat_pos_length,char * spd,char * unit,int unit_length,char * cse,time_t * stim,int * status)102 int decode_gps_rmc( char *data,
103                     char *long_pos,
104                     int long_pos_length,
105                     char *lat_pos,
106                     int lat_pos_length,
107                     char *spd,
108                     char *unit,
109                     int unit_length,
110                     char *cse,
111                     time_t *stim,
112                     int *status)
113 {
114 
115   char *temp_ptr;
116   char *temp_ptr2;
117   char temp_data[MAX_LINE_SIZE+1];    // Big in case we get concatenated packets (it happens!)
118   char sampletime[7]; // We ignore fractional seconds
119   char long_pos_x[11];
120   char long_ew;
121   char lat_pos_y[10];
122   char lat_ns;
123   char speed[10];
124   char speed_unit;
125   char course[8];
126   char sampledate[7];
127 
128 #ifdef HAVE_STRPTIME
129   char sampledatime[15];
130   char *tzp;
131   char tzn[512];
132   struct tm stm;
133 #endif // HAVE_STRPTIME
134 
135 
136 // We should check for a minimum line length before parsing,
137 // and check for end of input while tokenizing.
138 
139   if ( (data == NULL) || (strlen(data) < 37) )
140   {
141     return(0);  // Not enough data to parse position from.
142   }
143 
144   if (!(isRMC(data)))   // No GxRMC found
145   {
146     return(0);
147   }
148 
149   if(strchr(data,',') == NULL)  // No comma found
150   {
151     return(0);
152   }
153 
154   (void)strtok(data,",");   // get GxRMC and skip it
155   temp_ptr=strtok(NULL,",");   // get time
156 
157   if (temp_ptr == NULL)   // No comma found
158   {
159     return(0);
160   }
161 
162   xastir_snprintf(sampletime,
163                   sizeof(sampletime),
164                   "%s",
165                   temp_ptr);
166 
167   temp_ptr=strtok(NULL,",");  // get fix status
168 
169   if (temp_ptr == NULL) // No comma found
170   {
171     return(0);
172   }
173 
174   if (temp_ptr[0] == 'A')
175   {
176     *status = 1;
177   }
178   else
179   {
180     *status = 0;
181   }
182 
183   xastir_snprintf(temp_data,
184                   sizeof(temp_data),
185                   "%s",
186                   temp_ptr);
187   temp_data[2] = '\0';
188 
189   if (temp_data[0] != 'A')  // V is a warning but we can get good data still ?
190   {
191     return(0);  // Didn't find 'A' in the proper spot
192   }
193 
194   temp_ptr=strtok(NULL,",");  // get latitude
195 
196   if (temp_ptr == NULL)
197   {
198     return(0);  // Doesn't look like latitude
199   }
200 
201   // Newer GPS'es appear not to zero-fill on the left.  Check for
202   // the decimal point in all the possible places.
203   if (temp_ptr[1] != '.'
204       && temp_ptr[2] != '.'
205       && temp_ptr[3] != '.'
206       && temp_ptr[4] != '.')
207   {
208     return(0);  // Doesn't look like latitude
209   }
210 
211 // Note:  Starlink Invicta shows "lllll.ll" format for latitude in
212 // the GPRMC sentence, which would mean we'd need another term in
213 // the above, and would need to terminate at [10] below (making sure
214 // we extended the field another char as well to handle it).  I'm
215 // hoping it was a typo in the Starlink Invicta spec, as latitude
216 // never requires three digits for degrees.
217 
218   xastir_snprintf(lat_pos_y,
219                   sizeof(lat_pos_y),
220                   "%s",
221                   temp_ptr);
222   lat_pos_y[9] = '\0';
223 
224 // Note that some GPS's put out latitude with extra precision, such as 4801.1234
225 
226   // Check for comma char, replace with '\0'
227   temp_ptr2 = strstr(lat_pos_y, ",");
228   if (temp_ptr2)
229   {
230     temp_ptr2[0] = '\0';
231   }
232 
233   temp_ptr=strtok(NULL,",");  // get N-S
234 
235   if (temp_ptr == NULL)   // No comma found
236   {
237     return(0);
238   }
239 
240   xastir_snprintf(temp_data,
241                   sizeof(temp_data),
242                   "%s",
243                   temp_ptr);
244   temp_data[1] = '\0';
245 
246   lat_ns=toupper((int)temp_data[0]);
247 
248   if(lat_ns != 'N' && lat_ns != 'S')
249   {
250     return(0);  // Doesn't look like latitude
251   }
252 
253   temp_ptr=strtok(NULL,",");  // get long
254 
255   if (temp_ptr == NULL)
256   {
257     return(0);  // Doesn't look like longitude
258   }
259 
260   // Newer GPS'es appear not to zero-fill on the left.  Check for
261   // the decimal point in all the possible places.
262   if (temp_ptr[1] != '.'
263       && temp_ptr[2] != '.'
264       && temp_ptr[3] != '.'
265       && temp_ptr[4] != '.'
266       && temp_ptr[5] != '.')
267   {
268     return(0);  // Doesn't look like longitude
269   }
270 
271 
272   xastir_snprintf(long_pos_x,
273                   sizeof(long_pos_x),
274                   "%s",
275                   temp_ptr);
276   long_pos_x[10] = '\0';
277 
278 // Note that some GPS's put out longitude with extra precision, such as 12201.1234
279 
280   // Check for comma char, replace with '\0'
281   temp_ptr2 = strstr(long_pos_x, ",");
282   if (temp_ptr2)
283   {
284     temp_ptr2[0] = '\0';
285   }
286 
287   temp_ptr=strtok(NULL,",");  // get E-W
288 
289   if (temp_ptr == NULL)   // No comma found
290   {
291     return(0);
292   }
293 
294   xastir_snprintf(temp_data,
295                   sizeof(temp_data),
296                   "%s",
297                   temp_ptr);
298   temp_data[1] = '\0';
299 
300   long_ew=toupper((int)temp_data[0]);
301 
302   if (long_ew != 'E' && long_ew != 'W')
303   {
304     return(0);  // Doesn't look like longitude
305   }
306 
307   temp_ptr=strtok(NULL,",");  // Get speed
308 
309   if (temp_ptr == 0)  // No comma found
310   {
311     return(0);
312   }
313 
314   xastir_snprintf(speed,
315                   sizeof(speed),
316                   "%s",
317                   temp_ptr);
318   speed[9] = '\0';
319 
320   speed_unit='K';
321   temp_ptr=strtok(NULL,",");  // Get course
322 
323   if (temp_ptr == NULL)   // No comma found
324   {
325     return(0);
326   }
327 
328   xastir_snprintf(course,
329                   sizeof(course),
330                   "%s",
331                   temp_ptr);
332   course[7] = '\0';
333 
334   temp_ptr=strtok(NULL,",");   // get date of fix
335 
336   if (temp_ptr == NULL)   // No comma found
337   {
338     return(0);
339   }
340 
341   xastir_snprintf(sampledate,
342                   sizeof(sampledate),
343                   "%s",
344                   temp_ptr);
345   sampledate[6] = '\0';
346 
347 
348   // Data is good
349   xastir_snprintf(long_pos, long_pos_length, "%s%c", long_pos_x,long_ew);
350   xastir_snprintf(lat_pos, lat_pos_length, "%s%c", lat_pos_y, lat_ns);
351   xastir_snprintf(spd, 10, "%s", speed);
352   xastir_snprintf(unit, unit_length, "%c", speed_unit);
353   xastir_snprintf(cse, 10, "%s", course);
354 
355 #ifdef HAVE_STRPTIME
356   // Translate date/time into time_t GPS time is in UTC.  First,
357   // save existing TZ Then set conversion to UTC, then set back to
358   // existing TZ.
359   tzp=getenv("TZ");
360   if ( tzp == NULL )
361   {
362     tzp = "";
363   }
364   xastir_snprintf(tzn,
365                   sizeof(tzn),
366                   "TZ=%s",
367                   tzp);
368   putenv("TZ=UTC");
369   tzset();
370   xastir_snprintf(sampledatime,
371                   sizeof(sampledatime),
372                   "%s%s",
373                   sampledate,
374                   sampletime);
375   (void)strptime(sampledatime, "%d%m%y%H%M%S", &stm);
376   *stim=mktime(&stm);
377   putenv(tzn);
378   tzset();
379 #endif  // HAVE_STRPTIME
380 
381   //fprintf(stderr,"Speed %s\n",spd);
382   return(1);
383 }
384 
385 
386 
387 
388 
389 // This function is destructive to its first parameter
390 //
391 // 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]
392 // 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]
393 //
394 // GPS-Quality:
395 //  0: Invalid Fix
396 //  1: GPS Fix
397 //  2: DGPS Fix
398 //  3: PPS Fix
399 //  4: RTK Fix
400 //  5: Float RTK Fix
401 //  6: Estimated (dead-reckoning) Fix
402 //  7: Manual Input Mode
403 //  8: Simulation Mode
404 //
405 // $GPGGA,170834,4124.8963,N,08151.6838,W,1,05,1.5,280.2,M,-34.0,M,,,*75
406 // $GPGGA,104438.833,4301.1439,N,08803.0338,W,1,05,1.8,185.8,M,-34.2,M,0.0,0000*40
407 
408 // NOTE:  while the above specifically refers to $GP strings for the GPS
409 // receivers, there are others such as GNSS, GLONASS, Beidou, and Gallileo
410 // that differ only in the third character.  We support those, too.
411 //
decode_gps_gga(char * data,char * long_pos,int long_pos_length,char * lat_pos,int lat_pos_length,char * sats,char * alt,char * aunit,int * status)412 int decode_gps_gga( char *data,
413                     char *long_pos,
414                     int long_pos_length,
415                     char *lat_pos,
416                     int lat_pos_length,
417                     char *sats,
418                     char *alt,
419                     char *aunit,
420                     int *status )
421 {
422 
423   char *temp_ptr;
424   char *temp_ptr2;
425   char temp_data[MAX_LINE_SIZE+1];    // Big in case we get concatenated packets (it happens!)
426   char long_pos_x[11];
427   char long_ew;
428   char lat_pos_y[10];
429   char lat_ns;
430   char sats_visible[4];
431   char altitude[8];
432   char alt_unit;
433 
434 
435 // We should check for a minimum line length before parsing,
436 // and check for end of input while tokenizing.
437 
438 
439   if ( (data == NULL) || (strlen(data) < 35) )  // Not enough data to parse position from.
440   {
441     return(0);
442   }
443 
444   if (!(isGGA(data)))
445   {
446     return(0);
447   }
448 
449   if (strchr(data,',') == NULL)
450   {
451     return(0);
452   }
453 
454   if (strtok(data,",") == NULL) // get GPGGA and skip it
455   {
456     return(0);
457   }
458 
459   if(strtok(NULL,",") == NULL)    // get time and skip it
460   {
461     return(0);
462   }
463 
464   temp_ptr = strtok(NULL,",");    // get latitude
465 
466   if (temp_ptr == NULL)
467   {
468     return(0);
469   }
470 
471   xastir_snprintf(lat_pos_y,
472                   sizeof(lat_pos_y),
473                   "%s",
474                   temp_ptr);
475   lat_pos_y[9] = '\0';
476 
477 // Note that some GPS's put out latitude with extra precision, such as 4801.1234
478 
479   // Check for comma char, replace with '\0'
480   temp_ptr2 = strstr(lat_pos_y, ",");
481   if (temp_ptr2)
482   {
483     temp_ptr2[0] = '\0';
484   }
485 
486   temp_ptr = strtok(NULL,",");    // get N-S
487 
488   if (temp_ptr == NULL)
489   {
490     return(0);
491   }
492 
493   xastir_snprintf(temp_data,
494                   sizeof(temp_data),
495                   "%s",
496                   temp_ptr);
497   temp_data[1] = '\0';
498 
499   lat_ns=toupper((int)temp_data[0]);
500 
501   if(lat_ns != 'N' && lat_ns != 'S')
502   {
503     return(0);
504   }
505 
506   temp_ptr = strtok(NULL,",");    // get long
507 
508   if(temp_ptr == NULL)
509   {
510     return(0);
511   }
512 
513   xastir_snprintf(long_pos_x,
514                   sizeof(long_pos_x),
515                   "%s",
516                   temp_ptr);
517   long_pos_x[10] = '\0';
518 
519 // Note that some GPS's put out longitude with extra precision, such as 12201.1234
520 
521   // Check for comma char, replace with '\0'
522   temp_ptr2 = strstr(long_pos_x, ",");
523   if (temp_ptr2)
524   {
525     temp_ptr2[0] = '\0';
526   }
527 
528   temp_ptr = strtok(NULL,",");    // get E-W
529 
530   if (temp_ptr == NULL)
531   {
532     return(0);
533   }
534 
535   xastir_snprintf(temp_data,
536                   sizeof(temp_data),
537                   "%s",
538                   temp_ptr);
539   temp_data[1] = '\0';
540 
541   long_ew=toupper((int)temp_data[0]);
542 
543   if (long_ew != 'E' && long_ew != 'W')
544   {
545     return(0);
546   }
547 
548   temp_ptr = strtok(NULL,",");    // get FIX Quality
549 
550   if (temp_ptr == NULL)
551   {
552     return(0);
553   }
554 
555   xastir_snprintf(temp_data,
556                   sizeof(temp_data),
557                   "%s",
558                   temp_ptr);
559   temp_data[1] = '\0';
560 
561   // '0' = bad fix, positive numbers = ok
562   if(temp_data[0] == '0')
563   {
564     return(0);
565   }
566 
567   // Save the fix quality in "status"
568   *status = atoi(temp_data);
569 
570   temp_ptr=strtok(NULL,",");      // Get sats vis
571 
572   if (temp_ptr == NULL)
573   {
574     return(0);
575   }
576 
577   xastir_snprintf(sats_visible,
578                   sizeof(sats_visible),
579                   "%s",
580                   temp_ptr);
581   sats_visible[2] = '\0';
582 
583   temp_ptr=strtok(NULL,",");      // get hoz dil
584 
585   if (temp_ptr == NULL)
586   {
587     return(0);
588   }
589 
590   temp_ptr=strtok(NULL,",");      // Get altitude
591 
592   if (temp_ptr == NULL)
593   {
594     return(0);
595   }
596 
597   // Get altitude
598   xastir_snprintf(altitude,
599                   sizeof(altitude),
600                   "%s",
601                   temp_ptr);
602   altitude[7] = '\0';
603 
604   temp_ptr=strtok(NULL,",");      // get UNIT
605 
606   if (temp_ptr == NULL)
607   {
608     return(0);
609   }
610 
611   // get UNIT
612   xastir_snprintf(temp_data,
613                   sizeof(temp_data),
614                   "%s",
615                   temp_ptr);
616   temp_data[1] = '\0';
617 
618   alt_unit=temp_data[0];
619 
620   // Data is good
621   xastir_snprintf(long_pos, long_pos_length, "%s%c", long_pos_x, long_ew);
622   xastir_snprintf(lat_pos, lat_pos_length, "%s%c", lat_pos_y, lat_ns);
623   xastir_snprintf(sats, 4, "%s", sats_visible);
624   xastir_snprintf(alt, 8, "%s", altitude);
625   xastir_snprintf(aunit, 2, "%c", alt_unit);
626 
627   return(1);
628 }
629 
630 
631 
632 
633 
634 //
635 // Note that the length of "gps_line_data" can be up to
636 // MAX_DEVICE_BUFFER, which is currently set to 4096.
637 //
gps_data_find(char * gps_line_data,int port)638 int gps_data_find(char *gps_line_data, int port)
639 {
640 
641   char long_pos[20],lat_pos[20],aunit[2];
642   time_t t;
643   char temp_str[MAX_GPS_STRING+1];
644   int have_valid_string = 0;
645 #ifndef __CYGWIN__
646   struct timeval tv;
647   struct timezone tz;
648 #endif  // __CYGWIN__
649 
650 
651 
652   if (isRMC(gps_line_data))
653   {
654 
655     if (debug_level & 128)
656     {
657       char filtered_data[MAX_LINE_SIZE+1];
658 
659       xastir_snprintf(filtered_data,
660                       sizeof(filtered_data),
661                       "%s",
662                       gps_line_data);
663 
664       makePrintable(filtered_data);
665       fprintf(stderr,"Got RMC %s\n", filtered_data);
666     }
667 
668     if (debug_level & 128)
669     {
670       // Got GPS RMC String
671       statusline(langcode("BBARSTA015"),0);
672     }
673 
674     xastir_snprintf(gps_gprmc,
675                     sizeof(gps_gprmc),
676                     "%s",
677                     gps_line_data);
678 
679     xastir_snprintf(temp_str, sizeof(temp_str), "%s", gps_gprmc);
680     // decode_gps_rmc is destructive to its first parameter
681     if (decode_gps_rmc( temp_str,
682                         long_pos,
683                         sizeof(long_pos),
684                         lat_pos,
685                         sizeof(lat_pos),
686                         gps_spd,
687                         gps_sunit,
688                         sizeof(gps_sunit),
689                         gps_cse,
690                         &t,
691                         &gps_valid ) == 1)      // mod station data
692     {
693       // got GPS data
694       have_valid_string++;
695       if (debug_level & 128)
696         fprintf(stderr,"RMC <%s> <%s><%s> %c <%s>\n",
697                 long_pos,lat_pos,gps_spd,gps_sunit[0],gps_cse);
698 
699       if (debug_level & 128)
700       {
701         fprintf(stderr,"Checking for Time Set on %d (%d)\n",
702                 port, devices[port].set_time);
703       }
704 
705 // Don't set the time if it's a Cygwin system.  Causes problems with
706 // date, plus time can be an hour off if daylight savings time is
707 // enabled on Windows.
708 //
709 #ifndef __CYGWIN__
710       if (devices[port].set_time)
711       {
712         tv.tv_sec=t;
713         tv.tv_usec=0;
714         tz.tz_minuteswest=0;
715         tz.tz_dsttime=0;
716 
717         if (debug_level & 128)
718         {
719           fprintf(stderr,"Setting Time %ld EUID: %d, RUID: %d\n",
720                   (long)t, (int)getuid(), (int)getuid());
721         }
722 
723 #ifdef HAVE_SETTIMEOFDAY
724 
725         ENABLE_SETUID_PRIVILEGE;
726         settimeofday(&tv, &tz);
727         DISABLE_SETUID_PRIVILEGE;
728 
729 #endif  // HAVE_SETTIMEOFDAY
730 
731       }
732 
733 #endif  // __CYGWIN__
734 
735     }
736   }
737   else
738   {
739     if (debug_level & 128)
740     {
741       int i;
742       fprintf(stderr,"Not $GxRMC: ");
743       for (i = 0; i<7; i++)
744       {
745         fprintf(stderr,"%c", gps_line_data[i]);
746       }
747       fprintf(stderr,"\n");
748     }
749   }
750 
751   if (isGGA(gps_line_data))
752   {
753 
754     if (debug_level & 128)
755     {
756       char filtered_data[MAX_LINE_SIZE+1];
757 
758       xastir_snprintf(filtered_data,
759                       sizeof(filtered_data),
760                       "%s",
761                       gps_line_data);
762 
763       makePrintable(filtered_data);
764       fprintf(stderr,"Got GGA %s\n", filtered_data);
765     }
766 
767     if (debug_level & 128)
768     {
769       // Got GPS GGA String
770       statusline(langcode("BBARSTA016"),0);
771     }
772 
773     xastir_snprintf(gps_gpgga,
774                     sizeof(gps_gpgga),
775                     "%s",
776                     gps_line_data);
777 
778     xastir_snprintf(temp_str, sizeof(temp_str), "%s", gps_gpgga);
779 
780     // decode_gps_gga is destructive to its first parameter
781     if ( decode_gps_gga( temp_str,
782                          long_pos,
783                          sizeof(long_pos),
784                          lat_pos,
785                          sizeof(lat_pos),
786                          gps_sats,
787                          gps_alt,
788                          aunit,
789                          &gps_valid ) == 1)   // mod station data
790     {
791       // got GPS data
792       have_valid_string++;
793       if (debug_level & 128)
794         fprintf(stderr,"GGA <%s> <%s> <%s> <%s> %c\n",
795                 long_pos,lat_pos,gps_sats,gps_alt,aunit[0]);
796     }
797   }
798   else
799   {
800     if (debug_level & 128)
801     {
802       int i;
803       fprintf(stderr,"Not $GPGGA: ");
804       for (i = 0; i<7; i++)
805       {
806         fprintf(stderr,"%c",gps_line_data[i]);
807       }
808       fprintf(stderr,"\n");
809     }
810   }
811 
812 
813   if (have_valid_string)
814   {
815 
816     if (debug_level & 128)
817     {
818       statusline(langcode("BBARSTA037"),0);
819     }
820 
821     // Go update my screen position
822     my_station_gps_change(long_pos,lat_pos,gps_cse,gps_spd,
823                           gps_sunit[0],gps_alt,gps_sats);
824 
825     // gps_stop_now is how we tell main.c that we've got a valid GPS string.
826     // Only useful for HSP mode?
827     if (!gps_stop_now)
828     {
829       gps_stop_now=1;
830     }
831 
832     // If HSP port, shutdown gps for timed interval
833     if (port_data[port].device_type == DEVICE_SERIAL_TNC_HSP_GPS)
834     {
835       // return dtr to normal
836       port_dtr(port,0);
837     }
838   }
839   return(have_valid_string);
840 }
841 
842 
843 
844 
845 
846 static char checksum[3];
847 
848 
849 
850 
851 
852 // Function to compute checksums for NMEA sentences
853 //
854 // Input: "$............*"
855 // Output: Two character string containing the checksum
856 //
857 // Checksum is computed from the '$' to the '*', but not including
858 // these two characters.  It is an 8-bit Xor of the characters
859 // specified, encoded in hexadecimal format.
860 //
nmea_checksum(char * nmea_sentence)861 char *nmea_checksum(char *nmea_sentence)
862 {
863   int i;
864   int sum = 0;
865   int right;
866   int left;
867   char convert[17] = "0123456789ABCDEF";
868 
869 
870   for (i = 1; i < ((int)strlen(nmea_sentence) - 1); i++)
871   {
872     sum = sum ^ nmea_sentence[i];
873   }
874 
875   right = sum % 16;
876   left = (sum / 16) % 16;
877 
878   xastir_snprintf(checksum, sizeof(checksum), "%c%c",
879                   convert[left],
880                   convert[right]);
881 
882   return(checksum);
883 }
884 
885 
886 
887 
888 
889 
890 // Function which will send an NMEA sentence to a Garmin GPS which
891 // will create a waypoint if the Garmin is set to NMEA-in/NMEA-out
892 // mode.  The sentence looks like this:
893 //
894 // $GPWPL,4807.038,N,01131.000,E,WPTNME*31
895 //
896 // $GPWPL,4849.65,N,06428.53,W,0001*54
897 // $GPWPL,4849.70,N,06428.50,W,0002*50
898 //
899 // 4807.038,N   Latitude
900 // 01131.000,E  Longitude
901 // WPTNME       Waypoint Name (stick to 6 chars for compatibility?)
902 // *31          Checksum, always begins with '*'
903 //
904 //
905 // Future implementation ideas:
906 //
907 // Create linked list of waypoints/location.
908 // Use the list to prevent multiple writes of the same waypoint if
909 // nothing has changed.
910 //
911 // Use the list to check distance of previously-written waypoints.
912 // If we're out of range, delete the waypoint and remove it from the
913 // list.
914 //
915 // Perhaps write the list to disk also.  As we shut down, delete the
916 // waypoints (self-cleaning).  As we come up, load them in again?
917 // We could also just continue cleaning out waypoints that are
918 // out-of-range since the last time we ran the program.  That's
919 // probably a better solution.
920 //
create_garmin_waypoint(long latitude,long longitude,char * call_sign)921 void create_garmin_waypoint(long latitude,long longitude,char *call_sign)
922 {
923   char short_callsign[10];
924   char lat_string[15];
925   char long_string[15];
926   char lat_char;
927   char long_char;
928   int i,j,len;
929   char out_string[80];
930   char out_string2[80];
931 
932 
933   convert_lat_l2s(latitude,
934                   lat_string,
935                   sizeof(lat_string),
936                   CONVERT_HP_NOSP);
937   lat_char = lat_string[strlen(lat_string) - 1];
938   lat_string[strlen(lat_string) - 1] = '\0';
939 
940   convert_lon_l2s(longitude,
941                   long_string,
942                   sizeof(long_string),
943                   CONVERT_HP_NOSP);
944   long_char = long_string[strlen(long_string) - 1];
945   long_string[strlen(long_string) - 1] = '\0';
946 
947   len = strlen(call_sign);
948   if (len > 9)
949   {
950     len = 9;
951   }
952 
953   j = 0;
954   for (i = 0; i <= len; i++)      // Copy the '\0' as well
955   {
956     if (call_sign[i] != '-')    // We don't want the dash
957     {
958       short_callsign[j++] = call_sign[i];
959     }
960   }
961   short_callsign[6] = '\0';   // Truncate at 6 chars
962 
963   // Convert to upper case.  Garmin's don't seem to like lower
964   // case waypoint names
965   to_upper(short_callsign);
966 
967   //fprintf(stderr,"Creating waypoint for %s:%s\n",call_sign,short_callsign);
968 
969   xastir_snprintf(out_string, sizeof(out_string),
970                   "$GPWPL,%s,%c,%s,%c,%s*",
971                   lat_string,
972                   lat_char,
973                   long_string,
974                   long_char,
975                   short_callsign);
976 
977   nmea_checksum(out_string);
978 
979   strncpy(out_string2, out_string, sizeof(out_string2));
980   out_string2[sizeof(out_string2)-1] = '\0';  // Terminate string
981 
982   strcat(out_string2, checksum);
983 
984   output_waypoint_data(out_string2);
985 
986   //fprintf(stderr,"%s\n",out_string2);
987 }
988 
989 // Test if this is a GGA string, irrespective of what constellation it
990 // might be for.  This should allow us to support GPS, GLONASS, Gallileo,
991 // Beidou, or GNSS receivers, and multi-constellation receivers.
isGGA(char * gps_line_data)992 int isGGA(char *gps_line_data)
993 {
994   int retval;
995   retval = (strncmp(gps_line_data,"$",1)==0);
996   retval = retval && ((strlen(gps_line_data)>7)
997                       && strncmp(&(gps_line_data[3]),"GGA,",4)==0);
998   retval = retval && (strchr("PNALB",gps_line_data[2])!=0);
999   return (retval);
1000 }
1001 
1002 // Test if this is an RMC string.   See comments for isGGA.
isRMC(char * gps_line_data)1003 int isRMC(char *gps_line_data)
1004 {
1005   int retval;
1006   retval = (strncmp(gps_line_data,"$",1)==0);
1007   retval = retval && ((strlen(gps_line_data)>7)
1008                       && strncmp(&(gps_line_data[3]),"RMC,",4)==0);
1009   retval = retval && (strchr("PNALB",gps_line_data[2])!=0);
1010   return (retval);
1011 }
1012