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