driver_garmin_txt.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. /*
  2. * Handle the Garmin simple text format supported by some Garmins.
  3. * Tested with the 'Garmin eTrex Legend' device working in 'Text Out' mode.
  4. *
  5. * Protocol info from:
  6. * http://www8.garmin.com/support/text_out.html
  7. * http://www.garmin.com/support/commProtocol.html
  8. *
  9. * Code by: Petr Slansky <slansky@usa.net>
  10. * all rights abandoned, a thank would be nice if you use this code.
  11. *
  12. * -D 3 = packet trace
  13. * -D 4 = packet details
  14. * -D 5 = more packet details
  15. * -D 6 = very excessive details
  16. *
  17. * limitations:
  18. * very simple protocol, only very basic information
  19. * TODO
  20. * do not have from garmin:
  21. * pdop
  22. * vdop
  23. * magnetic variation
  24. * satellite information
  25. *
  26. * This file is Copyright 2010 by the GPSD project
  27. * SPDX-License-Identifier: BSD-2-clause
  28. *
  29. */
  30. /***************************************************
  31. Garmin Simple Text Output Format:
  32. The simple text (ASCII) output contains time, position, and velocity data in
  33. the fixed width fields (not delimited) defined in the following table:
  34. FIELD DESCRIPTION: WIDTH: NOTES:
  35. ----------------------- ------- ------------------------
  36. Sentence start 1 Always '@'
  37. ----------------------- ------- ------------------------
  38. /Year 2 Last two digits of UTC year
  39. | ----------------------- ------- ------------------------
  40. | Month 2 UTC month, "01".."12"
  41. T | ----------------------- ------- ------------------------
  42. i | Day 2 UTC day of month, "01".."31"
  43. m | ----------------------- ------- ------------------------
  44. e | Hour 2 UTC hour, "00".."23"
  45. | ----------------------- ------- ------------------------
  46. | Minute 2 UTC minute, "00".."59"
  47. | ----------------------- ------- ------------------------
  48. \Second 2 UTC second, "00".."59"
  49. ----------------------- ------- ------------------------
  50. /Latitude hemisphere 1 'N' or 'S'
  51. | ----------------------- ------- ------------------------
  52. | Latitude position 7 WGS84 ddmmmmm, with an implied
  53. | decimal after the 4th digit
  54. | ----------------------- ------- ------------------------
  55. | Longitude hemisphere 1 'E' or 'W'
  56. | ----------------------- ------- ------------------------
  57. | Longitude position 8 WGS84 dddmmmmm with an implied
  58. P | decimal after the 5th digit
  59. o | ----------------------- ------- ------------------------
  60. s | Position status 1 'd' if current 2D differential GPS position
  61. i | 'D' if current 3D differential GPS position
  62. t | 'g' if current 2D GPS position
  63. i | 'G' if current 3D GPS position
  64. o | 'S' if simulated position
  65. n | '_' if invalid position
  66. | ----------------------- ------- ------------------------
  67. | Horizontal posn error 3 EPH in meters
  68. | ----------------------- ------- ------------------------
  69. | Altitude sign 1 '+' or '-'
  70. | ----------------------- ------- ------------------------
  71. | Altitude 5 Height above or below mean
  72. \ sea level in meters
  73. ----------------------- ------- ------------------------
  74. /East/West velocity 1 'E' or 'W'
  75. | direction
  76. | ----------------------- ------- ------------------------
  77. | East/West velocity 4 Meters per second in tenths,
  78. | magnitude ("1234" = 123.4 m/s)
  79. V | ----------------------- ------- ------------------------
  80. e | North/South velocity 1 'N' or 'S'
  81. l | direction
  82. o | ----------------------- ------- ------------------------
  83. c | North/South velocity 4 Meters per second in tenths,
  84. i | magnitude ("1234" = 123.4 m/s)
  85. t | ----------------------- ------- ------------------------
  86. y | Vertical velocity 1 'U' or 'D' (up/down)
  87. | direction
  88. | ----------------------- ------- ------------------------
  89. | Vertical velocity 4 Meters per second in hundredths,
  90. \ magnitude ("1234" = 12.34 m/s)
  91. ----------------------- ------- ------------------------
  92. Sentence end 2 Carriage return, '0x0D', and
  93. line feed, '0x0A'
  94. ----------------------- ------- ------------------------
  95. If a numeric value does not fill its entire field width, the field is padded
  96. with leading '0's (eg. an altitude of 50 meters above MSL will be output as
  97. "+00050").
  98. Any or all of the data in the text sentence (except for the sentence start
  99. and sentence end fields) may be replaced with underscores to indicate
  100. invalid data.
  101. ***************************************************/
  102. #include "../include/gpsd_config.h" /* must be before all includes */
  103. #include <math.h>
  104. #include <stdbool.h>
  105. #include <stdlib.h>
  106. #include <string.h>
  107. #include <strings.h>
  108. #include "../include/gpsd.h"
  109. #ifdef GARMINTXT_ENABLE
  110. /* Simple text message is fixed length, 55 chars text data + 2 characters EOL */
  111. /* buffer for text processing */
  112. #define TXT_BUFFER_SIZE 13
  113. /**************************************************************************
  114. * decode text string to double number, translate prefix to sign
  115. * return 0: OK
  116. * -1: data error
  117. * -2: data not valid
  118. *
  119. * examples with context->errout.debug == 0:
  120. * gar_decode(context, cbuf, 9, "EW", 100000.0, &result);
  121. * E01412345 -> +14.12345
  122. * gar_decode(context, cbuf, 9, "EW", 100000.0, &result);
  123. * W01412345 -> -14.12345
  124. * gar_decode(context, cbuf, 3, "", 10.0, &result);
  125. * 123 -> +12.3
  126. **************************************************************************/
  127. static int gar_decode(const struct gps_context_t *context,
  128. const char *data, const size_t length,
  129. const char *prefix, const double dividor,
  130. double *result)
  131. {
  132. char buf[10];
  133. float sign = 1.0;
  134. int preflen = (int)strlen(prefix);
  135. int offset = 1; /* assume one character prefix (E,W,S,N,U,D, etc) */
  136. long int intresult;
  137. if (length >= sizeof(buf)) {
  138. GPSD_LOG(LOG_ERROR, &context->errout, "internal buffer too small\n");
  139. return -1;
  140. }
  141. memset(buf, 0, (int)sizeof(buf));
  142. (void)strlcpy(buf, data, length);
  143. GPSD_LOG(LOG_RAW, &context->errout, "Decoded string: %s\n", buf);
  144. if (strchr(buf, '_') != NULL) {
  145. /* value is not valid, ignore it */
  146. return -2;
  147. }
  148. /* parse prefix */
  149. do {
  150. if (preflen == 0) {
  151. offset = 0; /* only number, no prefix */
  152. break;
  153. }
  154. /* second character in prefix is flag for negative number */
  155. if (preflen >= 2) {
  156. if (buf[0] == prefix[1]) {
  157. sign = -1.0;
  158. break;
  159. }
  160. }
  161. /* first character in prefix is flag for positive number */
  162. if (preflen >= 1) {
  163. if (buf[0] == prefix[0]) {
  164. sign = 1.0;
  165. break;
  166. }
  167. }
  168. GPSD_LOG(LOG_WARN, &context->errout,
  169. "Unexpected char \"%c\" in data \"%s\"\n",
  170. buf[0], buf);
  171. return -1;
  172. } while (0);
  173. if (strspn(buf + offset, "0123456789") != length - offset) {
  174. GPSD_LOG(LOG_WARN, &context->errout, "Invalid value %s\n", buf);
  175. return -1;
  176. }
  177. intresult = atol(buf + offset);
  178. if (intresult == 0L)
  179. sign = 0.0; /* don't create negative zero */
  180. *result = (double)intresult / dividor * sign;
  181. return 0; /* SUCCESS */
  182. }
  183. /**************************************************************************
  184. * decode integer from string, check if the result is in expected range
  185. * return 0: OK
  186. * -1: data error
  187. * -2: data not valid
  188. **************************************************************************/
  189. static int gar_int_decode(const struct gps_context_t *context,
  190. const char *data, const size_t length,
  191. const unsigned int min, const unsigned int max,
  192. unsigned int *result)
  193. {
  194. char buf[6];
  195. unsigned int res;
  196. if (length >= sizeof(buf)) {
  197. GPSD_LOG(LOG_ERROR, &context->errout, "internal buffer too small\n");
  198. return -1;
  199. }
  200. memset(buf, 0, (int)sizeof(buf));
  201. (void)strlcpy(buf, data, length);
  202. GPSD_LOG(LOG_RAW, &context->errout, "Decoded string: %s\n", buf);
  203. if (strchr(buf, '_') != NULL) {
  204. /* value is not valid, ignore it */
  205. return -2;
  206. }
  207. if (strspn(buf, "0123456789") != length) {
  208. GPSD_LOG(LOG_WARN, &context->errout, "Invalid value %s\n", buf);
  209. return -1;
  210. }
  211. res = (unsigned)atoi(buf);
  212. if ((res >= min) && (res <= max)) {
  213. *result = res;
  214. return 0; /* SUCCESS */
  215. } else {
  216. GPSD_LOG(LOG_WARN, &context->errout,
  217. "Value %u out of range <%u, %u>\n", res, min,
  218. max);
  219. return -1;
  220. }
  221. }
  222. /**************************************************************************
  223. *
  224. * Entry points begin here
  225. *
  226. **************************************************************************/
  227. gps_mask_t garmintxt_parse(struct gps_device_t * session)
  228. {
  229. /* parse GARMIN Simple Text sentence, unpack it into a session structure */
  230. gps_mask_t mask = 0;
  231. GPSD_LOG(LOG_PROG, &session->context->errout,
  232. "Garmin Simple Text packet, len %zd: %s\n",
  233. session->lexer.outbuflen, (char*)session->lexer.outbuffer);
  234. if (session->lexer.outbuflen < 54) {
  235. /* trailing CR and LF can be ignored; ('@' + 54x 'DATA' + '\r\n')
  236. * has length 57 */
  237. GPSD_LOG(LOG_WARN, &session->context->errout,
  238. "Message is too short, rejected.\n");
  239. return ONLINE_SET;
  240. }
  241. session->lexer.type = GARMINTXT_PACKET;
  242. /* only one message, set cycle start */
  243. session->cycle_end_reliable = true;
  244. do {
  245. struct tm gdate; /* date part of last sentence time */
  246. unsigned int result;
  247. char *buf = (char *)session->lexer.outbuffer + 1;
  248. GPSD_LOG(LOG_PROG, &session->context->errout,
  249. "Timestamp: %.12s\n", buf);
  250. /* year */
  251. if (0 != gar_int_decode(session->context,
  252. buf + 0, 2, 0, 99, &result))
  253. break;
  254. gdate.tm_year = (session->context->century + (int)result) - 1900;
  255. /* month */
  256. if (0 != gar_int_decode(session->context,
  257. buf + 2, 2, 1, 12, &result))
  258. break;
  259. gdate.tm_mon = (int)result - 1;
  260. /* day */
  261. if (0 != gar_int_decode(session->context,
  262. buf + 4, 2, 1, 31, &result))
  263. break;
  264. gdate.tm_mday = (int)result;
  265. /* hour */
  266. if (0 != gar_int_decode(session->context,
  267. buf + 6, 2, 0, 23, &result))
  268. break;
  269. /* mday update?? */
  270. gdate.tm_hour = (int)result;
  271. /* minute */
  272. if (0 != gar_int_decode(session->context,
  273. buf + 8, 2, 0, 59, &result))
  274. break;
  275. gdate.tm_min = (int)result;
  276. /* second */
  277. /* second value can be even 60, occasional leap second */
  278. if (0 != gar_int_decode(session->context,
  279. buf + 10, 2, 0, 60, &result))
  280. break;
  281. gdate.tm_sec = (int)result;
  282. session->newdata.time.tv_sec = mkgmtime(&gdate);
  283. session->newdata.time.tv_nsec = 0;
  284. mask |= TIME_SET;
  285. } while (0);
  286. /* assume that position is unknown; if the position is known we
  287. * will fix status information later */
  288. session->newdata.mode = MODE_NO_FIX;
  289. session->newdata.status = STATUS_UNK;
  290. mask |= MODE_SET | STATUS_SET | CLEAR_IS | REPORT_IS;
  291. /* process position */
  292. do {
  293. double lat, lon;
  294. unsigned int degfrag;
  295. char status;
  296. /* Latitude, [NS]ddmmmmm */
  297. /* decode degrees of Latitude */
  298. if (0 !=
  299. gar_decode(session->context,
  300. (char *)session->lexer.outbuffer + 13, 3, "NS", 1.0,
  301. &lat))
  302. break;
  303. /* decode minutes of Latitude */
  304. if (0 !=
  305. gar_int_decode(session->context,
  306. (char *)session->lexer.outbuffer + 16, 5, 0,
  307. 99999, &degfrag))
  308. break;
  309. lat += degfrag * 100.0 / 60.0 / 100000.0;
  310. session->newdata.latitude = lat;
  311. /* Longitude, [EW]dddmmmmm */
  312. /* decode degrees of Longitude */
  313. if (0 !=
  314. gar_decode(session->context,
  315. (char *)session->lexer.outbuffer + 21, 4, "EW", 1.0,
  316. &lon))
  317. break;
  318. /* decode minutes of Longitude */
  319. if (0 !=
  320. gar_int_decode(session->context,
  321. (char *)session->lexer.outbuffer + 25, 5, 0,
  322. 99999, &degfrag))
  323. break;
  324. lon += degfrag * 100.0 / 60.0 / 100000.0;
  325. session->newdata.longitude = lon;
  326. session->newdata.geoid_sep = wgs84_separation(lat, lon);
  327. /* fix mode, GPS status, [gGdDS_] */
  328. status = (char)session->lexer.outbuffer[30];
  329. switch (status) {
  330. case 'G':
  331. case 'S': /* 'S' is DEMO mode, assume 3D position */
  332. session->newdata.mode = MODE_3D;
  333. session->newdata.status = STATUS_GPS;
  334. break;
  335. case 'D':
  336. session->newdata.mode = MODE_3D;
  337. session->newdata.status = STATUS_DGPS;
  338. break;
  339. case 'g':
  340. session->newdata.mode = MODE_2D;
  341. session->newdata.status = STATUS_GPS;
  342. break;
  343. case 'd':
  344. session->newdata.mode = MODE_2D;
  345. session->newdata.status = STATUS_DGPS;
  346. break;
  347. default:
  348. session->newdata.mode = MODE_NO_FIX;
  349. session->newdata.status = STATUS_UNK;
  350. }
  351. mask |= MODE_SET | STATUS_SET | LATLON_SET;
  352. } while (0);
  353. /* EPH */
  354. do {
  355. double eph;
  356. if (0 !=
  357. gar_decode(session->context,
  358. (char *)session->lexer.outbuffer + 31, 3, "", 1.0,
  359. &eph))
  360. break;
  361. /* this conversion looks dodgy... */
  362. session->newdata.eph = eph * (GPSD_CONFIDENCE / CEP50_SIGMA);
  363. mask |= HERR_SET;
  364. } while (0);
  365. /* Altitude */
  366. do {
  367. double alt;
  368. if (0 !=
  369. gar_decode(session->context,
  370. (char *)session->lexer.outbuffer + 34, 6, "+-", 1.0,
  371. &alt))
  372. break;
  373. /* alt is MSL */
  374. session->newdata.altMSL = alt;
  375. /* Let gpsd_error_model() deal with altHAE */
  376. mask |= ALTITUDE_SET;
  377. } while (0);
  378. /* Velocities, meters per second */
  379. do {
  380. double ewvel, nsvel;
  381. double climb;
  382. if (0 != gar_decode(session->context,
  383. (char *)session->lexer.outbuffer + 40, 5,
  384. "EW", 10.0, &ewvel))
  385. break;
  386. if (0 != gar_decode(session->context,
  387. (char *)session->lexer.outbuffer + 45, 5,
  388. "NS", 10.0, &nsvel))
  389. break;
  390. if (0 != gar_decode(session->context,
  391. (char *)session->lexer.outbuffer + 50, 5,
  392. "UD", 100.0, &climb))
  393. break;
  394. session->newdata.NED.velN = ewvel;
  395. session->newdata.NED.velE = nsvel;
  396. session->newdata.NED.velD = -climb;
  397. mask |= VNED_SET;
  398. } while (0);
  399. GPSD_LOG(LOG_DATA, &session->context->errout,
  400. "GTXT: time=%lld, lat=%.2f lon=%.2f altMSL=%.2f "
  401. "climb=%.2f eph=%.2f mode=%d status=%d\n",
  402. (long long)session->newdata.time.tv_sec, session->newdata.latitude,
  403. session->newdata.longitude, session->newdata.altMSL,
  404. session->newdata.climb, session->newdata.eph,
  405. session->newdata.mode,
  406. session->newdata.status);
  407. return mask;
  408. }
  409. #endif /* GARMINTXT_ENABLE */
  410. // vim: set expandtab shiftwidth=4