123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465 |
- /*
- * Handle the Garmin simple text format supported by some Garmins.
- * Tested with the 'Garmin eTrex Legend' device working in 'Text Out' mode.
- *
- * Protocol info from:
- * http://www8.garmin.com/support/text_out.html
- * http://www.garmin.com/support/commProtocol.html
- *
- * Code by: Petr Slansky <slansky@usa.net>
- * all rights abandoned, a thank would be nice if you use this code.
- *
- * -D 3 = packet trace
- * -D 4 = packet details
- * -D 5 = more packet details
- * -D 6 = very excessive details
- *
- * limitations:
- * very simple protocol, only very basic information
- * TODO
- * do not have from garmin:
- * pdop
- * vdop
- * magnetic variation
- * satellite information
- *
- * This file is Copyright 2010 by the GPSD project
- * SPDX-License-Identifier: BSD-2-clause
- *
- */
- /***************************************************
- Garmin Simple Text Output Format:
- The simple text (ASCII) output contains time, position, and velocity data in
- the fixed width fields (not delimited) defined in the following table:
- FIELD DESCRIPTION: WIDTH: NOTES:
- ----------------------- ------- ------------------------
- Sentence start 1 Always '@'
- ----------------------- ------- ------------------------
- /Year 2 Last two digits of UTC year
- | ----------------------- ------- ------------------------
- | Month 2 UTC month, "01".."12"
- T | ----------------------- ------- ------------------------
- i | Day 2 UTC day of month, "01".."31"
- m | ----------------------- ------- ------------------------
- e | Hour 2 UTC hour, "00".."23"
- | ----------------------- ------- ------------------------
- | Minute 2 UTC minute, "00".."59"
- | ----------------------- ------- ------------------------
- \Second 2 UTC second, "00".."59"
- ----------------------- ------- ------------------------
- /Latitude hemisphere 1 'N' or 'S'
- | ----------------------- ------- ------------------------
- | Latitude position 7 WGS84 ddmmmmm, with an implied
- | decimal after the 4th digit
- | ----------------------- ------- ------------------------
- | Longitude hemisphere 1 'E' or 'W'
- | ----------------------- ------- ------------------------
- | Longitude position 8 WGS84 dddmmmmm with an implied
- P | decimal after the 5th digit
- o | ----------------------- ------- ------------------------
- s | Position status 1 'd' if current 2D differential GPS position
- i | 'D' if current 3D differential GPS position
- t | 'g' if current 2D GPS position
- i | 'G' if current 3D GPS position
- o | 'S' if simulated position
- n | '_' if invalid position
- | ----------------------- ------- ------------------------
- | Horizontal posn error 3 EPH in meters
- | ----------------------- ------- ------------------------
- | Altitude sign 1 '+' or '-'
- | ----------------------- ------- ------------------------
- | Altitude 5 Height above or below mean
- \ sea level in meters
- ----------------------- ------- ------------------------
- /East/West velocity 1 'E' or 'W'
- | direction
- | ----------------------- ------- ------------------------
- | East/West velocity 4 Meters per second in tenths,
- | magnitude ("1234" = 123.4 m/s)
- V | ----------------------- ------- ------------------------
- e | North/South velocity 1 'N' or 'S'
- l | direction
- o | ----------------------- ------- ------------------------
- c | North/South velocity 4 Meters per second in tenths,
- i | magnitude ("1234" = 123.4 m/s)
- t | ----------------------- ------- ------------------------
- y | Vertical velocity 1 'U' or 'D' (up/down)
- | direction
- | ----------------------- ------- ------------------------
- | Vertical velocity 4 Meters per second in hundredths,
- \ magnitude ("1234" = 12.34 m/s)
- ----------------------- ------- ------------------------
- Sentence end 2 Carriage return, '0x0D', and
- line feed, '0x0A'
- ----------------------- ------- ------------------------
- If a numeric value does not fill its entire field width, the field is padded
- with leading '0's (eg. an altitude of 50 meters above MSL will be output as
- "+00050").
- Any or all of the data in the text sentence (except for the sentence start
- and sentence end fields) may be replaced with underscores to indicate
- invalid data.
- ***************************************************/
- #include "../include/gpsd_config.h" /* must be before all includes */
- #include <math.h>
- #include <stdbool.h>
- #include <stdlib.h>
- #include <string.h>
- #include <strings.h>
- #include "../include/gpsd.h"
- #ifdef GARMINTXT_ENABLE
- /* Simple text message is fixed length, 55 chars text data + 2 characters EOL */
- /* buffer for text processing */
- #define TXT_BUFFER_SIZE 13
- /**************************************************************************
- * decode text string to double number, translate prefix to sign
- * return 0: OK
- * -1: data error
- * -2: data not valid
- *
- * examples with context->errout.debug == 0:
- * gar_decode(context, cbuf, 9, "EW", 100000.0, &result);
- * E01412345 -> +14.12345
- * gar_decode(context, cbuf, 9, "EW", 100000.0, &result);
- * W01412345 -> -14.12345
- * gar_decode(context, cbuf, 3, "", 10.0, &result);
- * 123 -> +12.3
- **************************************************************************/
- static int gar_decode(const struct gps_context_t *context,
- const char *data, const size_t length,
- const char *prefix, const double dividor,
- double *result)
- {
- char buf[10];
- float sign = 1.0;
- int preflen = (int)strlen(prefix);
- int offset = 1; /* assume one character prefix (E,W,S,N,U,D, etc) */
- long int intresult;
- if (length >= sizeof(buf)) {
- GPSD_LOG(LOG_ERROR, &context->errout, "internal buffer too small\n");
- return -1;
- }
- memset(buf, 0, (int)sizeof(buf));
- (void)strlcpy(buf, data, length);
- GPSD_LOG(LOG_RAW, &context->errout, "Decoded string: %s\n", buf);
- if (strchr(buf, '_') != NULL) {
- /* value is not valid, ignore it */
- return -2;
- }
- /* parse prefix */
- do {
- if (preflen == 0) {
- offset = 0; /* only number, no prefix */
- break;
- }
- /* second character in prefix is flag for negative number */
- if (preflen >= 2) {
- if (buf[0] == prefix[1]) {
- sign = -1.0;
- break;
- }
- }
- /* first character in prefix is flag for positive number */
- if (preflen >= 1) {
- if (buf[0] == prefix[0]) {
- sign = 1.0;
- break;
- }
- }
- GPSD_LOG(LOG_WARN, &context->errout,
- "Unexpected char \"%c\" in data \"%s\"\n",
- buf[0], buf);
- return -1;
- } while (0);
- if (strspn(buf + offset, "0123456789") != length - offset) {
- GPSD_LOG(LOG_WARN, &context->errout, "Invalid value %s\n", buf);
- return -1;
- }
- intresult = atol(buf + offset);
- if (intresult == 0L)
- sign = 0.0; /* don't create negative zero */
- *result = (double)intresult / dividor * sign;
- return 0; /* SUCCESS */
- }
- /**************************************************************************
- * decode integer from string, check if the result is in expected range
- * return 0: OK
- * -1: data error
- * -2: data not valid
- **************************************************************************/
- static int gar_int_decode(const struct gps_context_t *context,
- const char *data, const size_t length,
- const unsigned int min, const unsigned int max,
- unsigned int *result)
- {
- char buf[6];
- unsigned int res;
- if (length >= sizeof(buf)) {
- GPSD_LOG(LOG_ERROR, &context->errout, "internal buffer too small\n");
- return -1;
- }
- memset(buf, 0, (int)sizeof(buf));
- (void)strlcpy(buf, data, length);
- GPSD_LOG(LOG_RAW, &context->errout, "Decoded string: %s\n", buf);
- if (strchr(buf, '_') != NULL) {
- /* value is not valid, ignore it */
- return -2;
- }
- if (strspn(buf, "0123456789") != length) {
- GPSD_LOG(LOG_WARN, &context->errout, "Invalid value %s\n", buf);
- return -1;
- }
- res = (unsigned)atoi(buf);
- if ((res >= min) && (res <= max)) {
- *result = res;
- return 0; /* SUCCESS */
- } else {
- GPSD_LOG(LOG_WARN, &context->errout,
- "Value %u out of range <%u, %u>\n", res, min,
- max);
- return -1;
- }
- }
- /**************************************************************************
- *
- * Entry points begin here
- *
- **************************************************************************/
- gps_mask_t garmintxt_parse(struct gps_device_t * session)
- {
- /* parse GARMIN Simple Text sentence, unpack it into a session structure */
- gps_mask_t mask = 0;
- GPSD_LOG(LOG_PROG, &session->context->errout,
- "Garmin Simple Text packet, len %zd: %s\n",
- session->lexer.outbuflen, (char*)session->lexer.outbuffer);
- if (session->lexer.outbuflen < 54) {
- /* trailing CR and LF can be ignored; ('@' + 54x 'DATA' + '\r\n')
- * has length 57 */
- GPSD_LOG(LOG_WARN, &session->context->errout,
- "Message is too short, rejected.\n");
- return ONLINE_SET;
- }
- session->lexer.type = GARMINTXT_PACKET;
- /* only one message, set cycle start */
- session->cycle_end_reliable = true;
- do {
- struct tm gdate; /* date part of last sentence time */
- unsigned int result;
- char *buf = (char *)session->lexer.outbuffer + 1;
- GPSD_LOG(LOG_PROG, &session->context->errout,
- "Timestamp: %.12s\n", buf);
- /* year */
- if (0 != gar_int_decode(session->context,
- buf + 0, 2, 0, 99, &result))
- break;
- gdate.tm_year = (session->context->century + (int)result) - 1900;
- /* month */
- if (0 != gar_int_decode(session->context,
- buf + 2, 2, 1, 12, &result))
- break;
- gdate.tm_mon = (int)result - 1;
- /* day */
- if (0 != gar_int_decode(session->context,
- buf + 4, 2, 1, 31, &result))
- break;
- gdate.tm_mday = (int)result;
- /* hour */
- if (0 != gar_int_decode(session->context,
- buf + 6, 2, 0, 23, &result))
- break;
- /* mday update?? */
- gdate.tm_hour = (int)result;
- /* minute */
- if (0 != gar_int_decode(session->context,
- buf + 8, 2, 0, 59, &result))
- break;
- gdate.tm_min = (int)result;
- /* second */
- /* second value can be even 60, occasional leap second */
- if (0 != gar_int_decode(session->context,
- buf + 10, 2, 0, 60, &result))
- break;
- gdate.tm_sec = (int)result;
- session->newdata.time.tv_sec = mkgmtime(&gdate);
- session->newdata.time.tv_nsec = 0;
- mask |= TIME_SET;
- } while (0);
- /* assume that position is unknown; if the position is known we
- * will fix status information later */
- session->newdata.mode = MODE_NO_FIX;
- session->newdata.status = STATUS_UNK;
- mask |= MODE_SET | STATUS_SET | CLEAR_IS | REPORT_IS;
- /* process position */
- do {
- double lat, lon;
- unsigned int degfrag;
- char status;
- /* Latitude, [NS]ddmmmmm */
- /* decode degrees of Latitude */
- if (0 !=
- gar_decode(session->context,
- (char *)session->lexer.outbuffer + 13, 3, "NS", 1.0,
- &lat))
- break;
- /* decode minutes of Latitude */
- if (0 !=
- gar_int_decode(session->context,
- (char *)session->lexer.outbuffer + 16, 5, 0,
- 99999, °frag))
- break;
- lat += degfrag * 100.0 / 60.0 / 100000.0;
- session->newdata.latitude = lat;
- /* Longitude, [EW]dddmmmmm */
- /* decode degrees of Longitude */
- if (0 !=
- gar_decode(session->context,
- (char *)session->lexer.outbuffer + 21, 4, "EW", 1.0,
- &lon))
- break;
- /* decode minutes of Longitude */
- if (0 !=
- gar_int_decode(session->context,
- (char *)session->lexer.outbuffer + 25, 5, 0,
- 99999, °frag))
- break;
- lon += degfrag * 100.0 / 60.0 / 100000.0;
- session->newdata.longitude = lon;
- session->newdata.geoid_sep = wgs84_separation(lat, lon);
- /* fix mode, GPS status, [gGdDS_] */
- status = (char)session->lexer.outbuffer[30];
- switch (status) {
- case 'G':
- case 'S': /* 'S' is DEMO mode, assume 3D position */
- session->newdata.mode = MODE_3D;
- session->newdata.status = STATUS_GPS;
- break;
- case 'D':
- session->newdata.mode = MODE_3D;
- session->newdata.status = STATUS_DGPS;
- break;
- case 'g':
- session->newdata.mode = MODE_2D;
- session->newdata.status = STATUS_GPS;
- break;
- case 'd':
- session->newdata.mode = MODE_2D;
- session->newdata.status = STATUS_DGPS;
- break;
- default:
- session->newdata.mode = MODE_NO_FIX;
- session->newdata.status = STATUS_UNK;
- }
- mask |= MODE_SET | STATUS_SET | LATLON_SET;
- } while (0);
- /* EPH */
- do {
- double eph;
- if (0 !=
- gar_decode(session->context,
- (char *)session->lexer.outbuffer + 31, 3, "", 1.0,
- &eph))
- break;
- /* this conversion looks dodgy... */
- session->newdata.eph = eph * (GPSD_CONFIDENCE / CEP50_SIGMA);
- mask |= HERR_SET;
- } while (0);
- /* Altitude */
- do {
- double alt;
- if (0 !=
- gar_decode(session->context,
- (char *)session->lexer.outbuffer + 34, 6, "+-", 1.0,
- &alt))
- break;
- /* alt is MSL */
- session->newdata.altMSL = alt;
- /* Let gpsd_error_model() deal with altHAE */
- mask |= ALTITUDE_SET;
- } while (0);
- /* Velocities, meters per second */
- do {
- double ewvel, nsvel;
- double climb;
- if (0 != gar_decode(session->context,
- (char *)session->lexer.outbuffer + 40, 5,
- "EW", 10.0, &ewvel))
- break;
- if (0 != gar_decode(session->context,
- (char *)session->lexer.outbuffer + 45, 5,
- "NS", 10.0, &nsvel))
- break;
- if (0 != gar_decode(session->context,
- (char *)session->lexer.outbuffer + 50, 5,
- "UD", 100.0, &climb))
- break;
- session->newdata.NED.velN = ewvel;
- session->newdata.NED.velE = nsvel;
- session->newdata.NED.velD = -climb;
- mask |= VNED_SET;
- } while (0);
- GPSD_LOG(LOG_DATA, &session->context->errout,
- "GTXT: time=%lld, lat=%.2f lon=%.2f altMSL=%.2f "
- "climb=%.2f eph=%.2f mode=%d status=%d\n",
- (long long)session->newdata.time.tv_sec, session->newdata.latitude,
- session->newdata.longitude, session->newdata.altMSL,
- session->newdata.climb, session->newdata.eph,
- session->newdata.mode,
- session->newdata.status);
- return mask;
- }
- #endif /* GARMINTXT_ENABLE */
- // vim: set expandtab shiftwidth=4
|