1 /*
2     Jeeps wrapper for Garmin serial protocol.
3 
4     Copyright (C) 2002, 2003, 2004, 2005, 2006  Robert Lipe, robertlipe+source@gpsbabel.org
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 
20  */
21 
22 #include <cctype>               // for isalpha, toupper
23 #include <climits>              // for INT_MAX
24 #include <cmath>                // for atan2, floor, sqrt
25 #include <csetjmp>              // for setjmp
26 #include <cstdio>               // for fprintf, fflush, snprintf, sprintf
27 #include <cstdlib>              // for atoi, strtol
28 #include <cstring>              // for memcpy, strlen, strncpy, strchr
29 #include <ctime>                // for time_t
30 
31 #include <QtCore/QByteArray>    // for QByteArray
32 #include <QtCore/QChar>         // for QChar
33 #include <QtCore/QString>       // for QString
34 #include <QtCore/Qt>            // for CaseInsensitive
35 #include <QtCore/QtGlobal>      // for qPrintable, foreach
36 
37 #include "defs.h"
38 #include "cet_util.h"           // for cet_convert_init, cet_cs_vec_utf8
39 #include "format.h"             // for Format
40 #include "garmin_device_xml.h"  // for gdx_get_info, gdx_info, gdx_file, gdx_jmp_buf
41 #include "garmin_fs.h"          // for garmin_fs_garmin_after_read, garmin_fs_garmin_before_write
42 #include "garmin_tables.h"      // for gt_find_icon_number_from_desc, PCX, gt_find_desc_from_icon_number
43 #include "grtcirc.h"            // for DEG
44 #include "jeeps/gps.h"
45 #include "jeeps/gpsserial.h"
46 #include "src/core/datetime.h"  // for DateTime
47 #include "vecs.h"               // for Vecs
48 
49 #define MYNAME "GARMIN"
50 static const char* portname;
51 static short_handle mkshort_handle;
52 static GPS_PWay* tx_waylist;
53 static GPS_PWay* tx_routelist;
54 static GPS_PWay* cur_tx_routelist_entry;
55 static GPS_PTrack* tx_tracklist;
56 static GPS_PTrack* cur_tx_tracklist_entry;
57 static int my_track_count = 0;
58 static char* getposn = nullptr;
59 static char* poweroff = nullptr;
60 static char* eraset = nullptr;
61 static char* resettime = nullptr;
62 static char* snlen = nullptr;
63 static char* snwhiteopt = nullptr;
64 static char* deficon = nullptr;
65 static char* category = nullptr;
66 static char* categorybitsopt = nullptr;
67 static char* baudopt = nullptr;
68 static int baud = 0;
69 static int categorybits;
70 static int receiver_must_upper = 1;
71 
72 static Format* gpx_vec;
73 
74 #define MILITANT_VALID_WAYPT_CHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
75 
76 /* Technically, even this is a little loose as spaces arent allowed */
77 static const char* valid_waypt_chars = MILITANT_VALID_WAYPT_CHARS " ";
78 
79 static
80 QVector<arglist_t> garmin_args = {
81   {
82     "snlen", &snlen, "Length of generated shortnames", nullptr,
83     ARGTYPE_INT, "1", nullptr, nullptr
84   },
85   {
86     "snwhite", &snwhiteopt, "Allow whitespace synth. shortnames",
87     nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
88   },
89   { "deficon", &deficon, "Default icon name", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr },
90   {
91     "get_posn", &getposn, "Return current position as a waypoint",
92     nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
93   },
94   {
95     "power_off", &poweroff, "Command unit to power itself down",
96     nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
97   },
98   {
99     "erase_t", &eraset, "Erase existing courses when writing new ones",
100     nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
101   },
102   {
103     "resettime", &resettime, "Sync GPS time to computer time",
104     nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
105   },
106   {
107     "category", &category, "Category number to use for written waypoints",
108     nullptr, ARGTYPE_INT, "1", "16", nullptr
109   },
110   {
111     "bitscategory", &categorybitsopt, "Bitmap of categories",
112     nullptr, ARGTYPE_INT, "1", "65535", nullptr
113   },
114   {
115     "baud", &baudopt, "Speed in bits per second of serial port (baud=9600)",
116     nullptr, ARGTYPE_INT, ARG_NOMINMAX, nullptr },
117 
118 };
119 
120 static const char* d103_symbol_from_icon_number(unsigned int n);
121 static int d103_icon_number_from_symbol(const QString& s);
122 
123 
124 static void
rw_init(const QString & fname)125 rw_init(const QString& fname)
126 {
127   receiver_must_upper = 1;
128   const char* receiver_charset = nullptr;
129 
130   if (!mkshort_handle) {
131     mkshort_handle = mkshort_new_handle();
132   }
133 
134   if (global_opts.debug_level > 0)  {
135     GPS_Enable_Warning();
136     GPS_Enable_User();
137   }
138   if (global_opts.debug_level > 1)  {
139     GPS_Enable_Diagnose();
140   }
141   GPS_Enable_Error();
142 
143   if (poweroff) {
144     GPS_Command_Off(qPrintable(fname));
145     return;
146   }
147 
148   if (categorybitsopt) {
149     categorybits = strtol(categorybitsopt, nullptr, 0);
150   }
151 
152   if (baudopt) {
153     baud = strtol(baudopt, nullptr, 0);
154     switch (baud) {
155       case 9600:
156       case 19200:
157       case 38400:
158       case 57600:
159       case 115200:
160         break;
161       default:
162         fatal("Baud rate %d is not supported\n", baud);
163     }
164   }
165 
166   if (GPS_Init(qPrintable(fname)) < 0) {
167     fatal(MYNAME ":Can't init %s\n", qPrintable(fname));
168   }
169 
170   /*
171    * THis is Gross. The B&W Vista sometimes sets its time decades into
172    * the future with no way to reset it.  This apparently can "cure"
173    * an affected unit.
174    */
175   if (resettime) {
176     GPS_User("Issuing Time Reset...\n");
177     GPS_Command_Send_Time(qPrintable(fname), current_time().toTime_t());
178     GPS_User("done.\n");
179   }
180 
181   portname = xstrdup(qPrintable(fname));
182 
183   if (baud && baud != DEFAULT_BAUD) {
184     if (0 == GPS_Set_Baud_Rate(portname, baud)) {
185       gps_baud_rate = baud;
186     }
187   }
188 
189   /*
190    * Grope the unit we're talking to to set setshort_length to
191    * 	20 for  the V,
192    * 	10 for Street Pilot, (old) Rhino, 76
193    * 	6 for the III, 12, emap, and etrex
194    * Fortunately, getting this "wrong" only results in ugly names
195    * when we're using the synthesize_shortname path.
196    */
197   int receiver_short_length = 10;
198 
199   switch (gps_waypt_type) {	/* waypoint type as defined by jeeps */
200   case 0:
201     fatal("Garmin unit %d does not support waypoint xfer.",
202           gps_save_id);
203 
204     break;
205   case 100:	/* The GARMIN GPS Interface Specification, */
206   case 101:	/* says these waypoint types use an ident */
207   case 102:	/* length of 6.  Waypoint types 106, 108 */
208   case 103:	/* and 109 are all variable  length    */
209   case 104:
210   case 105:
211   case 107:
212   case 150:
213   case 151:
214   case 152:
215   case 154:
216   case 155:
217     receiver_short_length = 6;
218     break;
219   case 106:	/* Waypoint types with variable ident length */
220   case 108: 	/* Need GPSr id to know the actual length */
221   case 109:
222   case 110:
223     switch (gps_save_id) {
224     case 130:	/* Garmin Etrex (yellow) */
225       receiver_short_length = 6;
226       break;
227     case 295: 	/* eTrex (yellow, fw v. 3.30) */
228     case 696: 	/* eTrex HC */
229     case 574: 	/* Geko 201 */
230       receiver_short_length = 6;
231       valid_waypt_chars =
232         MILITANT_VALID_WAYPT_CHARS " +-";
233       setshort_badchars(mkshort_handle, "\"$.,'!");
234       break;
235 
236     case 155:	/* Garmin V */
237     case 404:	/* SP2720 */
238     case 520:	/* SP2820 */
239       receiver_short_length = 20;
240       break;
241     case 382: 	/* C320 */
242       receiver_short_length = 30;
243       receiver_must_upper = 0;
244       break;
245     case 292: /* (60|76)C[S]x series */
246     case 421: /* Vista|Legend Cx */
247     case 694: /* Legend HCx */
248     case 695: /* Vista HC */
249     case 786: /* HC model */
250     case 957: /* Legend HC */
251       receiver_short_length = 14;
252       snwhiteopt = xstrdup("1");
253       receiver_must_upper = 0;
254       /* This might be 8859-1 */
255       receiver_charset = CET_CHARSET_MS_ANSI;
256       break;
257     case 291: /* GPSMAP 60CS, probably others */
258     case 1095: /* GPS 72H */
259       receiver_short_length = 10;
260       valid_waypt_chars = MILITANT_VALID_WAYPT_CHARS " +-";
261       setshort_badchars(mkshort_handle, "\"$.,'!");
262       break;
263     case 231: /* Quest */
264     case 463: /* Quest 2 */
265       receiver_must_upper = 0;
266       receiver_short_length = 30;
267       receiver_charset = CET_CHARSET_MS_ANSI;
268       break;
269     case 577: // Rino 530HCx Version 2.50
270       receiver_must_upper = 0;
271       receiver_short_length = 14;
272       break;
273     case 429: // Streetpilot i3
274       receiver_must_upper = 0;
275       receiver_charset = CET_CHARSET_MS_ANSI;
276       receiver_short_length = 30;
277       break;
278     case 484: // Forerunner 305
279       receiver_short_length = 8;
280       break;
281     case 260: /* GPSMap 296 */
282     default:
283       break;
284     }
285     break;
286   default:
287     break;
288 
289   }
290 
291   if (global_opts.debug_level > 0)  {
292     fprintf(stderr, "Waypoint type: %d\n"
293             "Chosen waypoint length %d\n",
294             gps_waypt_type, receiver_short_length);
295     if (gps_category_type) {
296       fprintf(stderr, "Waypoint category type: %d\n",
297               gps_category_type);
298     }
299   }
300 
301   /*
302    * If the user provided a short_length, override the calculated value.
303    */
304   if (snlen) {
305     setshort_length(mkshort_handle, atoi(snlen));
306   } else {
307     setshort_length(mkshort_handle, receiver_short_length);
308   }
309 
310   if (snwhiteopt) {
311     setshort_whitespace_ok(mkshort_handle, atoi(snwhiteopt));
312   }
313 
314   /*
315    * Until Garmins documents how to determine valid character space
316    * for the new models, we just release this safety check manually.
317    */
318   if (receiver_must_upper) {
319     setshort_goodchars(mkshort_handle, valid_waypt_chars);
320   } else {
321     setshort_badchars(mkshort_handle, "");
322   }
323 
324   setshort_mustupper(mkshort_handle, receiver_must_upper);
325 
326   /*
327    * This used to mean something when we used cet, but these days this
328    * format either use implicit QString conversions (utf8) which is
329    * likely a bug, or we have hard coded QString::fromLatin1 or CSTRc.
330    * So all the above detection of receiver_charset is for naught.
331    * But perhaps we will use an appropriate codec based on receiver_charset
332    * someday.
333    */
334   if (receiver_charset) {
335     cet_convert_init(receiver_charset, 1);
336   }
337 }
338 
339 static void
rd_init(const QString & fname)340 rd_init(const QString& fname)
341 {
342   if (setjmp(gdx_jmp_buf)) {
343     const gdx_info* gi = gdx_get_info();
344     gpx_vec = Vecs::Instance().find_vec("gpx");
345     gpx_vec->rd_init(gi->from_device.canon);
346   } else {
347     gpx_vec = nullptr;
348     rw_init(fname);
349   }
350 }
351 
352 static void
rw_deinit()353 rw_deinit()
354 {
355   if (gps_baud_rate != DEFAULT_BAUD) {
356     if (0 == GPS_Set_Baud_Rate(portname, DEFAULT_BAUD)) {
357       gps_baud_rate = baud;
358     }
359   }
360 
361   if (mkshort_handle) {
362     mkshort_del_handle(&mkshort_handle);
363   }
364 
365   xfree(portname);
366   portname = nullptr;
367 }
368 
369 static int
waypt_read_cb(int total_ct,GPS_PWay *)370 waypt_read_cb(int total_ct, GPS_PWay* )
371 {
372   static int i;
373 
374   if (global_opts.verbose_status) {
375     i++;
376     waypt_status_disp(total_ct, i);
377   }
378   return 0;
379 }
380 
381 static void
waypt_read()382 waypt_read()
383 {
384   int n;
385   GPS_PWay* way = nullptr;
386 
387   if (getposn) {
388     auto* wpt = new Waypoint;
389     wpt->latitude = gps_save_lat;
390     wpt->longitude = gps_save_lon;
391     wpt->shortname = "Position";
392     if (gps_save_time) {
393       wpt->SetCreationTime(gps_save_time);
394     }
395     waypt_add(wpt);
396     return;
397   }
398 
399   if ((n = GPS_Command_Get_Waypoint(portname, &way, waypt_read_cb)) < 0) {
400     fatal(MYNAME  ":Can't get waypoint from %s\n", portname);
401   }
402 
403   for (int i = 0; i < n; i++) {
404     auto* wpt_tmp = new Waypoint;
405 
406     wpt_tmp->shortname = QString::fromLatin1(way[i]->ident);
407     wpt_tmp->description = QString::fromLatin1(way[i]->cmnt);
408     wpt_tmp->shortname = wpt_tmp->shortname.simplified();
409     wpt_tmp->description = wpt_tmp->description.simplified();
410     wpt_tmp->longitude = way[i]->lon;
411     wpt_tmp->latitude = way[i]->lat;
412     if (gps_waypt_type == 103) {
413       wpt_tmp->icon_descr = d103_symbol_from_icon_number(
414                               way[i]->smbl);
415     } else {
416       wpt_tmp->icon_descr = gt_find_desc_from_icon_number(
417                               way[i]->smbl, PCX);
418     }
419     /*
420      * If a unit doesn't store altitude info (i.e. a D103)
421      * gpsmem will default the alt to INT_MAX.   Other units
422      * (I can't recall if it was the V (D109) hor the Vista (D108)
423      * return INT_MAX+1, contrary to the Garmin protocol doc which
424      * says they should report 1.0e25.   So we'll try to trap
425      * all the cases here.     Yes, libjeeps should probably
426      * do this and not us...
427      */
428     if ((way[i]->alt == (float)(1U<<31)) ||
429         (way[i]->alt == INT_MAX) ||
430         (way[i]->alt >= (float) 1.0e20)
431        ) {
432       wpt_tmp->altitude = unknown_alt;
433     } else {
434       wpt_tmp->altitude = way[i]->alt;
435     }
436     if (way[i]->time_populated) {
437       wpt_tmp->SetCreationTime(way[i]->time);
438     }
439     garmin_fs_garmin_after_read(way[i], wpt_tmp, gps_waypt_type);
440     waypt_add(wpt_tmp);
441     GPS_Way_Del(&way[i]);
442   }
443   if (way) {
444     xfree(way);
445   }
446 }
447 
lap_read_nop_cb(int,struct GPS_SWay **)448 static int lap_read_nop_cb(int, struct GPS_SWay**)
449 {
450   return 0;
451 }
452 
453 // returns 1 if the waypoint's start_time can be found
454 // in the laps array, 0 otherwise
checkWayPointIsAtSplit(Waypoint * wpt,GPS_PLap * laps,int nlaps)455 static unsigned int checkWayPointIsAtSplit(Waypoint* wpt, GPS_PLap* laps, int nlaps)
456 {
457   int result = 0;
458 
459   if ((laps != nullptr) && (nlaps > 0)) {
460     for (int i = (nlaps-1); i >= 0; i--) {
461       GPS_PLap lap = laps[i];
462       time_t delta = lap->start_time - wpt->GetCreationTime().toTime_t();
463       if ((delta >= -1) && (delta <= 1)) {
464         result = 1;
465         break;
466 
467         // as an optimization this will stop going through
468         // the lap array when the negative delta gets too
469         // big. It assumes that laps is sorted by time in
470         // ascending order (which appears to be the case for
471         // Forerunners. Don't know about other devices.
472       } else if (delta < -1) {
473         break;
474       }
475     }
476   }
477 
478   return result;
479 }
480 
481 static
482 void
track_read()483 track_read()
484 {
485   GPS_PTrack* array;
486   route_head* trk_head = nullptr;
487   int trk_num = 0;
488   const char* trk_name = "";
489   GPS_PLap* laps = nullptr;
490   int nlaps = 0;
491   int next_is_new_trkseg = 0;
492 
493   if (gps_lap_type != -1) {
494     nlaps = GPS_Command_Get_Lap(portname, &laps, &lap_read_nop_cb);
495   }
496 
497 
498   int32 ntracks = GPS_Command_Get_Track(portname, &array, waypt_read_cb);
499 
500   if (ntracks <= 0) {
501     return;
502   }
503 
504   for (int i = 0; i < ntracks; i++) {
505     /*
506      * This is probably always in slot zero, but the Garmin
507      * serial spec says these can appear anywhere.  Toss them
508      * out so we don't treat it as an extraneous trackpoint.
509      */
510     if (array[i]->ishdr) {
511       trk_name = array[i]->trk_ident;
512       if (!trk_name) {
513         trk_name = "";
514       }
515     }
516 
517     if (trk_head == nullptr || array[i]->ishdr) {
518       trk_head = new route_head;
519       trk_head->rte_num = trk_num;
520       trk_head->rte_name = QString::fromLatin1(trk_name);
521       trk_num++;
522       track_add_head(trk_head);
523     }
524 
525     /* Need to do this here because fitness devices set tnew
526      * on a trackpoint without lat/lon.
527      */
528     if (array[i]->tnew) {
529       next_is_new_trkseg = 1;
530     }
531 
532     if (array[i]->no_latlon || array[i]->ishdr) {
533       continue;
534     }
535     auto* wpt = new Waypoint;
536 
537     wpt->longitude = array[i]->lon;
538     wpt->latitude = array[i]->lat;
539     wpt->altitude = array[i]->alt;
540     wpt->heartrate = array[i]->heartrate;
541     wpt->cadence = array[i]->cadence;
542     wpt->shortname = array[i]->trk_ident;
543     wpt->SetCreationTime(array[i]->Time);
544     wpt->wpt_flags.is_split = checkWayPointIsAtSplit(wpt, laps,
545                               nlaps);
546     wpt->wpt_flags.new_trkseg = next_is_new_trkseg;
547     next_is_new_trkseg = 0;
548 
549     if (array[i]->dpth < 1.0e25f) {
550       WAYPT_SET(wpt, depth, array[i]->dpth);
551     }
552     if (array[i]->temperature_populated) {
553       WAYPT_SET(wpt, temperature, array[i]->temperature);
554     }
555 
556     track_add_wpt(trk_head, wpt);
557   }
558 
559   while (ntracks) {
560     GPS_Track_Del(&array[--ntracks]);
561   }
562   xfree(array);
563 }
564 
565 static
566 void
route_read()567 route_read()
568 {
569   GPS_PWay* array;
570   /* TODO: Fixes warning but is it right?
571    * RJL:  No, the warning isn't right; GCC's flow analysis is broken.
572    * still, it's good taste...
573    */
574   route_head* rte_head = nullptr;
575 
576   int32 nroutepts = GPS_Command_Get_Route(portname, &array);
577 
578 //	fprintf(stderr, "Routes %d\n", (int) nroutepts);
579 #if 1
580   for (int i = 0; i < nroutepts; i++) {
581     if (array[i]->isrte) {
582       char* csrc = nullptr;
583       /* What a horrible API has libjeeps for making this
584        * my problem.
585        */
586       switch (array[i]->rte_prot) {
587       case 201:
588         csrc = array[i]->rte_cmnt;
589         break;
590       case 202:
591         csrc = array[i]->rte_ident;
592         break;
593       default:
594         break;
595       }
596       rte_head = new route_head;
597       route_add_head(rte_head);
598       if (csrc) {
599         rte_head->rte_name = QString::fromLatin1(csrc);
600       }
601     } else {
602       if (array[i]->islink)  {
603         continue;
604       } else {
605         auto* wpt_tmp = new Waypoint;
606         wpt_tmp->latitude = array[i]->lat;
607         wpt_tmp->longitude = array[i]->lon;
608         wpt_tmp->shortname = array[i]->ident;
609         route_add_wpt(rte_head, wpt_tmp);
610       }
611     }
612   }
613 #else
614   GPS_Fmt_Print_Route(array, nroutepts, stderr);
615 #endif
616 
617 }
618 
619 #if 0
620 static
621 void
622 lap_read_as_track(void)
623 {
624   int32 ntracks;
625   GPS_PLap* array;
626   route_head* trk_head = NULL;
627   int trk_num = 0;
628   int index;
629   int i;
630   char tbuf[128];
631 
632   ntracks = GPS_Command_Get_Lap(portname, &array, waypt_read_cb);
633   if (ntracks <= 0) {
634     return;
635   }
636   for (i = 0; i < ntracks; i++) {
637     Waypoint* wpt;
638     if (array[i]->index == -1) {
639       index=i;
640     } else {
641       index=array[i]->index;
642       index=i;
643     }
644 
645     if ((trk_head == NULL) || (i == 0) ||
646         /* D906 - last track:index is the track index */
647         (array[i]->index == -1 && array[i]->track_index != 255) ||
648         /* D10xx - no real separator, use begin/end time to guess */
649         (abs(array[i-1]->start_time + array[i]->total_time/100-array[i]->start_time) > 2)
650        ) {
651       static struct tm* stmp;
652       stmp = gmtime(&array[i]->start_time);
653       trk_head = new route_head;
654       /*For D906, we would like to use the track_index in the last packet instead...*/
655       trk_head->rte_num = ++trk_num;
656       strftime(tbuf, 32, "%Y-%m-%dT%H:%M:%SZ", stmp);
657       trk_head->rte_name = tbuf;
658       track_add_head(trk_head);
659 
660       wpt = new Waypoint;
661 
662       wpt->longitude = array[i]->begin_lon;
663       wpt->latitude = array[i]->begin_lat;
664       wpt->heartrate = array[i]->avg_heart_rate;
665       wpt->cadence = array[i]->avg_cadence;
666       wpt->speed = array[i]->max_speed;
667       wpt->creation_time = array[i]->start_time;
668       wpt->microseconds = 0;
669 
670       sprintf(tbuf, "#%d-0", index);
671       wpt->shortname = tbuf;
672       sprintf(tbuf, "D:%f Cal:%d MS:%f AH:%d MH:%d AC:%d I:%d T:%d",
673               array[i]->total_distance, array[i]->calories, array[i]->max_speed, array[i]->avg_heart_rate,
674               array[i]->max_heart_rate, array[i]->avg_cadence, array[i]->intensity, array[i]->trigger_method);
675       wpt->description = tbuf;
676       track_add_wpt(trk_head, wpt);
677     }
678     /*Allow even if no correct location, no skip if invalid */
679     /*		if (array[i]->no_latlon) {
680     *			continue;
681     *		}
682     */
683     wpt = new Waypoint;
684 
685     wpt->longitude = array[i]->end_lon;
686     wpt->latitude = array[i]->end_lat;
687     wpt->heartrate = array[i]->avg_heart_rate;
688     wpt->cadence = array[i]->avg_cadence;
689     wpt->speed = array[i]->max_speed;
690     wpt->creation_time = array[i]->start_time + array[i]->total_time/100;
691     wpt->microseconds = 10000*(array[i]->total_time % 100);
692     /*Add fields with no mapping in the description */
693     sprintf(tbuf, "#%d", index);
694     wpt->shortname = tbuf;
695     sprintf(tbuf, "D:%f Cal:%d MS:%f AH:%d MH:%d AC:%d I:%d T:%d (%f,%f)",
696             array[i]->total_distance, array[i]->calories, array[i]->max_speed, array[i]->avg_heart_rate,
697             array[i]->max_heart_rate, array[i]->avg_cadence, array[i]->intensity, array[i]->trigger_method,
698             array[i]->begin_lon, array[i]->begin_lat);
699     wpt->description = tbuf;
700 
701     track_add_wpt(trk_head, wpt);
702   }
703   while (ntracks) {
704     GPS_Lap_Del(&array[--ntracks]);
705   }
706   xfree(array);
707 }
708 #endif
709 
710 /*
711  * Rather than propagate Garmin-specific data types outside of the Garmin
712  * code, we convert the PVT (position/velocity/time) data from the receiver
713  * to the data type we use throughout.   Yes, we do lose some data that way.
714  */
715 static void
pvt2wpt(GPS_PPvt_Data pvt,Waypoint * wpt)716 pvt2wpt(GPS_PPvt_Data pvt, Waypoint* wpt)
717 {
718   wpt->altitude = pvt->alt;
719   wpt->latitude = pvt->lat;
720   wpt->longitude = pvt->lon;
721   WAYPT_SET(wpt,course,1);
722   WAYPT_SET(wpt,speed,1);
723 
724   wpt->course = 180 + DEG(atan2(-pvt->east, -pvt->north));
725 
726   /* velocity in m/s */
727   WAYPT_SET(wpt,speed, sqrt(pvt->north*pvt->north + pvt->east*pvt->east));
728   // wpt->vs = pvt->up;
729 
730   /*
731    * The unit reports time in three fields:
732    * 1) The # of days to most recent Sun. since  1989-12-31 midnight UTC.
733    * 2) The number of seconds (fractions allowed) since that Sunday.
734    * 3) The number of leap seconds that offset the current UTC and GPS
735    *    reference clocks.
736    */
737   double wptime = 631065600.0 + pvt->wn_days * 86400.0  +
738     pvt->tow
739     - pvt->leap_scnds;
740   double wptimes = floor(wptime);
741   wpt->SetCreationTime(wptimes, 1000000.0 * (wptime - wptimes));
742 
743   /*
744    * The Garmin spec fifteen different models that use a different
745    * table for 'fix' without a really good way to tell if the model
746    * we're talking to happens to be one of those...By inspection,
747    * it looks like even though the models (Summit, Legend, etc.) may
748    * be popular, it's older (2001 and earlier or so) versions that
749    * are affected and I think there are relatively few readers of
750    * the fix field anyway.   Time will tell if this is a good plan.
751    */
752   switch (pvt->fix) {
753   case 0:
754     wpt->fix = fix_unknown;
755     break;
756   case 1:
757     wpt->fix = fix_none;
758     break;
759   case 2:
760     wpt->fix = fix_2d;
761     break;
762   case 3:
763     wpt->fix = fix_3d;
764     break;
765   case 4:
766     wpt->fix = fix_dgps;
767     break; /* 2D_diff */
768   case 5:
769     wpt->fix = fix_dgps;
770     break; /* 3D_diff */
771   default:
772     /* undocumented type. */
773     break;
774   }
775 }
776 
777 static gpsdevh* pvt_fd;
778 
779 static void
pvt_init(const QString & fname)780 pvt_init(const QString& fname)
781 {
782   rw_init(fname);
783   GPS_Command_Pvt_On(qPrintable(fname), &pvt_fd);
784 }
785 
786 static Waypoint*
pvt_read(posn_status * posn_status)787 pvt_read(posn_status* posn_status)
788 {
789   auto* wpt = new Waypoint;
790   GPS_PPvt_Data pvt = GPS_Pvt_New();
791 
792   if (GPS_Command_Pvt_Get(&pvt_fd, &pvt)) {
793     pvt2wpt(pvt, wpt);
794     GPS_Pvt_Del(&pvt);
795 
796     wpt->shortname = "Position";
797 
798     if (gps_errno && posn_status) {
799       posn_status->request_terminate = 1;
800     }
801 
802     return wpt;
803   }
804 
805   /*
806    * If the caller has not given us a better way to return the
807    * error, do it now.
808    */
809   if (gps_errno) {
810     fatal(MYNAME ": Fatal error reading position.\n");
811   }
812 
813   delete wpt;
814   GPS_Pvt_Del(&pvt);
815 
816   return nullptr;
817 }
818 
819 static void
data_read()820 data_read()
821 {
822   if (gpx_vec) {
823     gpx_vec->read();
824     return;
825   }
826 
827   if (poweroff) {
828     return;
829   }
830 
831   if (global_opts.masked_objective & WPTDATAMASK) {
832     waypt_read();
833   }
834   if (global_opts.masked_objective & TRKDATAMASK) {
835     track_read();
836   }
837   if (global_opts.masked_objective & RTEDATAMASK) {
838     route_read();
839   }
840   if (!(global_opts.masked_objective &
841         (WPTDATAMASK | TRKDATAMASK | RTEDATAMASK | POSNDATAMASK))) {
842     fatal(MYNAME ": Nothing to do.\n");
843   }
844 }
845 
846 static GPS_PWay
sane_GPS_Way_New()847 sane_GPS_Way_New()
848 {
849   GPS_PWay way = GPS_Way_New();
850   if (!way) {
851     fatal(MYNAME ":not enough memory\n");
852   }
853 
854   /*
855    *  Undo less than helpful defaults from Way_New.
856    */
857   way->rte_ident[0] = 0;
858   way->rte_cmnt[0] = 0;
859   way->rte_link_subclass[0] = 0;
860   way->rte_link_ident[0] = 0;
861   way->city[0] = 0;
862   way->state[0] = 0;
863   way->facility[0] = 0;
864   way->addr[0] = 0;
865   way->cross_road[0] = 0;
866   way->dpth = 1.0e25f;
867   way->wpt_class = 0;  // user waypoint by default.
868 
869   return way;
870 }
871 
872 static int
waypt_write_cb(GPS_PWay *)873 waypt_write_cb(GPS_PWay*)
874 {
875   static int i;
876   int n = waypt_count();
877 
878   if (global_opts.verbose_status) {
879     i++;
880     waypt_status_disp(n, i);
881   }
882   return 0;
883 }
884 
885 /*
886  * If we're using smart names, try to put the cache info in the
887  * description.
888  */
889 static const char*
get_gc_info(const Waypoint * wpt)890 get_gc_info(const Waypoint* wpt)
891 {
892   if (global_opts.smart_names) {
893     if (wpt->gc_data->type == gt_virtual) {
894       return  "V ";
895     }
896     if (wpt->gc_data->type == gt_unknown) {
897       return  "? ";
898     }
899     if (wpt->gc_data->type == gt_multi) {
900       return  "Mlt ";
901     }
902     if (wpt->gc_data->type == gt_earth) {
903       return  "EC ";
904     }
905     if (wpt->gc_data->type == gt_event) {
906       return  "Ev ";
907     }
908     if (wpt->gc_data->container == gc_micro) {
909       return  "M ";
910     }
911     if (wpt->gc_data->container == gc_small) {
912       return  "S ";
913     }
914   }
915   return "";
916 }
917 
918 static int
waypoint_prepare()919 waypoint_prepare()
920 {
921   int i;
922   int n = waypt_count();
923   extern WaypointList* global_waypoint_list;
924   int icon;
925 
926   tx_waylist = (struct GPS_SWay**) xcalloc(n,sizeof(*tx_waylist));
927 
928   for (i = 0; i < n; i++) {
929     tx_waylist[i] = sane_GPS_Way_New();
930   }
931 
932   i = 0;
933 
934   // Iterate with waypt_disp_all?
935   for (const Waypoint* wpt : qAsConst(*global_waypoint_list)) {
936     char obuf[256];
937 
938     QString src;
939     if (!wpt->description.isEmpty()) {
940       src = wpt->description;
941     }
942     if (!wpt->notes.isEmpty()) {
943       src = wpt->notes;
944     }
945 
946     /*
947      * mkshort will do collision detection and namespace
948      * cleaning
949      */
950     char* ident = mkshort(mkshort_handle,
951                            global_opts.synthesize_shortnames ? CSTRc(src) :
952                              CSTRc(wpt->shortname), false);
953     /* Should not be a strcpy as 'ident' isn't really a C string,
954      * but rather a garmin "fixed length" buffer that's padded
955      * to the end with spaces.  So this is NOT (strlen+1).
956      */
957     memcpy(tx_waylist[i]->ident, ident, strlen(ident));
958 
959     if (global_opts.synthesize_shortnames) {
960       xfree(ident);
961     }
962     tx_waylist[i]->ident[sizeof(tx_waylist[i]->ident)-1] = 0;
963 
964     // If we were explicitly given a comment from GPX, use that.
965     //  This logic really is horrible and needs to be untangled.
966     if (!wpt->description.isEmpty() &&
967         global_opts.smart_names && !wpt->gc_data->diff) {
968       memcpy(tx_waylist[i]->cmnt, CSTRc(wpt->description), strlen(CSTRc(wpt->description)));
969     } else {
970       if (global_opts.smart_names &&
971           wpt->gc_data->diff && wpt->gc_data->terr) {
972         snprintf(obuf, sizeof(obuf), "%s%u/%u %s",
973                  get_gc_info(wpt),
974                  wpt->gc_data->diff, wpt->gc_data->terr,
975                  CSTRc(src));
976         memcpy(tx_waylist[i]->cmnt, obuf, strlen(obuf));
977       } else  {
978         memcpy(tx_waylist[i]->cmnt, CSTRc(src), strlen(CSTRc(src)));
979       }
980     }
981 
982 
983 
984     tx_waylist[i]->lon = wpt->longitude;
985     tx_waylist[i]->lat = wpt->latitude;
986 
987     if (deficon) {
988       icon = gt_find_icon_number_from_desc(deficon, PCX);
989     } else {
990       if (get_cache_icon(wpt)) {
991         icon = gt_find_icon_number_from_desc(get_cache_icon(wpt), PCX);
992       } else {
993         icon = gt_find_icon_number_from_desc(wpt->icon_descr, PCX);
994       }
995     }
996 
997     /* For units that support tiny numbers of waypoints, just
998      * overwrite that and go very literal.
999      */
1000     if (gps_waypt_type == 103) {
1001       icon = d103_icon_number_from_symbol(wpt->icon_descr);
1002     }
1003     tx_waylist[i]->smbl = icon;
1004     if (wpt->altitude == unknown_alt) {
1005       tx_waylist[i]->alt_is_unknown = 1;
1006       tx_waylist[i]->alt = 0;
1007     } else {
1008       tx_waylist[i]->alt = wpt->altitude;
1009     }
1010     gpsbabel::DateTime t = wpt->GetCreationTime();
1011     if (t.isValid()) {
1012       tx_waylist[i]->time = t.toTime_t();
1013       tx_waylist[i]->time_populated = 1;
1014     }
1015     if (category) {
1016       tx_waylist[i]->category = 1 << (atoi(category) - 1);
1017     }
1018     if (categorybits) {
1019       tx_waylist[i]->category = categorybits;
1020     }
1021     garmin_fs_garmin_before_write(wpt, tx_waylist[i], gps_waypt_type);
1022     i++;
1023   }
1024 
1025   return n;
1026 }
1027 
1028 static void
waypoint_write()1029 waypoint_write()
1030 {
1031   int32 ret;
1032 
1033   int n = waypoint_prepare();
1034 
1035   if ((ret = GPS_Command_Send_Waypoint(portname, tx_waylist, n, waypt_write_cb)) < 0) {
1036     fatal(MYNAME ":communication error sending waypoints..\n");
1037   }
1038 
1039   for (int i = 0; i < n; ++i) {
1040     GPS_Way_Del(&tx_waylist[i]);
1041   }
1042   if (global_opts.verbose_status) {
1043     fprintf(stdout, "\r\n");
1044     fflush(stdout);
1045   }
1046   xfree(tx_waylist);
1047 }
1048 
1049 static void
route_hdr_pr(const route_head * rte)1050 route_hdr_pr(const route_head* rte)
1051 {
1052   (*cur_tx_routelist_entry)->rte_num = rte->rte_num;
1053   (*cur_tx_routelist_entry)->isrte = 1;
1054   if (!rte->rte_name.isEmpty()) {
1055     strncpy((*cur_tx_routelist_entry)->rte_ident, CSTRc(rte->rte_name),
1056             sizeof((*cur_tx_routelist_entry)->rte_ident) - 1);
1057     (*cur_tx_routelist_entry)->rte_ident[sizeof((*cur_tx_routelist_entry)->rte_ident) - 1] = 0;
1058   }
1059 }
1060 
1061 static void
route_waypt_pr(const Waypoint * wpt)1062 route_waypt_pr(const Waypoint* wpt)
1063 {
1064   GPS_PWay rte = *cur_tx_routelist_entry;
1065 
1066   /*
1067    * As stupid as this is, libjeeps seems to want an empty
1068    * waypoint between every link in a route that has nothing
1069    * but the 'islink' member set.   Rather than "fixing" libjeeps,
1070    * we just double them up (sigh) and do that here.
1071    */
1072   rte->islink = 1;
1073   rte->lon = wpt->longitude;
1074   rte->lat = wpt->latitude;
1075   cur_tx_routelist_entry++;
1076   rte = *cur_tx_routelist_entry;
1077 
1078   rte->lon = wpt->longitude;
1079   rte->lat = wpt->latitude;
1080   rte->smbl = gt_find_icon_number_from_desc(wpt->icon_descr, PCX);
1081 
1082   // map class so unit doesn't duplicate routepoints as a waypoint.
1083   rte->wpt_class = 0x80;
1084 
1085   if (wpt->altitude != unknown_alt) {
1086     rte->alt = wpt->altitude;
1087   } else {
1088     rte->alt_is_unknown = 1;
1089     rte->alt = 0;
1090   }
1091 
1092   char* d = rte->ident;
1093   for (auto idx : wpt->shortname) {
1094     int c = idx.toLatin1();
1095     if (receiver_must_upper && isalpha(c)) {
1096       c = toupper(c);
1097     }
1098     if (strchr(valid_waypt_chars, c)) {
1099       *d++ = c;
1100     }
1101   }
1102 
1103   rte->ident[sizeof(rte->ident)-1] = 0;
1104   if (wpt->description.isEmpty()) {
1105     rte->cmnt[0] = 0;
1106   } else {
1107     strncpy(rte->cmnt, CSTR(wpt->description), sizeof(rte->cmnt) - 1);
1108     rte->cmnt[sizeof(rte->cmnt)-1] = 0;
1109   }
1110   cur_tx_routelist_entry++;
1111 }
1112 
1113 static void
route_write()1114 route_write()
1115 {
1116   int n = 2 * route_waypt_count(); /* Doubled for the islink crap. */
1117 
1118   tx_routelist = (struct GPS_SWay**) xcalloc(n,sizeof(GPS_PWay));
1119   cur_tx_routelist_entry = tx_routelist;
1120 
1121   for (int i = 0; i < n; i++) {
1122     tx_routelist[i] = sane_GPS_Way_New();
1123   }
1124 
1125   route_disp_all(route_hdr_pr, nullptr, route_waypt_pr);
1126   GPS_Command_Send_Route(portname, tx_routelist, n);
1127 }
1128 
1129 static void
track_hdr_pr(const route_head * trk_head)1130 track_hdr_pr(const route_head* trk_head)
1131 {
1132   (*cur_tx_tracklist_entry)->ishdr = true;
1133   if (!trk_head->rte_name.isEmpty()) {
1134     strncpy((*cur_tx_tracklist_entry)->trk_ident, CSTRc(trk_head->rte_name), sizeof((*cur_tx_tracklist_entry)->trk_ident) - 1);
1135     (*cur_tx_tracklist_entry)->trk_ident[sizeof((*cur_tx_tracklist_entry)->trk_ident)-1] = 0;
1136   } else {
1137     sprintf((*cur_tx_tracklist_entry)->trk_ident, "TRACK%02d", my_track_count);
1138   }
1139   cur_tx_tracklist_entry++;
1140   my_track_count++;
1141 }
1142 
1143 static void
track_waypt_pr(const Waypoint * wpt)1144 track_waypt_pr(const Waypoint* wpt)
1145 {
1146   (*cur_tx_tracklist_entry)->lat = wpt->latitude;
1147   (*cur_tx_tracklist_entry)->lon = wpt->longitude;
1148   (*cur_tx_tracklist_entry)->alt = (wpt->altitude != unknown_alt) ? wpt->altitude : 1e25;
1149   (*cur_tx_tracklist_entry)->Time = wpt->GetCreationTime().toTime_t();
1150   if (!wpt->shortname.isEmpty()) {
1151     strncpy((*cur_tx_tracklist_entry)->trk_ident, CSTRc(wpt->shortname), sizeof((*cur_tx_tracklist_entry)->trk_ident) - 1);
1152     (*cur_tx_tracklist_entry)->trk_ident[sizeof((*cur_tx_tracklist_entry)->trk_ident)-1] = 0;
1153   }
1154   (*cur_tx_tracklist_entry)->tnew = wpt->wpt_flags.new_trkseg;
1155   cur_tx_tracklist_entry++;
1156 }
1157 
1158 static int
track_prepare()1159 track_prepare()
1160 {
1161   int32 n = track_waypt_count() + track_count();
1162 
1163   tx_tracklist = (struct GPS_STrack**) xcalloc(n, sizeof(GPS_PTrack));
1164   cur_tx_tracklist_entry = tx_tracklist;
1165   for (int i = 0; i < n; i++) {
1166     tx_tracklist[i] = GPS_Track_New();
1167   }
1168   my_track_count = 0;
1169   track_disp_all(track_hdr_pr, nullptr, track_waypt_pr);
1170 
1171   GPS_Prepare_Track_For_Device(&tx_tracklist, &n);
1172 
1173   return n;
1174 }
1175 
1176 static void
track_write()1177 track_write()
1178 {
1179   int n = track_prepare();
1180   GPS_Command_Send_Track(portname, tx_tracklist, n, (eraset)? 1 : 0);
1181 
1182   for (int i = 0; i < n; i++) {
1183     GPS_Track_Del(&tx_tracklist[i]);
1184   }
1185   xfree(tx_tracklist);
1186 }
1187 
1188 static void
course_write()1189 course_write()
1190 {
1191   int i;
1192 
1193   int n_wpt = waypoint_prepare();
1194   int n_trk = track_prepare();
1195 
1196   GPS_Command_Send_Track_As_Course(portname, tx_tracklist, n_trk,
1197                                    tx_waylist, n_wpt, (eraset)? 1 : 0);
1198 
1199   for (i = 0; i < n_wpt; ++i) {
1200     GPS_Way_Del(&tx_waylist[i]);
1201   }
1202   xfree(tx_waylist);
1203 
1204   for (i = 0; i < n_trk; i++) {
1205     GPS_Track_Del(&tx_tracklist[i]);
1206   }
1207   xfree(tx_tracklist);
1208 }
1209 
1210 static void
data_write()1211 data_write()
1212 {
1213   if (poweroff) {
1214     return;
1215   }
1216 
1217   /* If we have both trackpoints and waypoints and the device
1218    * supports courses, combine them to a course. Otherwise,
1219    * send tracks & waypoints separately.
1220    */
1221   if ((global_opts.masked_objective & WPTDATAMASK) &&
1222       (global_opts.masked_objective & TRKDATAMASK) &&
1223       gps_course_transfer != -1) {
1224     course_write();
1225   } else {
1226     if (global_opts.masked_objective & WPTDATAMASK) {
1227       waypoint_write();
1228     }
1229     if (global_opts.masked_objective & TRKDATAMASK) {
1230       track_write();
1231     }
1232   }
1233   if (global_opts.masked_objective & RTEDATAMASK) {
1234     route_write();
1235   }
1236 }
1237 
1238 
1239 ff_vecs_t garmin_vecs = {
1240   ff_type_serial,
1241   FF_CAP_RW_ALL,
1242   rd_init,
1243   rw_init,
1244   rw_deinit,
1245   rw_deinit,
1246   data_read,
1247   data_write,
1248   nullptr,
1249   &garmin_args,
1250   CET_CHARSET_ASCII, 0,
1251   { pvt_init, pvt_read, rw_deinit, nullptr, nullptr, nullptr },
1252   nullptr
1253 };
1254 
1255 static const char* d103_icons[16] = {
1256   "dot",
1257   "house",
1258   "gas",
1259   "car",
1260   "fish",
1261   "boat",
1262   "anchor",
1263   "wreck",
1264   "exit",
1265   "skull",
1266   "flag",
1267   "camp",
1268   "circle_x",
1269   "deer",
1270   "1st_aid",
1271   "back-track"
1272 };
1273 
1274 static const char*
d103_symbol_from_icon_number(unsigned int n)1275 d103_symbol_from_icon_number(unsigned int n)
1276 {
1277   if (n  <= 15) {
1278     return d103_icons[n];
1279   } else {
1280     return "unknown";
1281   }
1282 }
1283 
1284 static int
d103_icon_number_from_symbol(const QString & s)1285 d103_icon_number_from_symbol(const QString& s)
1286 {
1287   if (s.isNull()) {
1288     return 0;
1289   }
1290 
1291   for (unsigned int i = 0; i < sizeof(d103_icons) / sizeof(d103_icons[0]); i++) {
1292     if (0 == (s.compare(d103_icons[i], Qt::CaseInsensitive))) {
1293       return i;
1294     }
1295   }
1296   return 0;
1297 }
1298