You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

magproto.cc 37KB

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