merge with master

This commit is contained in:
Kosma Moczek 2014-06-17 14:52:46 +02:00
commit c375b8144b
6 changed files with 682 additions and 296 deletions

View File

@ -4,8 +4,8 @@
# the terms of the Do What The Fuck You Want To Public License, Version 2, as # the terms of the Do What The Fuck You Want To Public License, Version 2, as
# published by Sam Hocevar. See the COPYING file for more details. # published by Sam Hocevar. See the COPYING file for more details.
CFLAGS = -g -Wall -Wextra -Werror CFLAGS = -g -Wall -Wextra -Werror -std=c99
LDFLAGS = -lcheck LDLIBS = -lcheck
all: scan-build test example all: scan-build test example
@echo "+++ All good.""" @echo "+++ All good."""

View File

@ -20,27 +20,33 @@ systems.
* ``GGA`` (Fix Data) * ``GGA`` (Fix Data)
* ``GSA`` (DOP and active satellites) * ``GSA`` (DOP and active satellites)
* ``GLL`` (Geographic Position -- Latitude/Longitude) * ``GLL`` (Geographic Position -- Latitude/Longitude)
* ``GST`` (Pseudorange Noise Statistics)
* ``GSV`` (Satellites in view)
Adding support for more sentences is trivial; see ``minmea.c`` source. Adding support for more sentences is trivial; see ``minmea.c`` source.
## Fractional number format ## Fractional number format
Internally, minmea stores fractional numbers as pairs of two integers: ``(value, scale)``. Internally, minmea stores fractional numbers as pairs of two integers: ``{value, scale}``.
For example, a value of ``"-123.456"`` would be parsed as ``(-123456, 1000)``. As this For example, a value of ``"-123.456"`` would be parsed as ``{-123456, 1000}``. As this
format is quite unwieldy, minmea provides the following convenience macros for converting format is quite unwieldy, minmea provides the following convenience functions for converting
to either fixed-point or floating-point format: to either fixed-point or floating-point format:
* ``minmea_rescale(-123456, 1000, 10) => -1235`` * ``minmea_rescale({-123456, 1000}, 10) => -1235``
* ``minmea_float(-123456, 1000) => -123.456`` * ``minmea_float({-123456, 1000}) => -123.456``
The compound type ``struct minmea_float`` uses ``int_least32_t`` internally. Therefore,
the coordinate precision is guaranteed to be at least ``[+-]DDDMM.MMMMM`` (five decimal digits)
or ±20cm LSB at the equator.
## Coordinate format ## Coordinate format
NMEA uses the clunky ``DDMM.MMMM`` format which, honestly, is not good in the internet era. NMEA uses the clunky ``DDMM.MMMM`` format which, honestly, is not good in the internet era.
Internally, minmea stores it as a fractional number (see above); for practical uses, Internally, minmea stores it as a fractional number (see above); for practical uses,
the value should be probably converted to the DD.DDDDD floating point format using the the value should be probably converted to the DD.DDDDD floating point format using the
following macro: following function:
* ``minmea_coord(-375165, 100) => -37.860832`` * ``minmea_tocoord({-375165, 100}) => -37.860832``
The library doesn't perform this conversion automatically for the following reasons: The library doesn't perform this conversion automatically for the following reasons:
@ -53,30 +59,43 @@ The library doesn't perform this conversion automatically for the following reas
```c ```c
char line[MINMEA_MAX_LENGTH]; char line[MINMEA_MAX_LENGTH];
while (fgets(line, sizeof(line), stdin) != NULL) { while (fgets(line, sizeof(line), stdin) != NULL) {
printf("%s", line);
switch (minmea_sentence_id(line)) { switch (minmea_sentence_id(line)) {
case MINMEA_SENTENCE_RMC: { case MINMEA_SENTENCE_RMC: {
struct minmea_sentence_rmc frame; struct minmea_sentence_rmc frame;
if (minmea_parse_rmc(&frame, line)) { if (minmea_parse_rmc(&frame, line)) {
printf("+++ raw coordinates and speed: (%d/%d,%d/%d) %d/%d\n", printf("$RMC: raw coordinates and speed: (%d/%d,%d/%d) %d/%d\n",
frame.latitude, frame.latitude_scale, frame.latitude.value, frame.latitude.scale,
frame.longitude, frame.longitude_scale, frame.longitude.value, frame.longitude.scale,
frame.speed, frame.speed_scale); frame.speed.value, frame.speed.scale);
printf("+++ fixed-point coordinates and speed scaled to three decimal places: (%d,%d) %d\n", printf("$RMC fixed-point coordinates and speed scaled to three decimal places: (%d,%d) %d\n",
minmea_rescale(frame.latitude, frame.latitude_scale, 1000), minmea_rescale(&frame.latitude, 1000),
minmea_rescale(frame.longitude, frame.longitude_scale, 1000), minmea_rescale(&frame.longitude, 1000),
minmea_rescale(frame.speed, frame.speed_scale, 1000)); minmea_rescale(&frame.speed, 1000));
printf("+++ floating point degree coordinates and speed: (%f,%f) %f\n", printf("$RMC floating point degree coordinates and speed: (%f,%f) %f\n",
minmea_coord(frame.latitude, frame.latitude_scale), minmea_tocoord(&frame.latitude),
minmea_coord(frame.longitude, frame.longitude_scale), minmea_tocoord(&frame.longitude),
minmea_float(frame.speed, frame.speed_scale)); minmea_tofloat(&frame.speed));
} }
} break; } break;
case MINMEA_SENTENCE_GGA: { case MINMEA_SENTENCE_GGA: {
struct minmea_sentence_gga frame; struct minmea_sentence_gga frame;
if (minmea_parse_gga(&frame, line)) { if (minmea_parse_gga(&frame, line)) {
printf("$GPGGA: fix quality: %d\n", frame.fix_quality); printf("$GGA: fix quality: %d\n", frame.fix_quality);
}
} break;
case MINMEA_SENTENCE_GSV: {
struct minmea_sentence_gsv frame;
if (minmea_parse_gsv(&frame, line)) {
printf("$GSV: message %d of %d\n", frame.msg_nr, frame.total_msgs);
printf("$GSV: sattelites in view: %d\n", frame.total_sats);
for (int i = 0; i < 4; i++)
printf("$GSV: sat nr %d, elevation: %d, azimuth: %d, snr: %d dbm\n",
frame.sats[i].nr,
frame.sats[i].elevation,
frame.sats[i].azimuth,
frame.sats[i].snr);
} }
} break; } break;
} }
@ -100,10 +119,6 @@ typing ``make``.
## Limitations ## Limitations
* Fractional numbers are represented as ``int`` internally, which is 32 bits on
most embedded platforms. Therefore, the maximum supported coordinate precision
is ``[+-]DDDMM.MMMMM``. The library does not check for integer overflow at the
moment; coordinates with more precision will not parse correctly.
* Only a handful of frames is supported right now. * Only a handful of frames is supported right now.
* There's no support for omitting parts of the library from building. As * There's no support for omitting parts of the library from building. As
a workaround, use the ``-ffunction-sections -Wl,--gc-sections`` linker flags a workaround, use the ``-ffunction-sections -Wl,--gc-sections`` linker flags

View File

@ -21,25 +21,58 @@ int main()
case MINMEA_SENTENCE_RMC: { case MINMEA_SENTENCE_RMC: {
struct minmea_sentence_rmc frame; struct minmea_sentence_rmc frame;
if (minmea_parse_rmc(&frame, line)) { if (minmea_parse_rmc(&frame, line)) {
printf("+++ raw coordinates and speed: (%d/%d,%d/%d) %d/%d\n", printf("$xxRMC: raw coordinates and speed: (%d/%d,%d/%d) %d/%d\n",
frame.latitude, frame.latitude_scale, frame.latitude.value, frame.latitude.scale,
frame.longitude, frame.longitude_scale, frame.longitude.value, frame.longitude.scale,
frame.speed, frame.speed_scale); frame.speed.value, frame.speed.scale);
printf("+++ fixed-point coordinates and speed scaled to three decimal places: (%d,%d) %d\n", printf("$xxRMC fixed-point coordinates and speed scaled to three decimal places: (%d,%d) %d\n",
minmea_rescale(frame.latitude, frame.latitude_scale, 1000), minmea_rescale(&frame.latitude, 1000),
minmea_rescale(frame.longitude, frame.longitude_scale, 1000), minmea_rescale(&frame.longitude, 1000),
minmea_rescale(frame.speed, frame.speed_scale, 1000)); minmea_rescale(&frame.speed, 1000));
printf("+++ floating point degree coordinates and speed: (%f,%f) %f\n", printf("$xxRMC floating point degree coordinates and speed: (%f,%f) %f\n",
minmea_coord(frame.latitude, frame.latitude_scale), minmea_tocoord(&frame.latitude),
minmea_coord(frame.longitude, frame.longitude_scale), minmea_tocoord(&frame.longitude),
minmea_float(frame.speed, frame.speed_scale)); minmea_tofloat(&frame.speed));
} }
} break; } break;
case MINMEA_SENTENCE_GGA: { case MINMEA_SENTENCE_GGA: {
struct minmea_sentence_gga frame; struct minmea_sentence_gga frame;
if (minmea_parse_gga(&frame, line)) { if (minmea_parse_gga(&frame, line)) {
printf("$GPGGA: fix quality: %d\n", frame.fix_quality); printf("$xxGGA: fix quality: %d\n", frame.fix_quality);
}
} break;
case MINMEA_SENTENCE_GST: {
struct minmea_sentence_gst frame;
if (minmea_parse_gst(&frame, line)) {
printf("$xxGST: raw latitude,longitude and altitude error deviation: (%d/%d,%d/%d,%d/%d)\n",
frame.latitude_error_deviation.value, frame.latitude_error_deviation.scale,
frame.longitude_error_deviation.value, frame.longitude_error_deviation.scale,
frame.altitude_error_deviation.value, frame.altitude_error_deviation.scale);
printf("$xxGST fixed point latitude,longitude and altitude error deviation \
scaled to one decimal place: (%d,%d,%d)\n",
minmea_rescale(&frame.latitude_error_deviation, 10),
minmea_rescale(&frame.longitude_error_deviation, 10),
minmea_rescale(&frame.altitude_error_deviation, 10));
printf("$xxGST floating point degree latitude, longitude and altitude error deviation: (%f,%f,%f)",
minmea_tofloat(&frame.latitude_error_deviation),
minmea_tofloat(&frame.longitude_error_deviation),
minmea_tofloat(&frame.altitude_error_deviation));
}
} break;
case MINMEA_SENTENCE_GSV: {
struct minmea_sentence_gsv frame;
if (minmea_parse_gsv(&frame, line)) {
printf("$xxGSV: message %d of %d\n", frame.msg_nr, frame.total_msgs);
printf("$xxGSV: sattelites in view: %d\n", frame.total_sats);
for (int i = 0; i < 4; i++)
printf("$xxGSV: sat nr %d, elevation: %d, azimuth: %d, snr: %d dbm\n",
frame.sats[i].nr,
frame.sats[i].elevation,
frame.sats[i].azimuth,
frame.sats[i].snr);
} }
} break; } break;

210
minmea.c
View File

@ -72,27 +72,45 @@ static inline bool minmea_isfield(char c) {
bool minmea_scan(const char *sentence, const char *format, ...) bool minmea_scan(const char *sentence, const char *format, ...)
{ {
bool result = false; bool result = false;
bool optional = false;
va_list ap; va_list ap;
va_start(ap, format); va_start(ap, format);
const char *field = sentence; const char *field = sentence;
#define next_field() \ #define next_field() \
do { \ do { \
while (minmea_isfield(*sentence++)) {} \ /* Progress to the next field. */ \
while (minmea_isfield(*sentence)) \
sentence++; \
/* Make sure there is a field there. */ \
if (*sentence == ',') { \
sentence++; \
field = sentence; \ field = sentence; \
} else { \
field = NULL; \
} \
} while (0) } while (0)
while (*format) { while (*format) {
char type = *format++; char type = *format++;
if (type == ';') {
// All further fields are optional.
optional = true;
continue;
}
if (!field && !optional) {
// Field requested but we ran out if input. Bail out.
goto parse_error;
}
switch (type) { switch (type) {
case 'c': { // Single character field (char). case 'c': { // Single character field (char).
char value = '\0'; char value = '\0';
if (minmea_isfield(*field)) if (field && minmea_isfield(*field))
value = *field; value = *field;
else
value = '\0';
*va_arg(ap, char *) = value; *va_arg(ap, char *) = value;
} break; } break;
@ -100,7 +118,7 @@ bool minmea_scan(const char *sentence, const char *format, ...)
case 'd': { // Single character direction field (int). case 'd': { // Single character direction field (int).
int value = 0; int value = 0;
if (minmea_isfield(*field)) { if (field && minmea_isfield(*field)) {
switch (*field) { switch (*field) {
case 'N': case 'N':
case 'E': case 'E':
@ -111,58 +129,76 @@ bool minmea_scan(const char *sentence, const char *format, ...)
value = -1; value = -1;
break; break;
default: default:
goto end; goto parse_error;
} }
} }
*va_arg(ap, int *) = value; *va_arg(ap, int *) = value;
} break; } break;
case 'f': { // Fractional value with scale (int, int). case 'f': { // Fractional value with scale (struct minmea_float).
int sign = 0; int sign = 0;
int value = -1; int_least32_t value = -1;
int scale = 0; int_least32_t scale = 0;
if (field) {
while (minmea_isfield(*field)) { while (minmea_isfield(*field)) {
if (*field == '+' && !sign && value == -1) { if (*field == '+' && !sign && value == -1) {
sign = 1; sign = 1;
} else if (*field == '-' && !sign && value == -1) { } else if (*field == '-' && !sign && value == -1) {
sign = -1; sign = -1;
} else if (isdigit((unsigned char) *field)) { } else if (isdigit((unsigned char) *field)) {
int digit = *field - '0';
if (value == -1) if (value == -1)
value = 0; value = 0;
value = (10 * value) + (*field - '0'); if (value > (INT_LEAST32_MAX-digit) / 10) {
/* we ran out of bits, what do we do? */
if (scale) {
/* truncate extra precision */
break;
} else {
/* integer overflow. bail out. */
goto parse_error;
}
}
value = (10 * value) + digit;
if (scale) if (scale)
scale *= 10; scale *= 10;
} else if (*field == '.' && scale == 0) { } else if (*field == '.' && scale == 0) {
scale = 1; scale = 1;
} else { } else {
goto end; goto parse_error;
} }
field++; field++;
} }
}
if ((sign || scale) && value == -1) if ((sign || scale) && value == -1)
goto end; goto parse_error;
if (value == -1) { if (value == -1) {
/* No digits were scanned. */
value = 0; value = 0;
scale = 0; scale = 0;
} else if (scale == 0) {
/* No decimal point. */
scale = 1;
} }
if (sign) if (sign)
value *= sign; value *= sign;
*va_arg(ap, int *) = value; *va_arg(ap, struct minmea_float *) = (struct minmea_float) {value, scale};
*va_arg(ap, int *) = scale;
} break; } break;
case 'i': { // Integer value, default 0 (int). case 'i': { // Integer value, default 0 (int).
int value; int value = 0;
if (field) {
char *endptr; char *endptr;
value = strtol(field, &endptr, 10); value = strtol(field, &endptr, 10);
if (minmea_isfield(*endptr)) if (minmea_isfield(*endptr))
goto end; goto parse_error;
}
*va_arg(ap, int *) = value; *va_arg(ap, int *) = value;
} break; } break;
@ -170,17 +206,24 @@ bool minmea_scan(const char *sentence, const char *format, ...)
case 's': { // String value (char *). case 's': { // String value (char *).
char *buf = va_arg(ap, char *); char *buf = va_arg(ap, char *);
if (field) {
while (minmea_isfield(*field)) while (minmea_isfield(*field))
*buf++ = *field++; *buf++ = *field++;
}
*buf = '\0'; *buf = '\0';
} break; } break;
case 't': { // NMEA talker+sentence identifier (char *). case 't': { // NMEA talker+sentence identifier (char *).
// This field is always mandatory.
if (!field)
goto parse_error;
if (field[0] != '$') if (field[0] != '$')
goto end; goto parse_error;
for (int i=0; i<5; i++) for (int f=0; f<5; f++)
if (!minmea_isfield(field[1+i])) if (!minmea_isfield(field[1+f]))
goto end; goto parse_error;
char *buf = va_arg(ap, char *); char *buf = va_arg(ap, char *);
memcpy(buf, field+1, 5); memcpy(buf, field+1, 5);
@ -191,16 +234,18 @@ bool minmea_scan(const char *sentence, const char *format, ...)
struct minmea_date *date = va_arg(ap, struct minmea_date *); struct minmea_date *date = va_arg(ap, struct minmea_date *);
int d = -1, m = -1, y = -1; int d = -1, m = -1, y = -1;
if (field && minmea_isfield(*field)) {
// Always six digits. // Always six digits.
for (int i=0; i<6; i++) for (int f=0; f<6; f++)
if (!isdigit((unsigned char) field[i])) if (!isdigit((unsigned char) field[f]))
goto end_D; goto parse_error;
d = strtol((char[]) {field[0], field[1], '\0'}, NULL, 10); d = strtol((char[]) {field[0], field[1], '\0'}, NULL, 10);
m = strtol((char[]) {field[2], field[3], '\0'}, NULL, 10); m = strtol((char[]) {field[2], field[3], '\0'}, NULL, 10);
y = strtol((char[]) {field[4], field[5], '\0'}, NULL, 10); y = strtol((char[]) {field[4], field[5], '\0'}, NULL, 10);
}
end_D:
date->day = d; date->day = d;
date->month = m; date->month = m;
date->year = y; date->year = y;
@ -210,10 +255,12 @@ bool minmea_scan(const char *sentence, const char *format, ...)
struct minmea_time *time = va_arg(ap, struct minmea_time *); struct minmea_time *time = va_arg(ap, struct minmea_time *);
int h = -1, i = -1, s = -1, u = -1; int h = -1, i = -1, s = -1, u = -1;
if (field && minmea_isfield(*field)) {
// Minimum required: integer time. // Minimum required: integer time.
for (int i=0; i<6; i++) for (int f=0; f<6; f++)
if (!isdigit((unsigned char) field[i])) if (!isdigit((unsigned char) field[f]))
goto end_T; goto parse_error;
h = strtol((char[]) {field[0], field[1], '\0'}, NULL, 10); h = strtol((char[]) {field[0], field[1], '\0'}, NULL, 10);
i = strtol((char[]) {field[2], field[3], '\0'}, NULL, 10); i = strtol((char[]) {field[2], field[3], '\0'}, NULL, 10);
@ -232,8 +279,8 @@ bool minmea_scan(const char *sentence, const char *format, ...)
} else { } else {
u = 0; u = 0;
} }
}
end_T:
time->hours = h; time->hours = h;
time->minutes = i; time->minutes = i;
time->seconds = s; time->seconds = s;
@ -244,17 +291,16 @@ bool minmea_scan(const char *sentence, const char *format, ...)
} break; } break;
default: { // Unknown. default: { // Unknown.
goto end; goto parse_error;
} break; } break;
} }
// Advance to next field.
next_field(); next_field();
} }
result = true; result = true;
end: parse_error:
va_end(ap); va_end(ap);
return result; return result;
} }
@ -289,6 +335,10 @@ enum minmea_sentence_id minmea_sentence_id(const char *sentence)
return MINMEA_SENTENCE_GSA; return MINMEA_SENTENCE_GSA;
if (!strcmp(type+2, "GLL")) if (!strcmp(type+2, "GLL"))
return MINMEA_SENTENCE_GLL; return MINMEA_SENTENCE_GLL;
if (!strcmp(type+2, "GST"))
return MINMEA_SENTENCE_GST;
if (!strcmp(type+2, "GSV"))
return MINMEA_SENTENCE_GSV;
return MINMEA_UNKNOWN; return MINMEA_UNKNOWN;
} }
@ -305,20 +355,20 @@ bool minmea_parse_rmc(struct minmea_sentence_rmc *frame, const char *sentence)
type, type,
&frame->time, &frame->time,
&validity, &validity,
&frame->latitude, &frame->latitude_scale, &latitude_direction, &frame->latitude, &latitude_direction,
&frame->longitude, &frame->longitude_scale, &longitude_direction, &frame->longitude, &longitude_direction,
&frame->speed, &frame->speed_scale, &frame->speed,
&frame->course, &frame->course_scale, &frame->course,
&frame->date, &frame->date,
&frame->variation, &frame->variation_scale, &variation_direction)) &frame->variation, &variation_direction))
return false; return false;
if (strcmp(type+2, "RMC")) if (strcmp(type+2, "RMC"))
return false; return false;
frame->valid = (validity == 'A'); frame->valid = (validity == 'A');
frame->latitude *= latitude_direction; frame->latitude.value *= latitude_direction;
frame->longitude *= longitude_direction; frame->longitude.value *= longitude_direction;
frame->variation *= variation_direction; frame->variation.value *= variation_direction;
return true; return true;
} }
@ -333,20 +383,20 @@ bool minmea_parse_gga(struct minmea_sentence_gga *frame, const char *sentence)
if (!minmea_scan(sentence, "tTfdfdiiffcfci_", if (!minmea_scan(sentence, "tTfdfdiiffcfci_",
type, type,
&frame->time, &frame->time,
&frame->latitude, &frame->latitude_scale, &latitude_direction, &frame->latitude, &latitude_direction,
&frame->longitude, &frame->longitude_scale, &longitude_direction, &frame->longitude, &longitude_direction,
&frame->fix_quality, &frame->fix_quality,
&frame->satellites_tracked, &frame->satellites_tracked,
&frame->hdop, &frame->hdop_scale, &frame->hdop,
&frame->altitude, &frame->altitude_scale, &frame->altitude_units, &frame->altitude, &frame->altitude_units,
&frame->height, &frame->height_scale, &frame->height_units, &frame->height, &frame->height_units,
&frame->dgps_age)) &frame->dgps_age))
return false; return false;
if (strcmp(type+2, "GGA")) if (strcmp(type+2, "GGA"))
return false; return false;
frame->latitude *= latitude_direction; frame->latitude.value *= latitude_direction;
frame->longitude *= longitude_direction; frame->longitude.value *= longitude_direction;
return true; return true;
} }
@ -373,14 +423,9 @@ bool minmea_parse_gsa(struct minmea_sentence_gsa *frame, const char *sentence)
&frame->sats[10], &frame->sats[10],
&frame->sats[11], &frame->sats[11],
&frame->pdop, &frame->pdop,
&frame->pdop_scale,
&frame->hdop, &frame->hdop,
&frame->hdop_scale, &frame->vdop))
&frame->vdop,
&frame->vdop_scale
)){
return false; return false;
}
if (strcmp(type+2, "GSA")) if (strcmp(type+2, "GSA"))
return false; return false;
@ -411,21 +456,78 @@ bool minmea_parse_gll(struct minmea_sentence_gll *frame, const char *sentence)
return true; return true;
} }
bool minmea_parse_gst(struct minmea_sentence_gst *frame, const char *sentence)
{
// $GPGST,024603.00,3.2,6.6,4.7,47.3,5.8,5.6,22.0*58
char type[6];
if (!minmea_scan(sentence, "tTfffffff",
type,
&frame->time,
&frame->rms_deviation,
&frame->semi_major_deviation,
&frame->semi_minor_deviation,
&frame->semi_major_orientation,
&frame->latitude_error_deviation,
&frame->longitude_error_deviation,
&frame->altitude_error_deviation))
return false;
if (strcmp(type+2, "GST"))
return false;
return true;
}
bool minmea_parse_gsv(struct minmea_sentence_gsv *frame, const char *sentence)
{
// $GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,00,13,06,292,00*74
char type[6];
if (!minmea_scan(sentence, "tiiiiiiiiiiiiiiiiiii",
type,
&frame->total_msgs,
&frame->msg_nr,
&frame->total_sats,
&frame->sats[0].nr,
&frame->sats[0].elevation,
&frame->sats[0].azimuth,
&frame->sats[0].snr,
&frame->sats[1].nr,
&frame->sats[1].elevation,
&frame->sats[1].azimuth,
&frame->sats[1].snr,
&frame->sats[2].nr,
&frame->sats[2].elevation,
&frame->sats[2].azimuth,
&frame->sats[2].snr,
&frame->sats[3].nr,
&frame->sats[3].elevation,
&frame->sats[3].azimuth,
&frame->sats[3].snr
)) {
return false;
}
if (strcmp(type+2, "GSV"))
return false;
return true;
}
int minmea_gettimeofday(struct timeval *tv, const struct minmea_date *date, const struct minmea_time *time) int minmea_gettimeofday(struct timeval *tv, const struct minmea_date *date, const struct minmea_time *time)
{ {
if (date->year == -1 || time->hours == -1) if (date->year == -1 || time->hours == -1)
return -1; return -1;
struct tm tm; struct tm tm;
memset(&tm, 0, sizeof(tm));
tm.tm_year = 2000 + date->year - 1900; tm.tm_year = 2000 + date->year - 1900;
tm.tm_mon = date->month - 1; tm.tm_mon = date->month - 1;
tm.tm_mday = date->day; tm.tm_mday = date->day;
tm.tm_hour = time->hours; tm.tm_hour = time->hours;
tm.tm_min = time->minutes; tm.tm_min = time->minutes;
tm.tm_sec = time->seconds; tm.tm_sec = time->seconds;
tm.tm_isdst = 0;
time_t timestamp = timegm(&tm);
time_t timestamp = timegm(&tm);
if (timestamp != -1) { if (timestamp != -1) {
tv->tv_sec = timestamp; tv->tv_sec = timestamp;
tv->tv_usec = time->microseconds; tv->tv_usec = time->microseconds;

View File

@ -9,6 +9,8 @@
#ifndef MINMEA_H #ifndef MINMEA_H
#define MINMEA_H #define MINMEA_H
#define _BSD_SOURCE
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
@ -29,7 +31,14 @@ enum minmea_sentence_id {
MINMEA_SENTENCE_RMC, MINMEA_SENTENCE_RMC,
MINMEA_SENTENCE_GGA, MINMEA_SENTENCE_GGA,
MINMEA_SENTENCE_GSA, MINMEA_SENTENCE_GSA,
MINMEA_SENTENCE_GLL MINMEA_SENTENCE_GLL,
MINMEA_SENTENCE_GST,
MINMEA_SENTENCE_GSV,
};
struct minmea_float {
int_least32_t value;
int_least32_t scale;
}; };
struct minmea_date { struct minmea_date {
@ -48,23 +57,23 @@ struct minmea_time {
struct minmea_sentence_rmc { struct minmea_sentence_rmc {
struct minmea_time time; struct minmea_time time;
bool valid; bool valid;
int latitude, latitude_scale; struct minmea_float latitude;
int longitude, longitude_scale; struct minmea_float longitude;
int speed, speed_scale; struct minmea_float speed;
int course, course_scale; struct minmea_float course;
struct minmea_date date; struct minmea_date date;
int variation, variation_scale; struct minmea_float variation;
}; };
struct minmea_sentence_gga { struct minmea_sentence_gga {
struct minmea_time time; struct minmea_time time;
int latitude, latitude_scale; struct minmea_float latitude;
int longitude, longitude_scale; struct minmea_float longitude;
int fix_quality; int fix_quality;
int satellites_tracked; int satellites_tracked;
int hdop, hdop_scale; struct minmea_float hdop;
int altitude, altitude_scale; char altitude_units; struct minmea_float altitude; char altitude_units;
int height, height_scale; char height_units; struct minmea_float height; char height_units;
int dgps_age; int dgps_age;
}; };
@ -86,6 +95,18 @@ struct minmea_sentence_gll {
char status; char status;
char mode; char mode;
}; };
struct minmea_sentence_gst {
struct minmea_time time;
struct minmea_float rms_deviation;
struct minmea_float semi_major_deviation;
struct minmea_float semi_minor_deviation;
struct minmea_float semi_major_orientation;
struct minmea_float latitude_error_deviation;
struct minmea_float longitude_error_deviation;
struct minmea_float altitude_error_deviation;
};
enum minmea_gsa_mode { enum minmea_gsa_mode {
MINMEA_GPGSA_MODE_AUTO = 'A', MINMEA_GPGSA_MODE_AUTO = 'A',
MINMEA_GPGSA_MODE_FORCED = 'M', MINMEA_GPGSA_MODE_FORCED = 'M',
@ -101,9 +122,23 @@ struct minmea_sentence_gsa {
char mode; char mode;
int fix_type; int fix_type;
int sats[12]; int sats[12];
int pdop, pdop_scale; struct minmea_float pdop;
int hdop, hdop_scale; struct minmea_float hdop;
int vdop, vdop_scale; struct minmea_float vdop;
};
struct minmea_sat_info {
int nr;
int elevation;
int azimuth;
int snr;
};
struct minmea_sentence_gsv {
int total_msgs;
int msg_nr;
int total_sats;
struct minmea_sat_info sats[4];
}; };
/** /**
@ -141,6 +176,8 @@ bool minmea_parse_rmc(struct minmea_sentence_rmc *frame, const char *sentence);
bool minmea_parse_gga(struct minmea_sentence_gga *frame, const char *sentence); bool minmea_parse_gga(struct minmea_sentence_gga *frame, const char *sentence);
bool minmea_parse_gsa(struct minmea_sentence_gsa *frame, const char *sentence); bool minmea_parse_gsa(struct minmea_sentence_gsa *frame, const char *sentence);
bool minmea_parse_gll(struct minmea_sentence_gll *frame, const char *sentence); bool minmea_parse_gll(struct minmea_sentence_gll *frame, const char *sentence);
bool minmea_parse_gst(struct minmea_sentence_gst *frame, const char *sentence);
bool minmea_parse_gsv(struct minmea_sentence_gsv *frame, const char *sentence);
/** /**
* Convert GPS UTC date/time representation to a UNIX timestamp. * Convert GPS UTC date/time representation to a UNIX timestamp.
@ -150,40 +187,40 @@ int minmea_gettimeofday(struct timeval *tv, const struct minmea_date *date, cons
/** /**
* Rescale a fixed-point value to a different scale. Rounds towards zero. * Rescale a fixed-point value to a different scale. Rounds towards zero.
*/ */
static inline int minmea_rescale(int value, int from, int to) static inline int_least32_t minmea_rescale(struct minmea_float *f, int_least32_t new_scale)
{ {
if (from == 0) if (f->scale == 0)
return 0; return 0;
if (from == to) if (f->scale == new_scale)
return value; return f->value;
if (from > to) if (f->scale > new_scale)
return (value + ((value > 0) - (value < 0)) * from/to/2) / (from/to); return (f->value + ((f->value > 0) - (f->value < 0)) * f->scale/new_scale/2) / (f->scale/new_scale);
else else
return value * (to/from); return f->value * (new_scale/f->scale);
} }
/** /**
* Convert a fixed-point value to a floating-point value. * Convert a fixed-point value to a floating-point value.
* Returns NaN for "unknown" values. * Returns NaN for "unknown" values.
*/ */
static inline float minmea_float(int value, int scale) static inline float minmea_tofloat(struct minmea_float *f)
{ {
if (scale == 0) if (f->scale == 0)
return NAN; return NAN;
return (float) value / (float) scale; return (float) f->value / (float) f->scale;
} }
/** /**
* Convert a raw coordinate to a floating point DD.DDD... value. * Convert a raw coordinate to a floating point DD.DDD... value.
* Returns NaN for "unknown" values. * Returns NaN for "unknown" values.
*/ */
static inline float minmea_coord(int value, int scale) static inline float minmea_tocoord(struct minmea_float *f)
{ {
if (scale == 0) if (f->scale == 0)
return NAN; return NAN;
int degrees = value / (scale * 100); int_least32_t degrees = f->value / (f->scale * 100);
int minutes = value % (scale * 100); int_least32_t minutes = f->value % (f->scale * 100);
return (float) degrees + (float) minutes / (60 * scale); return (float) degrees + (float) minutes / (60 * f->scale);
} }
#ifdef __cplusplus #ifdef __cplusplus

431
tests.c
View File

@ -6,6 +6,8 @@
* published by Sam Hocevar. See the COPYING file for more details. * published by Sam Hocevar. See the COPYING file for more details.
*/ */
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <math.h> #include <math.h>
@ -32,6 +34,7 @@ static const char *valid_sequences[] = {
"$GPGLL,5106.94086,N,01701.51680,E,123204.00,A,A*63", "$GPGLL,5106.94086,N,01701.51680,E,123204.00,A,A*63",
"$GPRMC,123205.00,A,5106.94085,N,01701.51689,E,0.016,,280214,,,A*7B", "$GPRMC,123205.00,A,5106.94085,N,01701.51689,E,0.016,,280214,,,A*7B",
"$GPVTG,,T,,M,0.016,N,0.030,K,A*27", "$GPVTG,,T,,M,0.016,N,0.030,K,A*27",
"$GPGST,024603.00,3.2,6.6,4.7,47.3,5.8,5.6,22.0*58",
NULL, NULL,
}; };
@ -61,7 +64,7 @@ END_TEST
START_TEST(test_minmea_scan_c) START_TEST(test_minmea_scan_c)
{ {
char ch; char ch, extra;
ck_assert(minmea_scan("A,123.45", "c", &ch) == true); ck_assert(minmea_scan("A,123.45", "c", &ch) == true);
ck_assert_int_eq(ch, 'A'); ck_assert_int_eq(ch, 'A');
@ -71,6 +74,19 @@ START_TEST(test_minmea_scan_c)
ck_assert(minmea_scan(",123.45", "c", &ch) == true); ck_assert(minmea_scan(",123.45", "c", &ch) == true);
ck_assert_int_eq(ch, '\0'); ck_assert_int_eq(ch, '\0');
ck_assert(minmea_scan("A,B", "cc", &ch, &extra) == true);
ck_assert_int_eq(ch, 'A');
ck_assert_int_eq(extra, 'B');
ck_assert(minmea_scan("C", "cc", &ch, &extra) == false);
ck_assert(minmea_scan("D", "c;c", &ch, &extra) == true);
ck_assert_int_eq(ch, 'D');
ck_assert_int_eq(extra, '\0');
ck_assert(minmea_scan("E,F", "c;c", &ch, &extra) == true);
ck_assert_int_eq(ch, 'E');
ck_assert_int_eq(extra, 'F');
} }
END_TEST END_TEST
@ -96,41 +112,105 @@ END_TEST
START_TEST(test_minmea_scan_f) START_TEST(test_minmea_scan_f)
{ {
int value, scale; struct minmea_float f;
ck_assert(minmea_scan("-", "f", &value, &scale) == false); ck_assert(minmea_scan("-", "f", &f) == false);
ck_assert(minmea_scan("10-", "f", &value, &scale) == false); ck_assert(minmea_scan("10-", "f", &f) == false);
ck_assert(minmea_scan("+-10", "f", &value, &scale) == false); ck_assert(minmea_scan("+-10", "f", &f) == false);
ck_assert(minmea_scan("12..45", "f", &value, &scale) == false); ck_assert(minmea_scan("12..45", "f", &f) == false);
ck_assert(minmea_scan("blah", "f", &value, &scale) == false); ck_assert(minmea_scan("blah", "f", &f) == false);
ck_assert(minmea_scan("12.3.4", "f", &value, &scale) == false); ck_assert(minmea_scan("12.3.4", "f", &f) == false);
ck_assert(minmea_scan(",", "f", &value, &scale) == true); ck_assert(minmea_scan(",", "f", &f) == true);
ck_assert_int_eq(scale, 0); ck_assert_int_eq(f.scale, 0);
ck_assert(minmea_scan("", "f", &value, &scale) == true); ck_assert(minmea_scan("", "f", &f) == true);
ck_assert_int_eq(scale, 0); ck_assert_int_eq(f.scale, 0);
ck_assert(minmea_scan("15.345", "f", &value, &scale) == true); ck_assert(minmea_scan("42", "f", &f) == true);
ck_assert_int_eq(value, 15345); ck_assert_int_eq(f.value, 42);
ck_assert_int_eq(scale, 1000); ck_assert_int_eq(f.scale, 1);
ck_assert(minmea_scan("-1.23,V", "f", &value, &scale) == true); ck_assert(minmea_scan("15.345", "f", &f) == true);
ck_assert_int_eq(value, -123); ck_assert_int_eq(f.value, 15345);
ck_assert_int_eq(scale, 100); ck_assert_int_eq(f.scale, 1000);
/* some GPS units have absurdly big precision. handle whatever int handles. */ ck_assert(minmea_scan("-1.23,V", "f", &f) == true);
ck_assert(minmea_scan("5106.94091", "f", &value, &scale) == true); ck_assert_int_eq(f.value, -123);
ck_assert_int_eq(value, 510694091); ck_assert_int_eq(f.scale, 100);
ck_assert_int_eq(scale, 100000);
/* for now we support +-180 degrees with 5 decimal digits; anything /* the guaranteed range is 32 bits which translates to +-180 degrees
* more will overflow. */ * with 5 decimal digits. make sure we support that. */
ck_assert(minmea_scan("18000.00000", "f", &value, &scale) == true); ck_assert(minmea_scan("18000.00000", "f", &f) == true);
ck_assert_int_eq(value, 1800000000); ck_assert_int_eq(f.value, 1800000000);
ck_assert_int_eq(scale, 100000); ck_assert_int_eq(f.scale, 100000);
ck_assert(minmea_scan("-18000.00000", "f", &value, &scale) == true); ck_assert(minmea_scan("-18000.00000", "f", &f) == true);
ck_assert_int_eq(value, -1800000000); ck_assert_int_eq(f.value, -1800000000);
ck_assert_int_eq(scale, 100000); ck_assert_int_eq(f.scale, 100000);
if (sizeof(int_least32_t) == 4) {
/* fits in 32 bits */
ck_assert(minmea_scan("2147483647", "f", &f) == true);
ck_assert_int_eq(f.value, 2147483647);
ck_assert_int_eq(f.scale, 1);
/* doesn't fit, truncate precision */
ck_assert(minmea_scan("2147483.648", "f", &f) == true);
ck_assert_int_eq(f.value, 214748364);
ck_assert_int_eq(f.scale, 100);
/* doesn't fit, bail out */
ck_assert(minmea_scan("2147483648", "f", &f) == false);
} else if (sizeof(int_least32_t) == 8) {
/* fits in 64 bits */
ck_assert(minmea_scan("9223372036854775807", "f", &f) == true);
ck_assert_int_eq(f.value, 9223372036854775807);
ck_assert_int_eq(f.scale, 1);
/* doesn't fit, truncate precision */
ck_assert(minmea_scan("9223372036854775.808", "f", &f) == true);
ck_assert_int_eq(f.value, 922337203685477580);
ck_assert_int_eq(f.scale, 100);
/* doesn't fit, bail out */
ck_assert(minmea_scan("9223372036854775808", "f", &f) == false);
} else {
ck_abort_msg("your platform is esoteric. please fix this unit test.");
}
/* optional f.values */
ck_assert(minmea_scan("foo", "_;f", &f) == true);
ck_assert_int_eq(f.scale, 0);
ck_assert(minmea_scan("foo,", "_;f", &f) == true);
ck_assert_int_eq(f.scale, 0);
ck_assert(minmea_scan("foo,12.3", "_;f", &f) == true);
ck_assert_int_eq(f.value, 123);
ck_assert_int_eq(f.scale, 10);
}
END_TEST
START_TEST(test_minmea_scan_i)
{
int value, extra;
// valid parses
ck_assert(minmea_scan("14", "i", &value) == true);
ck_assert_int_eq(value, 14);
ck_assert(minmea_scan("-1234", "i", &value) == true);
ck_assert_int_eq(value, -1234);
// empty field
ck_assert(minmea_scan("", "i", &value) == true);
ck_assert_int_eq(value, 0);
// invalid value
ck_assert(minmea_scan("foo", "i", &value) == false);
// missing field
ck_assert(minmea_scan("41", "ii", &value, &extra) == false);
/* optional values */
ck_assert(minmea_scan("10", "i;i", &value, &extra) == true);
ck_assert_int_eq(value, 10);
ck_assert(minmea_scan("20,30", "i;i", &value, &extra) == true);
ck_assert_int_eq(value, 20);
ck_assert_int_eq(extra, 30);
ck_assert(minmea_scan("42,foo", "i;i", &value, &extra) == false);
} }
END_TEST END_TEST
@ -138,9 +218,16 @@ START_TEST(test_minmea_scan_s)
{ {
char value[MINMEA_MAX_LENGTH]; char value[MINMEA_MAX_LENGTH];
ck_assert(minmea_scan(",bar,baz", "s", value) == true);
ck_assert_str_eq(value, "");
ck_assert(minmea_scan("foo,bar,baz", "s", value) == true); ck_assert(minmea_scan("foo,bar,baz", "s", value) == true);
ck_assert_str_eq(value, "foo"); ck_assert_str_eq(value, "foo");
ck_assert(minmea_scan(",bar,baz", "s", value) == true);
ck_assert(minmea_scan("dummy", "_;s", value) == true);
ck_assert_str_eq(value, "");
ck_assert(minmea_scan("dummy,foo", "_;s", value) == true);
ck_assert_str_eq(value, "foo");
ck_assert(minmea_scan("dummy,", "_;s", value) == true);
ck_assert_str_eq(value, ""); ck_assert_str_eq(value, "");
} }
END_TEST END_TEST
@ -164,6 +251,9 @@ START_TEST(test_minmea_scan_D)
{ {
struct minmea_date date; struct minmea_date date;
ck_assert(minmea_scan("$GPXXX,3112", "_D", &date) == false);
ck_assert(minmea_scan("$GPXXX,foobar", "_D", &date) == false);
ck_assert(minmea_scan("$GPXXX,311299", "_D", &date) == true); ck_assert(minmea_scan("$GPXXX,311299", "_D", &date) == true);
ck_assert_int_eq(date.day, 31); ck_assert_int_eq(date.day, 31);
ck_assert_int_eq(date.month, 12); ck_assert_int_eq(date.month, 12);
@ -180,6 +270,9 @@ START_TEST(test_minmea_scan_T)
{ {
struct minmea_time time; struct minmea_time time;
ck_assert(minmea_scan("$GPXXX,2359", "_T", &time) == false);
ck_assert(minmea_scan("$GPXXX,foobar", "_T", &time) == false);
ck_assert(minmea_scan("$GPXXX,235960", "_T", &time) == true); ck_assert(minmea_scan("$GPXXX,235960", "_T", &time) == true);
ck_assert_int_eq(time.hours, 23); ck_assert_int_eq(time.hours, 23);
ck_assert_int_eq(time.minutes, 59); ck_assert_int_eq(time.minutes, 59);
@ -205,42 +298,42 @@ START_TEST(test_minmea_scan_complex1)
const char *sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n"; const char *sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
char type[6]; char type[6];
struct minmea_time time; struct minmea_time time;
int latitude, latitude_scale, latitude_direction; struct minmea_float latitude; int latitude_direction;
int longitude, longitude_scale, longitude_direction; struct minmea_float longitude; int longitude_direction;
int fix_quality; int fix_quality;
int satellites; int satellites;
int hdop, hdop_scale; struct minmea_float hdop;
int altitude, altitude_scale; char altitude_units; struct minmea_float altitude; char altitude_units;
int height, height_scale; char height_units; struct minmea_float height; char height_units;
ck_assert(minmea_scan(sentence, "tTfdfdiiffcfc__", ck_assert(minmea_scan(sentence, "tTfdfdiiffcfc__",
type, type,
&time, &time,
&latitude, &latitude_scale, &latitude_direction, &latitude, &latitude_direction,
&longitude, &longitude_scale, &longitude_direction, &longitude, &longitude_direction,
&fix_quality, &fix_quality,
&satellites, &satellites,
&hdop, &hdop_scale, &hdop,
&altitude, &altitude_scale, &altitude_units, &altitude, &altitude_units,
&height, &height_scale, &height_units) == true); &height, &height_units) == true);
ck_assert_str_eq(type, "GPGGA"); ck_assert_str_eq(type, "GPGGA");
ck_assert_int_eq(time.hours, 12); ck_assert_int_eq(time.hours, 12);
ck_assert_int_eq(time.minutes, 35); ck_assert_int_eq(time.minutes, 35);
ck_assert_int_eq(time.seconds, 19); ck_assert_int_eq(time.seconds, 19);
ck_assert_int_eq(latitude, 4807038); ck_assert_int_eq(latitude.value, 4807038);
ck_assert_int_eq(latitude_scale, 1000); ck_assert_int_eq(latitude.scale, 1000);
ck_assert_int_eq(latitude_direction, 1); ck_assert_int_eq(latitude_direction, 1);
ck_assert_int_eq(longitude, 1131000); ck_assert_int_eq(longitude.value, 1131000);
ck_assert_int_eq(longitude_scale, 1000); ck_assert_int_eq(longitude.scale, 1000);
ck_assert_int_eq(longitude_direction, 1); ck_assert_int_eq(longitude_direction, 1);
ck_assert_int_eq(fix_quality, 1); ck_assert_int_eq(fix_quality, 1);
ck_assert_int_eq(satellites, 8); ck_assert_int_eq(satellites, 8);
ck_assert_int_eq(hdop, 9); ck_assert_int_eq(hdop.value, 9);
ck_assert_int_eq(hdop_scale, 10); ck_assert_int_eq(hdop.scale, 10);
ck_assert_int_eq(altitude, 5454); ck_assert_int_eq(altitude.value, 5454);
ck_assert_int_eq(altitude_scale, 10); ck_assert_int_eq(altitude.scale, 10);
ck_assert_int_eq(altitude_units, 'M'); ck_assert_int_eq(altitude_units, 'M');
ck_assert_int_eq(height, 469); ck_assert_int_eq(height.value, 469);
ck_assert_int_eq(height_scale, 10); ck_assert_int_eq(height.scale, 10);
ck_assert_int_eq(height_units, 'M'); ck_assert_int_eq(height_units, 'M');
} }
@ -251,57 +344,96 @@ START_TEST(test_minmea_scan_complex2)
const char *sentence = "$GPBWC,081837,,,,,,T,,M,,N,*13"; const char *sentence = "$GPBWC,081837,,,,,,T,,M,,N,*13";
char type[6]; char type[6];
struct minmea_time time; struct minmea_time time;
int latitude, latitude_scale, latitude_direction; struct minmea_float latitude; int latitude_direction;
int longitude, longitude_scale, longitude_direction; struct minmea_float longitude; int longitude_direction;
int bearing_true, bearing_true_scale; char bearing_true_mark; struct minmea_float bearing_true; char bearing_true_mark;
int bearing_magnetic, bearing_magnetic_scale; char bearing_magnetic_mark; struct minmea_float bearing_magnetic; char bearing_magnetic_mark;
int distance, distance_scale; char distance_units; struct minmea_float distance; char distance_units;
char name[MINMEA_MAX_LENGTH]; char name[MINMEA_MAX_LENGTH];
ck_assert(minmea_scan(sentence, "tTfdfdfcfcfcs", ck_assert(minmea_scan(sentence, "tTfdfdfcfcfcs",
type, type,
&time, &time,
&latitude, &latitude_scale, &latitude_direction, &latitude, &latitude_direction,
&longitude, &longitude_scale, &longitude_direction, &longitude, &longitude_direction,
&bearing_true, &bearing_true_scale, &bearing_true_mark, &bearing_true, &bearing_true_mark,
&bearing_magnetic, &bearing_magnetic_scale, &bearing_magnetic_mark, &bearing_magnetic, &bearing_magnetic_mark,
&distance, &distance_scale, &distance_units, &distance, &distance_units,
name) == true); name) == true);
ck_assert_str_eq(type, "GPBWC"); ck_assert_str_eq(type, "GPBWC");
ck_assert_int_eq(time.hours, 8); ck_assert_int_eq(time.hours, 8);
ck_assert_int_eq(time.minutes, 18); ck_assert_int_eq(time.minutes, 18);
ck_assert_int_eq(time.seconds, 37); ck_assert_int_eq(time.seconds, 37);
ck_assert_int_eq(latitude_scale, 0); ck_assert_int_eq(latitude.scale, 0);
ck_assert_int_eq(latitude_direction, 0); ck_assert_int_eq(latitude_direction, 0);
ck_assert_int_eq(longitude_scale, 0); ck_assert_int_eq(longitude.scale, 0);
ck_assert_int_eq(longitude_direction, 0); ck_assert_int_eq(longitude_direction, 0);
ck_assert_int_eq(bearing_true_scale, 0); ck_assert_int_eq(bearing_true.scale, 0);
ck_assert_int_eq(bearing_true_mark, 'T'); ck_assert_int_eq(bearing_true_mark, 'T');
ck_assert_int_eq(bearing_magnetic_scale, 0); ck_assert_int_eq(bearing_magnetic.scale, 0);
ck_assert_int_eq(bearing_magnetic_mark, 'M'); ck_assert_int_eq(bearing_magnetic_mark, 'M');
ck_assert_int_eq(distance_scale, 0); ck_assert_int_eq(distance.scale, 0);
ck_assert_int_eq(distance_units, 'N'); ck_assert_int_eq(distance_units, 'N');
ck_assert_str_eq(name, ""); ck_assert_str_eq(name, "");
} }
END_TEST END_TEST
START_TEST(test_minmea_scan_complex3)
{
const char *sentence = "$GPGST,024603.00,3.2,6.6,4.7,47.3,5.8,5.6,22.0*58";
char type[6];
struct minmea_time time;
struct minmea_float rms_deviation;
struct minmea_float semi_major_deviation;
struct minmea_float semi_minor_deviation;
struct minmea_float semi_major_orientation;
struct minmea_float latitude_error_deviation;
struct minmea_float longitude_error_deviation;
struct minmea_float altitude_error_deviation;
ck_assert(minmea_scan(sentence, "tTfffffff",
type,
&time,
&rms_deviation,
&semi_major_deviation,
&semi_minor_deviation,
&semi_major_orientation,
&latitude_error_deviation,
&longitude_error_deviation,
&altitude_error_deviation) == true);
ck_assert_str_eq(type, "GPGST");
ck_assert_int_eq(time.hours, 2);
ck_assert_int_eq(time.minutes, 46);
ck_assert_int_eq(time.seconds, 3);
ck_assert_int_eq(time.microseconds, 0);
ck_assert_int_eq(rms_deviation.value, 32);
ck_assert_int_eq(rms_deviation.scale, 10);
ck_assert_int_eq(semi_major_deviation.value, 66);
ck_assert_int_eq(semi_major_deviation.scale, 10);
ck_assert_int_eq(semi_minor_deviation.value, 47);
ck_assert_int_eq(semi_minor_deviation.scale, 10);
ck_assert_int_eq(semi_major_orientation.value, 473);
ck_assert_int_eq(semi_major_orientation.scale, 10);
ck_assert_int_eq(latitude_error_deviation.value, 58);
ck_assert_int_eq(latitude_error_deviation.scale, 10);
ck_assert_int_eq(longitude_error_deviation.value, 56);
ck_assert_int_eq(longitude_error_deviation.scale, 10);
ck_assert_int_eq(altitude_error_deviation.value, 220);
ck_assert_int_eq(altitude_error_deviation.scale, 10);
}
END_TEST
START_TEST(test_minmea_parse_rmc1) START_TEST(test_minmea_parse_rmc1)
{ {
const char *sentence = "$GPRMC,081836.75,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E"; const char *sentence = "$GPRMC,081836.75,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E";
struct minmea_sentence_rmc frame = {}; struct minmea_sentence_rmc frame = {};
struct minmea_sentence_rmc expected = { static const struct minmea_sentence_rmc expected = {
.time = { 8, 18, 36, 750000 }, .time = { 8, 18, 36, 750000 },
.valid = true, .valid = true,
.latitude = -375165, .latitude = { -375165, 100 },
.latitude_scale = 100, .longitude = { 1450736, 100 },
.longitude = 1450736, .speed = { 0, 10 },
.longitude_scale = 100, .course = { 3600, 10 },
.speed = 0,
.speed_scale = 10,
.course = 3600,
.course_scale = 10,
.date = { 13, 9, 98 }, .date = { 13, 9, 98 },
.variation = 113, .variation = { 113, 10 },
.variation_scale = 10,
}; };
ck_assert(minmea_check(sentence) == true); ck_assert(minmea_check(sentence) == true);
ck_assert(minmea_parse_rmc(&frame, sentence) == true); ck_assert(minmea_parse_rmc(&frame, sentence) == true);
@ -313,13 +445,11 @@ START_TEST(test_minmea_parse_rmc2)
{ {
const char *sentence = "$GPRMC,,A,3751.65,N,14507.36,W,,,,,"; const char *sentence = "$GPRMC,,A,3751.65,N,14507.36,W,,,,,";
struct minmea_sentence_rmc frame = {}; struct minmea_sentence_rmc frame = {};
struct minmea_sentence_rmc expected = { static const struct minmea_sentence_rmc expected = {
.time = { -1, -1, -1, -1 }, .time = { -1, -1, -1, -1 },
.valid = true, .valid = true,
.latitude = 375165, .latitude = { 375165, 100 },
.latitude_scale = 100, .longitude = { -1450736, 100 },
.longitude = -1450736,
.longitude_scale = 100,
.date = { -1, -1, -1 }, .date = { -1, -1, -1 },
}; };
ck_assert(minmea_check(sentence) == true); ck_assert(minmea_check(sentence) == true);
@ -332,21 +462,16 @@ START_TEST(test_minmea_parse_gga1)
{ {
const char *sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47"; const char *sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47";
struct minmea_sentence_gga frame = {}; struct minmea_sentence_gga frame = {};
struct minmea_sentence_gga expected = { static const struct minmea_sentence_gga expected = {
.time = { 12, 35, 19, 0 }, .time = { 12, 35, 19, 0 },
.latitude = 4807038, .latitude = { 4807038, 1000 },
.latitude_scale = 1000, .longitude = { 1131000, 1000 },
.longitude = 1131000,
.longitude_scale = 1000,
.fix_quality = 1, .fix_quality = 1,
.satellites_tracked = 8, .satellites_tracked = 8,
.hdop = 9, .hdop = { 9, 10 },
.hdop_scale = 10, .altitude = { 5454, 10 },
.altitude = 5454,
.altitude_scale = 10,
.altitude_units = 'M', .altitude_units = 'M',
.height = 469, .height = { 469, 10 },
.height_scale = 10,
.height_units = 'M', .height_units = 'M',
.dgps_age = 0, .dgps_age = 0,
}; };
@ -356,20 +481,37 @@ START_TEST(test_minmea_parse_gga1)
} }
END_TEST END_TEST
START_TEST(test_minmea_parse_gst1)
{
const char *sentence = "$GPGST,024603.00,3.2,6.6,4.7,47.3,5.8,5.6,22.0*58";
struct minmea_sentence_gst frame = {};
struct minmea_sentence_gst expected = {
.time = { 2, 46, 3, 0 },
.rms_deviation = { 32, 10 },
.semi_major_deviation = { 66, 10 },
.semi_minor_deviation = { 47, 10 },
.semi_major_orientation = { 473, 10 },
.latitude_error_deviation = { 58, 10 },
.longitude_error_deviation = { 56, 10 },
.altitude_error_deviation = { 220, 10 },
};
ck_assert(minmea_check(sentence) == true);
ck_assert(minmea_parse_gst(&frame, sentence) == true);
ck_assert(!memcmp(&frame, &expected, sizeof(frame)));
}
END_TEST
START_TEST(test_minmea_parse_gsa1) START_TEST(test_minmea_parse_gsa1)
{ {
const char *sentence = "$GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39"; const char *sentence = "$GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39";
struct minmea_sentence_gsa frame = {}; struct minmea_sentence_gsa frame = {};
struct minmea_sentence_gsa expected = { static const struct minmea_sentence_gsa expected = {
.mode = MINMEA_GPGSA_MODE_AUTO, .mode = MINMEA_GPGSA_MODE_AUTO,
.fix_type = MINMEA_GPGSA_FIX_3D, .fix_type = MINMEA_GPGSA_FIX_3D,
.sats = { 4, 5, 0, 9, 12, 0, 0, 24, 0, 0, 0, 0 }, .sats = { 4, 5, 0, 9, 12, 0, 0, 24, 0, 0, 0, 0 },
.pdop = 25, .pdop = { 25, 10 },
.pdop_scale = 10, .hdop = { 13, 10 },
.hdop = 13, .vdop = { 21, 10 },
.hdop_scale = 10,
.vdop = 21,
.vdop_scale = 10
}; };
ck_assert(minmea_check(sentence) == true); ck_assert(minmea_check(sentence) == true);
ck_assert(minmea_parse_gsa(&frame, sentence) == true); ck_assert(minmea_parse_gsa(&frame, sentence) == true);
@ -400,6 +542,47 @@ START_TEST(test_minmea_parse_gpgll)
} }
END_TEST END_TEST
START_TEST(test_minmea_parse_gsv1)
{
const char *sentence = "$GPGSV,3,3,11,22,42,067,42,24,14,311,43,27,05,244,00,,,,*4D";
struct minmea_sentence_gsv frame = {};
static const struct minmea_sentence_gsv expected = {
.total_msgs = 3,
.msg_nr = 3,
.total_sats = 11,
.sats = {
{
.nr = 22,
.elevation = 42,
.azimuth = 67,
.snr = 42
},
{
.nr = 24,
.elevation = 14,
.azimuth = 311,
.snr = 43
},
{
.nr = 27,
.elevation = 5,
.azimuth = 244,
.snr = 0
},
{
.nr = 0,
.elevation = 0,
.azimuth = 0,
.snr = 0
}
}
};
ck_assert(minmea_check(sentence) == true);
ck_assert(minmea_parse_gsv(&frame, sentence) == true);
ck_assert(!memcmp(&frame, &expected, sizeof(frame)));
}
END_TEST
START_TEST(test_minmea_usage1) START_TEST(test_minmea_usage1)
{ {
const char *sentences[] = { const char *sentences[] = {
@ -407,6 +590,7 @@ START_TEST(test_minmea_usage1)
"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47", "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47",
"$GNGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1", "$GNGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1",
"$GPGLL,3723.2475,N,12158.3416,W,161229.487,A,A*41", "$GPGLL,3723.2475,N,12158.3416,W,161229.487,A,A*41",
"$GPGST,024603.00,3.2,6.6,4.7,47.3,5.8,5.6,22.0*58",
NULL, NULL,
}; };
@ -432,6 +616,10 @@ START_TEST(test_minmea_usage1)
ck_assert(minmea_parse_gll(&frame, *sentence) == true); ck_assert(minmea_parse_gll(&frame, *sentence) == true);
} break; } break;
case MINMEA_SENTENCE_GST: {
struct minmea_sentence_gst frame;
ck_assert(minmea_parse_gst(&frame, *sentence) == true);
} break;
default: { default: {
} break; } break;
@ -466,39 +654,46 @@ END_TEST
START_TEST(test_minmea_rescale) START_TEST(test_minmea_rescale)
{ {
/* basic and edge cases. */ /* basic and edge cases. */
ck_assert_int_eq(minmea_rescale(42, 0, 3), 0); ck_assert_int_eq(minmea_rescale(&(struct minmea_float) { 42, 0 }, 3), 0);
ck_assert_int_eq(minmea_rescale(1234, 10, 1), 123); ck_assert_int_eq(minmea_rescale(&(struct minmea_float) { 1234, 10 }, 1), 123);
ck_assert_int_eq(minmea_rescale(1235, 10, 1), 124); ck_assert_int_eq(minmea_rescale(&(struct minmea_float) { 1235, 10 }, 1), 124);
ck_assert_int_eq(minmea_rescale(1234, 10, 1000), 123400); ck_assert_int_eq(minmea_rescale(&(struct minmea_float) { 1234, 10 }, 1000), 123400);
/* round towards zero. */ /* round towards zero. */
ck_assert_int_eq(minmea_rescale(-1234, 10, 1), -123); ck_assert_int_eq(minmea_rescale(&(struct minmea_float) { -1234, 10 }, 1), -123);
ck_assert_int_eq(minmea_rescale(-1235, 10, 1), -124); ck_assert_int_eq(minmea_rescale(&(struct minmea_float) { -1235, 10 }, 1), -124);
ck_assert_int_eq(minmea_rescale(-1236, 10, 1), -124); ck_assert_int_eq(minmea_rescale(&(struct minmea_float) { -1236, 10 }, 1), -124);
/* shouldn't overflow on large numbers. */ /* shouldn't overflow on large numbers. */
ck_assert_int_eq(minmea_rescale(510693608, 100000, 10000), 51069361); ck_assert_int_eq(minmea_rescale(&(struct minmea_float) { 510693608, 100000 }, 10000), 51069361);
} }
END_TEST END_TEST
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wfloat-equal"
/* The float values used in tests should be exactly representable under IEEE754;
* false negatives will occur otherwise. */
START_TEST(test_minmea_float) START_TEST(test_minmea_float)
{ {
ck_assert(isnan(minmea_float(42, 0))); ck_assert(isnan(minmea_tofloat(&(struct minmea_float) { 42, 0 })));
ck_assert(minmea_float(7, 1) == 7.0); ck_assert(minmea_tofloat(&(struct minmea_float) { 7, 1}) == 7.0);
ck_assert(minmea_float(-200, 100) == -2.0); ck_assert(minmea_tofloat(&(struct minmea_float) { -200, 100}) == -2.0);
ck_assert(minmea_float(15, 10) == 1.5); ck_assert(minmea_tofloat(&(struct minmea_float) { 15, 10}) == 1.5);
} }
END_TEST END_TEST
START_TEST(test_minmea_coord) START_TEST(test_minmea_coord)
{ {
ck_assert(isnan(minmea_coord(42, 0))); ck_assert(isnan(minmea_tocoord(&(struct minmea_float) { 42, 0 })));
ck_assert(minmea_coord(4200, 1) == 42.0); ck_assert(minmea_tocoord(&(struct minmea_float) { 4200, 1 }) == 42.0);
ck_assert(minmea_coord(420000, 100) == 42.0); ck_assert(minmea_tocoord(&(struct minmea_float) { 420000, 100 }) == 42.0);
ck_assert(minmea_coord(423000, 100) == 42.5); ck_assert(minmea_tocoord(&(struct minmea_float) { 423000, 100 }) == 42.5);
} }
END_TEST END_TEST
#pragma GCC diagnostic pop
Suite *minmea_suite(void) Suite *minmea_suite(void)
{ {
Suite *s = suite_create ("minmea"); Suite *s = suite_create ("minmea");
@ -511,12 +706,14 @@ Suite *minmea_suite(void)
tcase_add_test(tc_scan, test_minmea_scan_c); tcase_add_test(tc_scan, test_minmea_scan_c);
tcase_add_test(tc_scan, test_minmea_scan_d); tcase_add_test(tc_scan, test_minmea_scan_d);
tcase_add_test(tc_scan, test_minmea_scan_f); tcase_add_test(tc_scan, test_minmea_scan_f);
tcase_add_test(tc_scan, test_minmea_scan_i);
tcase_add_test(tc_scan, test_minmea_scan_s); tcase_add_test(tc_scan, test_minmea_scan_s);
tcase_add_test(tc_scan, test_minmea_scan_t); tcase_add_test(tc_scan, test_minmea_scan_t);
tcase_add_test(tc_scan, test_minmea_scan_D); tcase_add_test(tc_scan, test_minmea_scan_D);
tcase_add_test(tc_scan, test_minmea_scan_T); tcase_add_test(tc_scan, test_minmea_scan_T);
tcase_add_test(tc_scan, test_minmea_scan_complex1); tcase_add_test(tc_scan, test_minmea_scan_complex1);
tcase_add_test(tc_scan, test_minmea_scan_complex2); tcase_add_test(tc_scan, test_minmea_scan_complex2);
tcase_add_test(tc_scan, test_minmea_scan_complex3);
suite_add_tcase(s, tc_scan); suite_add_tcase(s, tc_scan);
TCase *tc_parse = tcase_create("minmea_parse"); TCase *tc_parse = tcase_create("minmea_parse");
@ -525,6 +722,8 @@ Suite *minmea_suite(void)
tcase_add_test(tc_parse, test_minmea_parse_gga1); tcase_add_test(tc_parse, test_minmea_parse_gga1);
tcase_add_test(tc_parse, test_minmea_parse_gsa1); tcase_add_test(tc_parse, test_minmea_parse_gsa1);
tcase_add_test(tc_parse, test_minmea_parse_gpgll); tcase_add_test(tc_parse, test_minmea_parse_gpgll);
tcase_add_test(tc_parse, test_minmea_parse_gst1);
tcase_add_test(tc_parse, test_minmea_parse_gsv1);
suite_add_tcase(s, tc_parse); suite_add_tcase(s, tc_parse);
TCase *tc_usage = tcase_create("minmea_usage"); TCase *tc_usage = tcase_create("minmea_usage");