1 /*
2 Communicate Thales/Magellan serial protocol.
3
4 Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007,
5 2008, 2010 Robert Lipe, robertlipe+source@gpsbabel.org
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
21 */
22
23 #include <cassert> // for assert
24 #include <cctype> // for isprint, toupper
25 #include <cmath> // for fabs, lround
26 #include <cstdio> // for sscanf, size_t
27 #include <cstdlib> // for atoi, atof, strtoul
28 #include <cstring> // for strchr, strncmp, strlen, memmove, strrchr, memset
29 #include <ctime> // for gmtime
30
31 #include <QtCore/QByteArray> // for QByteArray
32 #include <QtCore/QDateTime> // for QDateTime
33 #include <QtCore/QDir> // for QDir, operator|, QDir::Files, QDir::Name, QDir::Readable
34 #include <QtCore/QFileInfo> // for QFileInfo
35 #include <QtCore/QFileInfoList> // for QFileInfoList
36 #include <QtCore/QLatin1String> // for QLatin1String
37 #include <QtCore/QList> // for QList
38 #include <QtCore/QScopedPointer> // for QScopedPointer
39 #include <QtCore/QString> // for QString, operator==
40 #include <QtCore/QStringList> // for QStringList
41 #include <QtCore/QTime> // for QTime
42 #include <QtCore/QVector> // for QVector
43 #include <QtCore/Qt> // for CaseInsensitive
44 #include <QtCore/QtGlobal> // for qPrintable, foreach
45
46 #include "defs.h"
47 #include "explorist_ini.h" // for explorist_ini_done, explorist_ini_get, mag_info
48 #include "format.h" // for Format
49 #include "gbfile.h" // for gbfclose, gbfeof, gbfgets, gbfopen, gbfwrite, gbfile
50 #include "gbser.h" // for gbser_deinit, gbser_init, gbser_is_serial, gbser_read_line, gbser_set_port, gbser_write, gbser_OK
51 #include "magellan.h" // for mm_meridian, mm_sportrak, magellan_icon_mapping_t, mm_gps315320, mm_unknown, mm_map330, mm_map410, pid_to_model_t, mm_gps310, m330_cleanse, mag_checksum, mag_find_descr_from_token, mag_find_token_from_descr, mag_rteparse, mag_trkparse
52 #include "src/core/datetime.h" // for DateTime
53 #include "vecs.h" // for Vecs
54
55
56 static int bitrate = 4800;
57 static int wptcmtcnt;
58 static int wptcmtcnt_max;
59 static int explorist;
60 static int broken_sportrak;
61 #define MYNAME "MAGPROTO"
62 #define MAXCMTCT 200
63
64 #define debug_serial (global_opts.debug_level > 1)
65
66 static QString termread(char* ibuf, int size);
67 static void termwrite(const char* obuf, int size);
68 static void mag_readmsg(gpsdata_type objective);
69 static void mag_handon();
70 static void mag_handoff();
71 static short_handle mkshort_handle = nullptr;
72 static char* deficon = nullptr;
73 static char* bs = nullptr;
74 static char* cmts = nullptr;
75 static char* noack = nullptr;
76 static char* nukewpt = nullptr;
77 static int route_out_count;
78 static int waypoint_read_count;
79 static int wpt_len = 8;
80 static QString curfname;
81 static int extension_hint;
82 // For Explorist GC/510/610/710 families, bludgeon in GPX support.
83 // (This has nothing to do with the Explorist 100...600 products.)
84 static Format* gpx_vec;
85 static mag_info* explorist_info;
86 static QStringList os_gpx_files(const char* dirname);
87
88 /*
89 * Magellan's firmware is *horribly* slow to send the next packet after
90 * we turn around an ack while we are reading from the device. It's
91 * quite spiffy when we're writing to the device. Since we're *way*
92 * less likely to lose data while reading from it than it is to lose data
93 * when we write to it, we turn off the acks when we are predominantly
94 * reading.
95 */
96 static int suppress_ack;
97
98 enum mag_rxstate {
99 mrs_handoff = 0,
100 mrs_handon,
101 mrs_awaiting_ack
102 };
103
104 /*
105 * An individual element of a route.
106 */
107 struct mag_rte_elem {
108 QString wpt_name;
109 QString wpt_icon;
110 };
111
112 /*
113 * A header of a route. Related elements of a route belong to this.
114 */
115 struct mag_rte_head_t {
116 QList<mag_rte_elem*> elem_list; /* list of child rte_elems */
117 char* rte_name{nullptr};
118 int nelems{0};
119 };
120
121 static QList<Waypoint*> rte_wpt_tmp; /* temporary PGMNWPL msgs for routes */
122
123 static gbfile* magfile_h;
124 static mag_rxstate magrxstate;
125 static int mag_error;
126 static unsigned int last_rx_csum;
127 static int found_done;
128 static int got_version;
129 static int is_file = 0;
130 static route_head* trk_head;
131 static int ignore_unable;
132
133 static Waypoint* mag_wptparse(char*);
134 using cleanse_fn = QString (const char*);
135 static cleanse_fn* mag_cleanse;
136 static const char** os_get_magellan_mountpoints();
137
138 static const magellan_icon_mapping_t gps315_icon_table[] = {
139 { "a", "filled circle" },
140 { "b", "box" },
141 { "c", "red buoy" },
142 { "d", "green buoy" },
143 { "e", "buoy" },
144 { "f", "rocks" },
145 { "g", "red daymark" },
146 { "h", "green daymark" },
147 { "i", "bell" },
148 { "j", "danger" },
149 { "k", "diver down" },
150 { "l", "fish" },
151 { "m", "house" },
152 { "n", "mark" },
153 { "o", "car" },
154 { "p", "tent" },
155 { "q", "boat" },
156 { "r", "food" },
157 { "s", "fuel" },
158 { "t", "tree" },
159 { nullptr, nullptr }
160 };
161
162 static const magellan_icon_mapping_t map330_icon_table[] = {
163 { "a", "crossed square" },
164 { "b", "box" },
165 { "c", "house" },
166 { "d", "aerial" },
167 { "e", "airport" },
168 { "f", "amusement park" },
169 { "g", "ATM" },
170 { "g", "Bank" },
171 { "h", "auto repair" },
172 { "i", "boating" },
173 { "j", "camping" },
174 { "k", "exit ramp" },
175 { "l", "first aid" },
176 { "m", "nav aid" },
177 { "n", "buoy" },
178 { "o", "fuel" },
179 { "p", "garden" },
180 { "q", "golf" },
181 { "r", "hotel" },
182 { "s", "hunting/fishing" },
183 { "t", "large city" },
184 { "u", "lighthouse" },
185 { "v", "major city" },
186 { "w", "marina" },
187 { "x", "medium city" },
188 { "y", "museum" },
189 { "z", "obstruction" },
190 { "aa", "park" },
191 { "ab", "resort" },
192 { "ac", "restaurant" },
193 { "ad", "rock" },
194 { "ae", "scuba" },
195 { "af", "RV service" },
196 { "ag", "shooting" },
197 { "ah", "sight seeing" },
198 { "ai", "small city" },
199 { "aj", "sounding" },
200 { "ak", "sports arena" },
201 { "al", "tourist info" },
202 { "am", "truck service" },
203 { "an", "winery" },
204 { "ao", "wreck" },
205 { "ap", "zoo" },
206 { "ah", "Virtual cache"}, /* Binos: because you "see" them. */
207 { "ak", "Micro-Cache" }, /* Looks like a film canister. */
208 { "an", "Multi-Cache"}, /* Winery: grapes 'coz they "bunch" */
209 { "s", "Unknown Cache"}, /* 'Surprise' cache: use a target. */
210 { "ac", "Event Cache"}, /* Event caches. May be food. */
211 { nullptr, nullptr }
212 };
213
214 pid_to_model_t pid_to_model[] = {
215 { mm_gps315320, 19, "ColorTrak" },
216 { mm_gps315320, 24, "GPS 315/320" },
217 { mm_map410, 25, "Map 410" },
218 { mm_map330, 30, "Map 330" },
219 { mm_gps310, 31, "GPS 310" },
220 { mm_meridian, 33, "Meridian" },
221 { mm_meridian, 35, "ProMark 2" },
222 { mm_sportrak, 36, "SporTrak Map/Pro" },
223 { mm_sportrak, 37, "SporTrak" },
224 { mm_meridian, 38, "FX324 Plotter" },
225 { mm_meridian, 39, "Meridian Color" },
226 { mm_meridian, 40, "FX324C Plotter" },
227 { mm_sportrak, 41, "Sportrak Color" },
228 { mm_sportrak, 42, "Sportrak Marine" },
229 { mm_meridian, 43, "Meridian Marine" },
230 { mm_sportrak, 44, "Sportrak Topo" },
231 { mm_sportrak, 45, "Mystic" },
232 { mm_meridian, 46, "MobileMapper" },
233 { mm_meridian, 110, "Explorist 100" },
234 { mm_meridian, 111, "Explorist 200" },
235 { mm_unknown, 0, nullptr }
236 };
237
238 static const magellan_icon_mapping_t* icon_mapping = map330_icon_table;
239
240 /*
241 * For each receiver type, return a "cleansed" version of the string
242 * that's valid for a waypoint name or comment. The string should be
243 * freed when you're done with it.
244 */
245 static QString
m315_cleanse(const char * istring)246 m315_cleanse(const char* istring)
247 {
248 char* rstring = (char*) xmalloc(strlen(istring)+1);
249 char* o;
250 const char* i;
251 static char m315_valid_chars[] =
252 "ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789";
253 for (o=rstring,i=istring; *i; i++) {
254 if (strchr(m315_valid_chars, toupper(*i))) {
255 *o++ = toupper(*i);
256 }
257 }
258 *o = 0;
259 QString rv(rstring);
260 xfree(rstring);
261 return rv;
262 }
263
264 /*
265 * Do same for 330, Meridian, and SportTrak.
266 */
267 QString
m330_cleanse(const char * istring)268 m330_cleanse(const char* istring)
269 {
270 static char m330_valid_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ "
271 "abcdefghijklmnopqrstuvwxyz"
272 "0123456789+-.'/!@#<%^&>()=:\\";
273 char* rstring = (char*) xmalloc(strlen(istring)+1);
274 char* o;
275 const char* i;
276
277 for (o=rstring,i=istring; *i; i++) {
278 if (strchr(m330_valid_chars, *i)) {
279 *o++ = *i;
280 }
281 }
282 *o = 0;
283 QString rv(rstring);
284 xfree(rstring);
285 return rv;
286 }
287
288 /*
289 * Given a protocol message, compute the checksum as needed by
290 * the Magellan protocol.
291 */
292 unsigned int
mag_checksum(const char * const buf)293 mag_checksum(const char* const buf)
294 {
295 int csum = 0;
296
297 for (const char* p = buf; *p; p++) {
298 csum ^= *p;
299 }
300
301 return csum;
302 }
303 static unsigned int
mag_pchecksum(const char * const buf,int len)304 mag_pchecksum(const char* const buf, int len)
305 {
306 int csum = 0;
307 const char* p = buf;
308 for (; len ; len--) {
309 csum ^= *p++;
310 }
311 return csum;
312 }
313
314 static void
mag_writemsg(const char * const buf)315 mag_writemsg(const char* const buf)
316 {
317 unsigned int osum = mag_checksum(buf);
318 int retry_cnt = 5;
319 QScopedPointer<char, QScopedPointerPodDeleter> obuf;
320
321 if (debug_serial) {
322 warning("WRITE: $%s*%02X\r\n",buf, osum);
323 }
324
325 retry:
326
327 int i = xasprintf(obuf, "$%s*%02X\r\n",buf, osum);
328 termwrite(obuf.data(), i);
329 if (magrxstate == mrs_handon || magrxstate == mrs_awaiting_ack) {
330 magrxstate = mrs_awaiting_ack;
331 mag_readmsg(trkdata);
332 if (last_rx_csum != osum) {
333 if (debug_serial) {
334 warning("COMM ERROR: Expected %02x, got %02x",
335 osum, last_rx_csum);
336 }
337 if (retry_cnt--) {
338 goto retry;
339 } else {
340 mag_handoff();
341 fatal(MYNAME
342 ": Too many communication errors.\n");
343 }
344 }
345 }
346 }
347
348 static void
mag_writeack(int osum)349 mag_writeack(int osum)
350 {
351 QScopedPointer<char, QScopedPointerPodDeleter> nbuf;
352 QScopedPointer<char, QScopedPointerPodDeleter> obuf;
353
354 if (is_file) {
355 return;
356 }
357
358 (void) xasprintf(nbuf, "PMGNCSM,%02X", osum);
359 unsigned int nsum = mag_checksum(nbuf.data());
360 int i = xasprintf(obuf, "$%s*%02X\r\n",nbuf.data(), nsum);
361
362 if (debug_serial) {
363 warning("ACK WRITE: %s",obuf.data());
364 }
365 /*
366 * Don't call mag_writemsg here so we don't get into ack feedback
367 * loops.
368 */
369 termwrite(obuf.data(), i);
370 }
371
372 static void
mag_handon()373 mag_handon()
374 {
375 if (!is_file) {
376 mag_writemsg("PMGNCMD,HANDON");
377 }
378 magrxstate = mrs_handon;
379
380 }
381
382 static void
mag_handoff()383 mag_handoff()
384 {
385 if (!is_file) {
386 mag_writemsg("PMGNCMD,HANDOFF");
387 }
388 magrxstate = mrs_handoff;
389 }
390
391 static void
mag_verparse(char * ibuf)392 mag_verparse(char* ibuf)
393 {
394 int prodid = mm_unknown;
395 char version[1024];
396 pid_to_model_t* pp = pid_to_model;
397
398 got_version = 1;
399 sscanf(ibuf,"$PMGNVER,%d,%[^,]", &prodid, version);
400
401 for (pp = pid_to_model; pp->model != mm_unknown; pp++) {
402 if (pp->pid == prodid) {
403 break;
404 }
405 }
406
407 if (prodid == 37) {
408 broken_sportrak = 1;
409 }
410
411 switch (pp->model) {
412 case mm_gps315320:
413 case mm_map410:
414 icon_mapping = gps315_icon_table;
415 setshort_length(mkshort_handle, 6);
416 setshort_mustupper(mkshort_handle, 1);
417 mag_cleanse = m315_cleanse;
418 break;
419 case mm_map330:
420 case mm_meridian:
421 case mm_sportrak:
422 icon_mapping = map330_icon_table;
423 setshort_length(mkshort_handle, wpt_len);
424 setshort_mustupper(mkshort_handle, 0);
425 mag_cleanse = m330_cleanse;
426 break;
427 default:
428 fatal(MYNAME ": Unknown receiver type %d, model version '%s'.\n", prodid, version);
429 }
430 }
431
432 #define IS_TKN(x) (strncmp(ibuf,x, sizeof(x)-1) == 0)
433
434 static void
mag_readmsg(gpsdata_type objective)435 mag_readmsg(gpsdata_type objective)
436 {
437 char ibuf[512]; /* oliskoli: corrupted data (I've seen descr with a lot
438 of escaped FFFFFFFF) may need more size */
439 int retrycnt = 20;
440
441 retry:
442 QString gr = termread(ibuf, sizeof(ibuf));
443
444 if (gr.isEmpty()) {
445 if (!got_version) {
446 /*
447 * The 315 can take up to six seconds to respond to
448 * a VERSION command. Since this is on startup,
449 * we'll be fairly persistent in retrying.
450 */
451 if (retrycnt--) {
452 goto retry;
453 } else {
454 fatal(MYNAME ": No data received from GPS.\n");
455 }
456 } else {
457 if (is_file) {
458 found_done = 1;
459 }
460 return;
461 }
462 }
463
464 /* If column zero isn't a dollar sign, it's not for us */
465 if (ibuf[0] != '$') {
466 fatal(MYNAME ": line doesn't start with '$'.\n");
467 }
468
469
470 int isz = strlen(ibuf);
471
472 if (isz < 5) {
473 if (debug_serial) {
474 warning("SHORT READ %d\n", isz);
475 }
476 return;
477 }
478 mag_error = 0;
479 while (!isprint(ibuf[isz])) {
480 isz--;
481 }
482 char* isump = &ibuf[isz-1];
483 unsigned int isum = strtoul(isump, nullptr,16);
484 if (isum != mag_pchecksum(&ibuf[1], isz-3)) {
485 if (debug_serial) {
486 warning("RXERR %02x/%02x: '%s'\n", isum, mag_pchecksum(&ibuf[1],isz-5), ibuf);
487 }
488 /* Special case receive errors early on. */
489 if (!got_version) {
490 fatal(MYNAME ": bad communication. Check bit rate.\n");
491 }
492 }
493 if (debug_serial) {
494 warning("READ: %s\n", ibuf);
495 }
496 if (IS_TKN("$PMGNCSM,")) {
497 last_rx_csum = strtoul(&ibuf[9], nullptr, 16);
498 magrxstate = mrs_handon;
499 return;
500 }
501 if (strncmp(ibuf, "$PMGNWPL,", 7) == 0) {
502 Waypoint* wpt = mag_wptparse(ibuf);
503 waypoint_read_count++;
504 if (global_opts.verbose_status) {
505 waypt_status_disp(waypoint_read_count,
506 waypoint_read_count);
507 }
508
509 if (extension_hint) {
510 if (extension_hint == WPTDATAMASK) {
511 waypt_add(wpt);
512 } else if (extension_hint == RTEDATAMASK) {
513 rte_wpt_tmp.append(wpt);
514 }
515 } else {
516 switch (objective) {
517 case wptdata:
518 waypt_add(wpt);
519 break;
520 case rtedata:
521 rte_wpt_tmp.append(wpt);
522 break;
523 default:
524 break;
525 }
526 }
527 }
528 if (strncmp(ibuf, "$PMGNTRK,", 7) == 0) {
529 Waypoint* wpt = mag_trkparse(ibuf);
530 /*
531 * Allow lazy allocation of track head.
532 */
533 if (trk_head == nullptr) {
534 /* These tracks don't have names, so derive one
535 * from input filename.
536 */
537
538 trk_head = new route_head;
539
540 /* Whack trailing extension if present. */
541 QString s = get_filename(curfname);
542 int idx = s.indexOf('.');
543 if (idx > 0) {
544 s.truncate(idx);
545 }
546
547 trk_head->rte_name = s;
548 track_add_head(trk_head);
549 }
550
551 track_add_wpt(trk_head, wpt);
552 }
553 if (strncmp(ibuf, "$PMGNRTE,", 7) == 0) {
554 mag_rteparse(ibuf);
555 }
556 if (IS_TKN("$PMGNVER,")) {
557 mag_verparse(ibuf);
558 }
559 mag_error = 0;
560 if (!ignore_unable && IS_TKN("$PMGNCMD,UNABLE")) {
561 warning("Unable to send\n");
562 found_done = 1;
563 mag_error = 1;
564 ignore_unable = 0;
565 return;
566 }
567 if (IS_TKN("$PMGNCMD,END") || (is_file && (gbfeof(magfile_h)))) {
568 found_done = 1;
569 return;
570 }
571
572 if (magrxstate != mrs_handoff) {
573 mag_writeack(isum);
574 }
575 }
576
577 static void* serial_handle = nullptr;
578
579 static int
terminit(const QString & portname,int create_ok)580 terminit(const QString& portname, int create_ok)
581 {
582 if (gbser_is_serial(qPrintable(portname))) {
583 if (serial_handle = gbser_init(qPrintable(portname)), nullptr != serial_handle) {
584 int rc;
585 if (rc = gbser_set_port(serial_handle, bitrate, 8, 0, 1), gbser_OK != rc) {
586 fatal(MYNAME ": Can't configure port\n");
587 }
588 }
589 is_file = 0;
590 if (serial_handle == nullptr) {
591 fatal(MYNAME ": Could not open serial port %s\n", qPrintable(portname));
592 }
593 return 1;
594 } else {
595 /* Does this check for an error? */
596 magfile_h = gbfopen(portname, create_ok ? "w+b" : "rb", MYNAME);
597 is_file = 1;
598 icon_mapping = map330_icon_table;
599 mag_cleanse = m330_cleanse;
600 got_version = 1;
601 return 0;
602 }
603 }
604
termread(char * ibuf,int size)605 static QString termread(char* ibuf, int size)
606 {
607 if (is_file) {
608 return gbfgets(ibuf, size, magfile_h);
609 } else {
610 int rc = gbser_read_line(serial_handle, ibuf, size, 2000, 0x0a, 0x0d);
611 if (rc != gbser_OK) {
612 fatal(MYNAME ": Read error\n");
613 }
614 return ibuf;
615 }
616 }
617
618 /* Though not documented in the protocol spec, if the unit itself
619 * wants to create a field containing a comma, it will encode it
620 * as <escape>2C. We extrapolate that any 2 digit hex encoding may
621 * be valid. We don't do this in termread() since we need to do it
622 * after the scanf. This means we have to do it field-by-field
623 * basis.
624 *
625 * The buffer is modified in place and shortened by copying the remaining
626 * string including the terminator.
627 */
628 static
629 void
mag_dequote(char * ibuf)630 mag_dequote(char* ibuf)
631 {
632 char* esc = nullptr;
633
634 while ((esc = strchr(ibuf, 0x1b))) {
635 int nremains = strlen(esc);
636 if (nremains >= 3) {
637 static const char hex[17] = "0123456789ABCDEF";
638 const char* c1 = strchr(hex, esc[1]);
639 const char* c2 = strchr(hex, esc[2]);
640 if (c1 && c2) {
641 int escv = (c1 - hex) * 16 + (c2 - hex);
642 if (escv == 255) { /* corrupted data */
643 char* tmp = esc + 1;
644 while (*tmp == 'F') {
645 tmp++;
646 }
647 memmove(esc, tmp, strlen(tmp) + 1);
648 } else {
649 *esc++ = (isprint(escv)) ? escv : '$';
650 /* buffers overlap */
651 memmove(esc, esc+2, nremains - 2);
652 }
653 }
654 } else {
655 *esc = '\0'; /* trim corrupted data,
656 otherwise we get an endless loop */
657 }
658 }
659 }
660
661 static void
termwrite(const char * obuf,int size)662 termwrite(const char* obuf, int size)
663 {
664 if (is_file) {
665 size_t nw;
666 if (nw = gbfwrite(obuf, 1, size, magfile_h), nw < (size_t) size) {
667 fatal(MYNAME ": Write error");
668 }
669 } else {
670 int rc;
671 if (rc = gbser_write(serial_handle, obuf, size), rc < 0) {
672 fatal(MYNAME ": Write error");
673 }
674 }
675 }
676
termdeinit()677 static void termdeinit()
678 {
679 if (is_file) {
680 gbfclose(magfile_h);
681 magfile_h = nullptr;
682 } else {
683 gbser_deinit(serial_handle);
684 serial_handle = nullptr;
685 }
686 }
687
688 /*
689 * Arg tables are doubled up so that -? can output appropriate help
690 */
691 static
692 QVector<arglist_t> mag_sargs = {
693 {
694 "deficon", &deficon, "Default icon name", nullptr, ARGTYPE_STRING,
695 ARG_NOMINMAX, nullptr
696 },
697 {
698 "maxcmts", &cmts, "Max number of comments to write (maxcmts=200)",
699 "200", ARGTYPE_INT, ARG_NOMINMAX, nullptr
700 },
701 {
702 "baud", &bs, "Numeric value of bitrate (baud=4800)", "4800",
703 ARGTYPE_INT, ARG_NOMINMAX, nullptr
704 },
705 {
706 "noack", &noack, "Suppress use of handshaking in name of speed",
707 nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
708 },
709 {
710 "nukewpt", &nukewpt, "Delete all waypoints", nullptr, ARGTYPE_BOOL,
711 ARG_NOMINMAX, nullptr
712 },
713 };
714
715 static
716 QVector<arglist_t> mag_fargs = {
717 {
718 "deficon", &deficon, "Default icon name", nullptr, ARGTYPE_STRING,
719 ARG_NOMINMAX, nullptr
720 },
721 {
722 "maxcmts", &cmts, "Max number of comments to write (maxcmts=200)",
723 nullptr, ARGTYPE_INT, ARG_NOMINMAX, nullptr
724 },
725 };
726
727 /*
728 * The part of the serial init that's common to read and write.
729 */
730 static void
mag_serial_init_common(const QString & portname)731 mag_serial_init_common(const QString& portname)
732 {
733 if (is_file) {
734 return;
735 }
736
737 mag_handoff();
738 if (!noack && !suppress_ack) {
739 mag_handon();
740 }
741
742 time_t now = current_time().toTime_t();
743 /*
744 * The 315 can take up to 4.25 seconds to respond to initialization
745 * commands. Time out on the side of caution.
746 */
747 time_t later = now + 6;
748 got_version = 0;
749 mag_writemsg("PMGNCMD,VERSION");
750
751 while (!got_version) {
752 mag_readmsg(trkdata);
753 if (current_time().toTime_t() > later) {
754 fatal(MYNAME ": No acknowledgment from GPS on %s\n",
755 qPrintable(portname));
756 }
757 }
758
759 if ((icon_mapping != gps315_icon_table)) {
760 /*
761 * The 315 can't handle this command, so we set a global
762 * to ignore the NAK on it.
763 */
764 ignore_unable = 1;
765 mag_writemsg("PMGNCMD,NMEAOFF");
766 ignore_unable = 0;
767 }
768
769 if (nukewpt) {
770 /* The unit will send us an "end" message upon completion */
771 mag_writemsg("PMGNCMD,DELETE,WAYPOINT");
772 mag_readmsg(trkdata);
773 if (!found_done) {
774 fatal(MYNAME ": Unexpected response to waypoint delete command.\n");
775 }
776 found_done = 0;
777 }
778
779 }
780 static void
mag_rd_init_common(const QString & portname)781 mag_rd_init_common(const QString& portname)
782 {
783 waypoint_read_count = 0;
784 // For Explorist GC, intercept the device access and redirect to GPX.
785 // We actually do the rd_init() inside read as we may have multiple
786 // files that we have to read.
787 if (portname == "usb:") {
788 const char** dlist = os_get_magellan_mountpoints();
789 explorist_info = explorist_ini_get(dlist);
790 if (explorist_info) {
791 gpx_vec = Vecs::Instance().find_vec("gpx");
792 }
793 return;
794 }
795
796 if (bs) {
797 bitrate=atoi(bs);
798 }
799
800 if (!mkshort_handle) {
801 mkshort_handle = mkshort_new_handle();
802 }
803
804 terminit(portname, 0);
805 mag_serial_init_common(portname);
806
807 rte_wpt_tmp.clear();
808
809 /* find the location of the tail of the path name,
810 * make a copy of it, then lop off the file extension
811 */
812
813 curfname = get_filename(portname);
814
815 /*
816 * I'd rather not derive behaviour from filenames but since
817 * we can't otherwise tell if we should put a WPT on the route
818 * queue or the WPT queue in the presence of (-w -r -t) we
819 * divine a hint from the filename extension when we can.
820 */
821 QString exten = QFileInfo(curfname).suffix();
822 if (exten.length() > 0) {
823 if (0 == exten.compare(QLatin1String("upt"), Qt::CaseInsensitive)) {
824 extension_hint = WPTDATAMASK;
825 } else if (0 == exten.compare(QLatin1String("log"), Qt::CaseInsensitive)) {
826 extension_hint = TRKDATAMASK;
827 } else if (0 == exten.compare(QLatin1String("rte"), Qt::CaseInsensitive)) {
828 extension_hint = RTEDATAMASK;
829 }
830 }
831
832 }
833
834 static void
mag_rd_init(const QString & portname)835 mag_rd_init(const QString& portname)
836 {
837 explorist = 0;
838 suppress_ack = 1;
839 mag_rd_init_common(portname);
840 }
841
842 static void
magX_rd_init(const QString & portname)843 magX_rd_init(const QString& portname)
844 {
845 explorist = 1;
846 mag_rd_init_common(portname);
847 }
848
849 static void
mag_wr_init_common(const QString & portname)850 mag_wr_init_common(const QString& portname)
851 {
852 suppress_ack = 0;
853 if (bs) {
854 bitrate=atoi(bs);
855 }
856
857 if (waypt_count() > 500) {
858 fatal(MYNAME ": Meridian/Explorist does not support more than 500 waypoints in one file. Only\n200 waypoints may have comments.\nDecrease the number of waypoints sent.\n");
859 }
860
861 if (cmts) {
862 wptcmtcnt_max = atoi(cmts);
863 } else {
864 wptcmtcnt_max = MAXCMTCT ;
865 }
866
867 if (!mkshort_handle) {
868 mkshort_handle = mkshort_new_handle();
869 }
870
871 terminit(portname, 1);
872 mag_serial_init_common(portname);
873
874 rte_wpt_tmp.clear();
875 }
876
877 /*
878 * Entry point for extended (explorist) points.
879 */
880 static void
magX_wr_init(const QString & portname)881 magX_wr_init(const QString& portname)
882 {
883 wpt_len = 20;
884 explorist = 1;
885 mag_wr_init_common(portname);
886 setshort_length(mkshort_handle, wpt_len);
887 setshort_whitespace_ok(mkshort_handle, 1);
888 }
889
890 static void
mag_wr_init(const QString & portname)891 mag_wr_init(const QString& portname)
892 {
893 explorist = 0;
894 wpt_len = 8;
895 mag_wr_init_common(portname);
896 /*
897 * Whitespace is actually legal, but since waypoint name length is
898 * only 8 bytes, we'll conserve them.
899 */
900
901 setshort_whitespace_ok(mkshort_handle, 0);
902 }
903
904 static void
mag_deinit()905 mag_deinit()
906 {
907 if (explorist_info) {
908 explorist_ini_done(explorist_info);
909 return;
910 }
911 mag_handoff();
912 termdeinit();
913 if (mkshort_handle) {
914 mkshort_del_handle(&mkshort_handle);
915 }
916
917 while (!rte_wpt_tmp.isEmpty()) {
918 delete rte_wpt_tmp.takeFirst();
919 }
920
921 trk_head = nullptr;
922
923 curfname.clear();
924 }
925
926 static void
mag_wr_deinit()927 mag_wr_deinit()
928 {
929 if (explorist) {
930 mag_writemsg("PMGNCMD,END");
931 }
932 mag_deinit();
933 }
934
935 /*
936 * I'm tired of arguing with scanf about optional fields . Detokenize
937 * an incoming string that may contain empty fields.
938 *
939 * Probably should be cleaned up and moved to common code, but
940 * making it deal with an arbitrary number of fields of arbitrary
941 * size is icky. We don't have to solve the general case here...
942 */
943
944 static char ifield[20][100];
945 static
parse_istring(char * istring)946 void parse_istring(char* istring)
947 {
948 int f = 0;
949 int n;
950 while (istring[0]) {
951 char* fp = ifield[f];
952 int x = sscanf(istring, "%[^,]%n", fp, &n);
953 f++;
954 if (x) {
955 istring += n;
956 /* IF more in this string, skip delim */
957 if (istring[0]) {
958 istring++;
959 }
960 } else {
961 istring ++;
962 }
963 }
964 }
965
966 /*
967 * Given an incoming track messages of the form:
968 * $PMGNTRK,3605.259,N,08644.389,W,00151,M,201444.61,A,,020302*66
969 * create and return a populated waypoint.
970 */
971 Waypoint*
mag_trkparse(char * trkmsg)972 mag_trkparse(char* trkmsg)
973 {
974 int hms;
975 int fracsecs;
976 struct tm tm;
977
978 auto* waypt = new Waypoint;
979
980 memset(&tm, 0, sizeof(tm));
981
982 /*
983 * As some of the fields are optional, sscanf works badly
984 * for us.
985 */
986 parse_istring(trkmsg);
987 double latdeg = atof(ifield[1]);
988 char latdir = ifield[2][0];
989 double lngdeg = atof(ifield[3]);
990 char lngdir = ifield[4][0];
991 int alt = atof(ifield[5]);
992 char altunits = ifield[6][0];
993 (void)altunits;
994 sscanf(ifield[7], "%d.%d", &hms, &fracsecs);
995 /* Field 8 is constant */
996 /* Field nine is optional track name */
997 int dmy = atoi(ifield[10]);
998
999 tm.tm_sec = hms % 100;
1000 hms = hms / 100;
1001 tm.tm_min = hms % 100;
1002 hms = hms / 100;
1003 tm.tm_hour = hms % 100;
1004
1005 tm.tm_year = 100 + dmy % 100;
1006 dmy = dmy / 100;
1007 tm.tm_mon = dmy % 100 - 1;
1008 dmy = dmy / 100;
1009 tm.tm_mday = dmy % 100;
1010
1011 waypt->SetCreationTime(mkgmtime(&tm), 10.0 * fracsecs);
1012
1013 if (latdir == 'S') {
1014 latdeg = -latdeg;
1015 }
1016 waypt->latitude = ddmm2degrees(latdeg);
1017
1018 if (lngdir == 'W') {
1019 lngdeg = -lngdeg;
1020 }
1021 waypt->longitude = ddmm2degrees(lngdeg);
1022
1023 waypt->altitude = alt;
1024
1025 return waypt;
1026
1027 }
1028
1029 /*
1030 * Given an incoming route messages of the form:
1031 * $PMGNRTE,4,1,c,1,DAD,a,Anna,a*61
1032 * generate a route.
1033 */
1034 void
mag_rteparse(char * rtemsg)1035 mag_rteparse(char* rtemsg)
1036 {
1037 int n;
1038 int frags,frag,rtenum;
1039 char xbuf[100],next_stop[100],abuf[100];
1040 char* currtemsg;
1041 static mag_rte_head_t* mag_rte_head;
1042 char* p;
1043
1044 #if 0
1045 sscanf(rtemsg,"$PMGNRTE,%d,%d,%c,%d%n",
1046 &frags,&frag,xbuf,&rtenum,&n);
1047 #else
1048 sscanf(rtemsg,"$PMGNRTE,%d,%d,%c,%d%n",
1049 &frags,&frag,xbuf,&rtenum,&n);
1050
1051 /* Explorist has a route name here */
1052 QString rte_name;
1053 if (explorist) {
1054 char* ca = rtemsg + n;
1055 is_fatal(*ca++ != ',', MYNAME ": Incorrectly formatted route line '%s'", rtemsg);
1056
1057 char* ce = strchr(ca, ',');
1058 is_fatal(ce == nullptr, MYNAME ": Incorrectly formatted route line '%s'", rtemsg);
1059
1060 if (ca == ce) {
1061 rte_name = "Route";
1062 rte_name += QString::number(rtenum);
1063 } else {
1064 rte_name = ca;
1065 rte_name.truncate(ce-ca);
1066 }
1067
1068 n += ((ce - ca) + 1);
1069 }
1070
1071 #endif
1072
1073 /*
1074 * This is the first component of a route. Allocate a new
1075 * head.
1076 */
1077 if (frag == 1) {
1078 mag_rte_head = new mag_rte_head_t;
1079 mag_rte_head->nelems = frags;
1080 }
1081
1082 currtemsg = rtemsg + n;
1083
1084 /*
1085 * The individual line may contain several route elements.
1086 * loop and pick those up.
1087 */
1088 while (sscanf(currtemsg,",%[^,],%[^,]%n",next_stop, abuf,&n)) {
1089 if ((next_stop[0] == 0) || (next_stop[0] == '*')) {
1090 break;
1091 }
1092
1093 /* trim CRC from waypoint icon string */
1094 if ((p = strchr(abuf, '*')) != nullptr) {
1095 *p = '\0';
1096 }
1097
1098 auto* rte_elem = new mag_rte_elem;
1099
1100 rte_elem->wpt_name = next_stop;
1101 rte_elem->wpt_icon = abuf;
1102
1103 mag_rte_head->elem_list.append(rte_elem);
1104
1105 /* Sportrak (the non-mapping unit) creates malformed
1106 * RTE sentence with no icon info after the routepoint
1107 * name. So if we saw an "icon" treat that as new
1108 * routepoint.
1109 */
1110 if (broken_sportrak && abuf[0]) {
1111 rte_elem = new mag_rte_elem;
1112 rte_elem->wpt_name = abuf;
1113
1114 mag_rte_head->elem_list.append(rte_elem);
1115 }
1116
1117 next_stop[0] = 0;
1118 currtemsg += n;
1119 }
1120
1121 /*
1122 * If this was the last fragment of the route, add it to the
1123 * gpsbabel internal structs now.
1124 */
1125 if (frag == mag_rte_head->nelems) {
1126
1127 auto* rte_head = new route_head;
1128 route_add_head(rte_head);
1129 rte_head->rte_num = rtenum;
1130 rte_head->rte_name = rte_name;
1131
1132 /*
1133 * It is quite feasible that we have 200 waypoints,
1134 * 3 of which are used in the route. We'll need to find
1135 * those in the queue for SD routes...
1136 */
1137
1138 while (!mag_rte_head->elem_list.isEmpty()) {
1139 mag_rte_elem* re = mag_rte_head->elem_list.takeFirst();
1140
1141 /*
1142 * Copy route points from temp wpt queue.
1143 */
1144 foreach (const Waypoint* waypt, rte_wpt_tmp) {
1145 if (waypt->shortname == re->wpt_name) {
1146 auto* wpt = new Waypoint(*waypt);
1147 route_add_wpt(rte_head, wpt);
1148 break;
1149 }
1150 }
1151
1152 delete re;
1153 }
1154 delete mag_rte_head;
1155 }
1156 }
1157
1158 QString
mag_find_descr_from_token(const char * token)1159 mag_find_descr_from_token(const char* token)
1160 {
1161 if (icon_mapping == nullptr) {
1162 return "unknown";
1163 }
1164
1165 for (const magellan_icon_mapping_t* i = icon_mapping; i->token; i++) {
1166 if (token[0] == 0) {
1167 break;
1168 }
1169 if (case_ignore_strcmp(token, i->token) == 0) {
1170 return i->icon;
1171 }
1172 }
1173 return icon_mapping[0].icon;
1174 }
1175
1176 QString
mag_find_token_from_descr(const QString & icon)1177 mag_find_token_from_descr(const QString& icon)
1178 {
1179 const magellan_icon_mapping_t* i = icon_mapping;
1180
1181 if (i == nullptr || icon == nullptr) {
1182 return "a";
1183 }
1184
1185 for (i = icon_mapping; i->token; i++) {
1186 if (icon.compare(i->icon, Qt::CaseInsensitive) == 0) {
1187 return i->token;
1188 }
1189 }
1190 return icon_mapping[0].token;
1191 }
1192
1193 /*
1194 * Given an incoming waypoint messages of the form:
1195 * $PMGNWPL,3549.499,N,08650.827,W,0000257,M,HOME,HOME,c*4D
1196 * create and return a populated waypoint.
1197 */
1198 static Waypoint*
mag_wptparse(char * trkmsg)1199 mag_wptparse(char* trkmsg)
1200 {
1201 double latdeg, lngdeg;
1202 char latdir;
1203 char lngdir;
1204 int alt;
1205 char altunits;
1206 char shortname[100];
1207 char descr[256];
1208 char icon_token[100];
1209 int i = 0;
1210
1211 descr[0] = 0;
1212 icon_token[0] = 0;
1213
1214 auto* waypt = new Waypoint;
1215
1216 sscanf(trkmsg,"$PMGNWPL,%lf,%c,%lf,%c,%d,%c,%[^,],%[^,]",
1217 &latdeg,&latdir,
1218 &lngdeg,&lngdir,
1219 &alt,&altunits,shortname,descr);
1220 char* icone = strrchr(trkmsg, '*');
1221 char* icons = strrchr(trkmsg, ',')+1;
1222
1223 mag_dequote(descr);
1224
1225 for (char* blah = icons ; blah < icone; blah++) {
1226 icon_token[i++] = *blah;
1227 }
1228 icon_token[i++] = '\0';
1229
1230 if (latdir == 'S') {
1231 latdeg = -latdeg;
1232 }
1233 waypt->latitude = ddmm2degrees(latdeg);
1234
1235 if (lngdir == 'W') {
1236 lngdeg = -lngdeg;
1237 }
1238 waypt->longitude = ddmm2degrees(lngdeg);
1239
1240 waypt->altitude = alt;
1241 waypt->shortname = shortname;
1242 waypt->description = descr;
1243 waypt->icon_descr = mag_find_descr_from_token(icon_token);
1244
1245 return waypt;
1246 }
1247
1248 static void
mag_read()1249 mag_read()
1250 {
1251 if (gpx_vec) {
1252 QStringList f = os_gpx_files(explorist_info->track_path);
1253 for (const auto& file : qAsConst(f)) {
1254 gpx_vec->rd_init(file);
1255 gpx_vec->read();
1256 gpx_vec->rd_deinit();
1257 }
1258
1259 f = os_gpx_files(explorist_info->waypoint_path);
1260 for (const auto& file : qAsConst(f)) {
1261 gpx_vec->rd_init(file);
1262 gpx_vec->read();
1263 gpx_vec->rd_deinit();
1264 }
1265 #if 0
1266 f = os_gpx_files(explorist_info->geo_path);
1267 for (const auto& file : qAsConst(f)) {
1268 gpx_vec->rd_init(file);
1269 gpx_vec->read();
1270 gpx_vec->rd_deinit();
1271 }
1272 #endif
1273 return;
1274 }
1275
1276 found_done = 0;
1277 if (global_opts.masked_objective & TRKDATAMASK) {
1278 magrxstate = mrs_handoff;
1279 if (!is_file) {
1280 mag_writemsg("PMGNCMD,TRACK,2");
1281 }
1282
1283 while (!found_done) {
1284 mag_readmsg(trkdata);
1285 }
1286 }
1287
1288 found_done = 0;
1289 if (global_opts.masked_objective & WPTDATAMASK) {
1290 magrxstate = mrs_handoff;
1291 if (!is_file) {
1292 mag_writemsg("PMGNCMD,WAYPOINT");
1293 }
1294
1295 while (!found_done) {
1296 mag_readmsg(wptdata);
1297 }
1298 }
1299
1300 found_done = 0;
1301 if (global_opts.masked_objective & RTEDATAMASK) {
1302 magrxstate = mrs_handoff;
1303 if (!is_file) {
1304 /*
1305 * serial routes require waypoint & routes
1306 * messages commands.
1307 */
1308 mag_writemsg("PMGNCMD,WAYPOINT");
1309
1310 while (!found_done) {
1311 mag_readmsg(rtedata);
1312 }
1313
1314 mag_writemsg("PMGNCMD,ROUTE");
1315
1316 found_done = 0;
1317 while (!found_done) {
1318 mag_readmsg(rtedata);
1319 }
1320 } else {
1321 /*
1322 * SD routes are a stream of PMGNWPL and
1323 * PMGNRTE messages, in that order.
1324 */
1325 while (!found_done) {
1326 mag_readmsg(rtedata);
1327 }
1328 }
1329 }
1330 }
1331
1332 static
1333 void
mag_waypt_pr(const Waypoint * waypointp)1334 mag_waypt_pr(const Waypoint* waypointp)
1335 {
1336 QScopedPointer<char, QScopedPointerPodDeleter> obuf;
1337 QScopedPointer<char, QScopedPointerPodDeleter> ofmtdesc;
1338 QString icon_token;
1339
1340 double ilat = waypointp->latitude;
1341 double ilon = waypointp->longitude;
1342
1343 double lon = fabs(ilon);
1344 double lat = fabs(ilat);
1345
1346 int lon_deg = lon;
1347 int lat_deg = lat;
1348
1349 lon = (lon - lon_deg) * 60.0;
1350 lat = (lat - lat_deg) * 60.0;
1351
1352 lon = (lon_deg * 100.0 + lon);
1353 lat = (lat_deg * 100.0 + lat);
1354
1355 if (deficon) {
1356 icon_token = mag_find_token_from_descr(deficon);
1357 } else {
1358 icon_token = mag_find_token_from_descr(waypointp->icon_descr);
1359 }
1360
1361 if (get_cache_icon(waypointp)) {
1362 icon_token = mag_find_token_from_descr(get_cache_icon(waypointp));
1363 }
1364
1365 QString isrc = waypointp->notes.isEmpty() ? waypointp->description : waypointp->notes;
1366 QString owpt = global_opts.synthesize_shortnames ?
1367 mkshort_from_wpt(mkshort_handle, waypointp) : waypointp->shortname;
1368 QString odesc = isrc;
1369 owpt = mag_cleanse(CSTRc(owpt));
1370
1371 if (global_opts.smart_icons &&
1372 waypointp->gc_data->diff && waypointp->gc_data->terr) {
1373 // It's a string and compactness counts, so "1.0" is OK to be "10".
1374 xasprintf(ofmtdesc, "%ud/%ud %s", waypointp->gc_data->diff,
1375 waypointp->gc_data->terr, CSTRc(odesc));
1376 odesc = mag_cleanse(ofmtdesc.data());
1377 } else {
1378 odesc = mag_cleanse(CSTRc(odesc));
1379 }
1380
1381 /*
1382 * For the benefit of DirectRoute (which uses waypoint comments
1383 * to deliver turn-by-turn popups for street routing) allow a
1384 * cap on the comments delivered so we leave space for it to route.
1385 */
1386 if (!odesc.isEmpty() && (wptcmtcnt++ >= wptcmtcnt_max)) {
1387 odesc.clear();
1388 }
1389
1390 xasprintf(obuf, "PMGNWPL,%4.3f,%c,%09.3f,%c,%07.0f,M,%-.*s,%-.46s,%s",
1391 lat, ilat < 0 ? 'S' : 'N',
1392 lon, ilon < 0 ? 'W' : 'E',
1393 waypointp->altitude == unknown_alt ?
1394 0 : waypointp->altitude,
1395 wpt_len,
1396 CSTRc(owpt),
1397 CSTRc(odesc),
1398 CSTR(icon_token));
1399 mag_writemsg(obuf.data());
1400
1401 if (!is_file) {
1402 if (mag_error) {
1403 warning("Protocol error Writing '%s'\n", obuf.data());
1404 }
1405 }
1406 }
1407
1408 static
mag_track_disp(const Waypoint * waypointp)1409 void mag_track_disp(const Waypoint* waypointp)
1410 {
1411 QScopedPointer<char, QScopedPointerPodDeleter> obuf;
1412
1413 double ilat = waypointp->latitude;
1414 double ilon = waypointp->longitude;
1415
1416 QByteArray dmy("");
1417 QByteArray hms("");
1418 if (waypointp->creation_time.isValid()) {
1419 // Round to hundredths of seconds before conversion to string.
1420 // Rounding can ripple all the way from the msec to the year.
1421 QDateTime dt = waypointp->GetCreationTime().toUTC();
1422 dt = dt.addMSecs(10 * lround(dt.time().msec()/10.0) - dt.time().msec());
1423 assert((dt.time().msec() % 10) == 0);
1424 dmy = dt.toString("ddMMyy").toUtf8();
1425 hms = dt.toString("hhmmss.zzz").left(9).toUtf8();
1426 }
1427
1428 double lon = fabs(ilon);
1429 double lat = fabs(ilat);
1430
1431 int lon_deg = lon;
1432 int lat_deg = lat;
1433
1434 lon = (lon - lon_deg) * 60.0;
1435 lat = (lat - lat_deg) * 60.0;
1436
1437 lon = (lon_deg * 100.0 + lon);
1438 lat = (lat_deg * 100.0 + lat);
1439
1440 xasprintf(obuf,"PMGNTRK,%4.3f,%c,%09.3f,%c,%05.0f,%c,%s,A,,%s",
1441 lat, ilat < 0 ? 'S' : 'N',
1442 lon, ilon < 0 ? 'W' : 'E',
1443 waypointp->altitude == unknown_alt ?
1444 0 : waypointp->altitude,
1445 'M', hms.constData(), dmy.constData());
1446 mag_writemsg(obuf.data());
1447 }
1448
1449 static
mag_track_pr()1450 void mag_track_pr()
1451 {
1452 track_disp_all(nullptr, nullptr, mag_track_disp);
1453 }
1454
1455 /*
1456 The spec says to stack points:
1457 $PMGNRTE,2,1,c,1,FOO,POINT1,b,POINT2,c,POINT3,d*6C<CR><LF>
1458
1459 Meridian SD card and serial (at least) writes in pairs:
1460 $PMGNRTE,4,1,c,1,HOME,c,I49X73,a*15
1461 ...
1462 $PMGNRTE,4,4,c,1,RON273,a,MYCF93,a*7B
1463
1464 The spec also says that some units don't like single-legged pairs,
1465 and to replace the 2nd name with "<<>>", but I haven't seen one of those.
1466 */
1467
1468 static void
mag_route_trl(const route_head * rte)1469 mag_route_trl(const route_head* rte)
1470 {
1471 QScopedPointer<char, QScopedPointerPodDeleter> obuff;
1472 QString buff1;
1473 QString buff2;
1474 QString* pbuff;
1475 QString icon_token;
1476
1477 /* count waypoints for this route */
1478 int i = rte->rte_waypt_ct;
1479
1480 /* number of output PMGNRTE messages at 2 points per line */
1481 int numlines = (i / 2) + (i % 2);
1482
1483 /* increment the route counter. */
1484 route_out_count++;
1485
1486 int thisline = i = 0;
1487 foreach (const Waypoint* waypointp, rte->waypoint_list) {
1488 i++;
1489
1490 if (deficon) {
1491 icon_token = mag_find_token_from_descr(deficon);
1492 } else {
1493 icon_token = mag_find_token_from_descr(waypointp->icon_descr);
1494 }
1495
1496 if (i == 1) {
1497 pbuff = &buff1;
1498 } else {
1499 pbuff = &buff2;
1500 }
1501 // Write name, icon tuple into alternating buff1/buff2 buffer.
1502 *pbuff = waypointp->shortname + ',' + icon_token;
1503
1504 if ((waypointp == rte->waypoint_list.back()) || ((i % 2) == 0)) {
1505 QString expbuf;
1506 thisline++;
1507 if (explorist) {
1508 expbuf = rte->rte_name + ',';
1509 }
1510
1511 xasprintf(obuff, "PMGNRTE,%d,%d,c,%d,%s%s,%s",
1512 numlines, thisline,
1513 rte->rte_num ? rte->rte_num : route_out_count,
1514 CSTRc(expbuf),
1515 CSTR(buff1), CSTR(buff2));
1516
1517 mag_writemsg(obuff.data());
1518 buff1.clear();
1519 buff2.clear();
1520 i = 0;
1521 }
1522 }
1523 }
1524
1525 static void
mag_route_pr()1526 mag_route_pr()
1527 {
1528 route_out_count = 0;
1529 route_disp_all(nullptr, mag_route_trl, mag_waypt_pr);
1530
1531 }
1532
1533 static void
mag_write()1534 mag_write()
1535 {
1536
1537 wptcmtcnt = 0;
1538
1539 switch (global_opts.objective) {
1540 case trkdata:
1541 mag_track_pr();
1542 break;
1543 case wptdata:
1544 waypt_disp_all(mag_waypt_pr);
1545 break;
1546 case rtedata:
1547 mag_route_pr();
1548 break;
1549 default:
1550 fatal(MYNAME ": Unknown objective.\n");
1551 }
1552 }
1553
os_get_magellan_mountpoints()1554 const char** os_get_magellan_mountpoints()
1555 {
1556 #if __APPLE__
1557 const char** dlist = (const char**) xcalloc(2, sizeof *dlist);
1558 dlist[0] = xstrdup("/Volumes/Magellan");
1559 dlist[1] = nullptr;
1560 return dlist;
1561 #else
1562 fatal("Not implemented");
1563 return nullptr;
1564 #endif
1565 }
1566
1567 static QStringList
os_gpx_files(const char * dirname)1568 os_gpx_files(const char* dirname)
1569 {
1570 QDir dir(dirname);
1571
1572 const QFileInfoList filist = dir.entryInfoList(QStringList("*.gpx"), QDir::Files | QDir::Readable, QDir::Name);
1573 QStringList rv;
1574 for (const auto& fi : filist) {
1575 rv.append(fi.absoluteFilePath());
1576 }
1577 return rv;
1578 }
1579
1580 /*
1581 * This is repeated just so it shows up as separate menu options
1582 * for the benefit of GUI wrappers.
1583 */
1584 ff_vecs_t mag_svecs = {
1585 ff_type_serial,
1586 FF_CAP_RW_ALL,
1587 mag_rd_init,
1588 mag_wr_init,
1589 mag_deinit,
1590 mag_deinit,
1591 mag_read,
1592 mag_write,
1593 nullptr,
1594 &mag_sargs,
1595 CET_CHARSET_ASCII, 0, /* CET-REVIEW */
1596 NULL_POS_OPS,
1597 nullptr,
1598 };
1599
1600 ff_vecs_t mag_fvecs = {
1601 ff_type_file,
1602 FF_CAP_RW_ALL,
1603 mag_rd_init,
1604 mag_wr_init,
1605 mag_deinit,
1606 mag_deinit,
1607 mag_read,
1608 mag_write,
1609 nullptr,
1610 &mag_fargs,
1611 CET_CHARSET_ASCII, 0, /* CET-REVIEW */
1612 NULL_POS_OPS,
1613 nullptr,
1614 };
1615
1616 /*
1617 * Extended (Explorist) entry tables.
1618 */
1619 ff_vecs_t magX_fvecs = {
1620 ff_type_file,
1621 FF_CAP_RW_ALL,
1622 magX_rd_init,
1623 magX_wr_init,
1624 mag_deinit,
1625 mag_wr_deinit,
1626 mag_read,
1627 mag_write,
1628 nullptr,
1629 &mag_fargs,
1630 CET_CHARSET_ASCII, 0, /* CET-REVIEW */
1631 NULL_POS_OPS,
1632 nullptr,
1633 };
1634