initial commit

This commit is contained in:
Kosma Moczek 2014-02-14 15:22:18 +01:00
commit 56177f0e1c
7 changed files with 1090 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
*.o
*~
minmea
tests
example

23
Makefile Normal file
View File

@ -0,0 +1,23 @@
CFLAGS = -g -Wall -Wextra -Werror
LDFLAGS = -lcheck
all: scan-build test example
@echo "+++ All good."""
test: tests
@echo "+++ Running Check test suite..."
./tests
scan-build: clean
@echo "+++ Running Clang Static Analyzer..."
scan-build $(MAKE) tests
clean:
$(RM) tests *.o
tests: tests.o minmea.o
example: example.o minmea.o
tests.o: tests.c minmea.h
minmea.o: minmea.c minmea.h
.PHONY: all test scan-build clean

78
README.md Normal file
View File

@ -0,0 +1,78 @@
# minmea, the lightweight GPS NMEA 0183 parser library
Minmea is a minimalistic GPS parser library intended for resource-constrained
platforms, especially microcontrollers and other embedded systems.
## Features
* Written in ISO C99.
* No dynamic memory allocation.
* No floating point usage in the core library.
* Supports both fixed and floating point values.
* One source file and one header - can't get any simpler.
* Easily extendable to support new sentences.
* Complete with a test suite and static analysis.
## Supported sentences
* ``$GPRMC``
* ``$GPGGA``
Adding support for more sentences is trivial; see ``minmea.c`` source.
## Example
```c
char line[MINMEA_MAX_LENGTH];
while (fgets(line, sizeof(line), stdin) != NULL) {
printf("%s", line);
switch (minmea_type(line)) {
case MINMEA_GPRMC: {
struct minmea_gprmc frame;
if (minmea_parse_gprmc(&frame, line)) {
printf("+++ raw coordinates and speed: (%d/%d,%d/%d) %d/%d\n",
frame.latitude, frame.latitude_scale,
frame.longitude, frame.longitude_scale,
frame.speed, frame.speed_scale);
printf("+++ 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.longitude, frame.longitude_scale, 1000),
minmea_rescale(frame.speed, frame.speed_scale, 1000));
printf("+++ floating point degree coordinates and speed: (%f,%f) %f\n",
minmea_coord(frame.latitude, frame.latitude_scale),
minmea_coord(frame.longitude, frame.longitude_scale),
minmea_float(frame.speed, frame.speed_scale));
}
} break;
case MINMEA_GPGGA: {
struct minmea_gpgga frame;
if (minmea_parse_gpgga(&frame, line)) {
printf("$GPGGA: fix quality: %d\n", frame.fix_quality);
}
} break;
default: {
} break;
}
}
```
## Integration with your project
Simply add ``minmea.[ch]`` to your project, ``#include "minmea.h"`` and you're
good to go.
## Running unit tests
Building and running the tests requires the following:
* Check Framework (http://check.sourceforge.net/).
* Clang Static Analyzer (http://clang-analyzer.llvm.org/).
If you have both in your ``$PATH``, running the tests should be as simple as
typing ``make``.
## Bugs
There are plenty. Report them on GitHub, or - even better - open a pull request.

46
example.c Normal file
View File

@ -0,0 +1,46 @@
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "minmea.h"
int main()
{
char line[MINMEA_MAX_LENGTH];
while (fgets(line, sizeof(line), stdin) != NULL) {
printf("%s", line);
switch (minmea_type(line)) {
case MINMEA_GPRMC: {
struct minmea_gprmc frame;
if (minmea_parse_gprmc(&frame, line)) {
printf("+++ raw coordinates and speed: (%d/%d,%d/%d) %d/%d\n",
frame.latitude, frame.latitude_scale,
frame.longitude, frame.longitude_scale,
frame.speed, frame.speed_scale);
printf("+++ 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.longitude, frame.longitude_scale, 1000),
minmea_rescale(frame.speed, frame.speed_scale, 1000));
printf("+++ floating point degree coordinates and speed: (%f,%f) %f\n",
minmea_coord(frame.latitude, frame.latitude_scale),
minmea_coord(frame.longitude, frame.longitude_scale),
minmea_float(frame.speed, frame.speed_scale));
}
} break;
case MINMEA_GPGGA: {
struct minmea_gpgga frame;
if (minmea_parse_gpgga(&frame, line)) {
printf("$GPGGA: fix quality: %d\n", frame.fix_quality);
}
} break;
default: {
} break;
}
}
return 0;
}
/* vim: ts=4 sw=4 et: */

353
minmea.c Normal file
View File

@ -0,0 +1,353 @@
#include "minmea.h"
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#define boolstr(s) ((s) ? "true" : "false")
static int hex2int(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A';
if (c >= 'a' && c <= 'f')
return c - 'a';
return -1;
}
bool minmea_check(const char *sentence)
{
uint8_t checksum = 0x00;
// Sequence length is limited.
if (strlen(sentence) > MINMEA_MAX_LENGTH + 3)
return false;
// A valid sentence starts with "$".
if (*sentence++ != '$')
return false;
// The optional checksum is an XOR of all bytes between "$" and "*".
while (*sentence && *sentence != '*' && isprint(*sentence))
checksum ^= *sentence++;
if (*sentence == '*') {
// Extract checksum.
sentence++;
int upper = hex2int(*sentence++);
if (upper == -1)
return false;
int lower = hex2int(*sentence++);
if (lower == -1)
return false;
int expected = upper << 4 | lower;
// Check for checksum mismatch.
if (checksum != expected)
return false;
}
// The only stuff allowed at this point is a newline.
if (*sentence && strcmp(sentence, "\n") && strcmp(sentence, "\r\n"))
return false;
return true;
}
static inline bool minmea_isfield(char c) {
return isprint(c) && c != ',' && c != '*';
}
bool minmea_scan(const char *sentence, const char *format, ...)
{
bool result = false;
va_list ap;
va_start(ap, format);
const char *field = sentence;
#define next_field() \
do { \
while (minmea_isfield(*sentence++)) {} \
field = sentence; \
} while (0)
while (*format) {
char type = *format++;
switch (type) {
case 'c': { // Single character field (char).
char value = '\0';
if (minmea_isfield(*field))
value = *field;
else
value = '\0';
*va_arg(ap, char *) = value;
} break;
case 'd': { // Single character direction field (int).
int value = 0;
if (minmea_isfield(*field)) {
switch (*field) {
case 'N':
case 'E':
value = 1;
break;
case 'S':
case 'W':
value = -1;
break;
default:
goto end;
}
}
*va_arg(ap, int *) = value;
} break;
case 'f': { // Fractional value with scale (int, int).
int sign = 0;
int value = -1;
int scale = 0;
while (minmea_isfield(*field)) {
if (*field == '+' && !sign && value == -1) {
sign = 1;
} else if (*field == '-' && !sign && value == -1) {
sign = -1;
} else if (isdigit(*field)) {
if (value == -1)
value = 0;
value = (10 * value) + (*field - '0');
if (scale)
scale *= 10;
} else if (*field == '.' && scale == 0) {
scale = 1;
} else {
goto end;
}
field++;
}
if ((sign || scale) && value == -1)
goto end;
if (value == -1) {
value = 0;
scale = 0;
}
if (sign)
value *= sign;
*va_arg(ap, int *) = value;
*va_arg(ap, int *) = scale;
} break;
case 'i': { // Integer value, default 0 (int).
int value;
char *endptr;
value = strtol(field, &endptr, 10);
if (minmea_isfield(*endptr))
goto end;
*va_arg(ap, int *) = value;
} break;
case 's': { // String value (char *).
char *buf = va_arg(ap, char *);
while (minmea_isfield(*field))
*buf++ = *field++;
*buf = '\0';
} break;
case 't': { // NMEA talker+sequence identifier (char *).
if (field[0] != '$')
goto end;
for (int i=0; i<5; i++)
if (!minmea_isfield(field[1+i]))
goto end;
char *buf = va_arg(ap, char *);
memcpy(buf, field+1, 5);
buf[5] = '\0';
} break;
case 'D': { // Date (int, int, int), -1 if empty.
struct minmea_date *date = va_arg(ap, struct minmea_date *);
int d = -1, m = -1, y = -1;
// Always six digits.
for (int i=0; i<6; i++)
if (!isdigit(field[i]))
goto end_D;
d = strtol((char[]) {field[0], field[1], '\0'}, NULL, 10);
m = strtol((char[]) {field[2], field[3], '\0'}, NULL, 10);
y = strtol((char[]) {field[4], field[5], '\0'}, NULL, 10);
end_D:
date->day = d;
date->month = m;
date->year = y;
} break;
case 'T': { // Time (int, int, int, int), -1 if empty.
struct minmea_time *time = va_arg(ap, struct minmea_time *);
int h = -1, i = -1, s = -1, u = -1;
// Minimum required: integer time.
for (int i=0; i<6; i++)
if (!isdigit(field[i]))
goto end_T;
h = strtol((char[]) {field[0], field[1], '\0'}, NULL, 10);
i = strtol((char[]) {field[2], field[3], '\0'}, NULL, 10);
s = strtol((char[]) {field[4], field[5], '\0'}, NULL, 10);
field += 6;
// Extra: fractional time. Saved as microseconds.
if (*field++ == '.') {
int value = 0;
int scale = 1000000;
while (isdigit(*field) && scale > 1) {
value = (value * 10) + (*field++ - '0');
scale /= 10;
}
u = value * scale;
} else {
u = 0;
}
end_T:
time->hours = h;
time->minutes = i;
time->seconds = s;
time->microseconds = u;
} break;
case '_': { // Ignore the field.
} break;
default: { // Unknown.
goto end;
} break;
}
// Advance to next field.
next_field();
}
result = true;
end:
va_end(ap);
return result;
}
enum minmea_type minmea_type(const char *sentence)
{
if (!minmea_check(sentence))
return MINMEA_INVALID;
char type[6];
if (!minmea_scan(sentence, "t", type))
return MINMEA_INVALID;
if (!strcmp(type, "GPRMC"))
return MINMEA_GPRMC;
if (!strcmp(type, "GPGGA"))
return MINMEA_GPGGA;
return MINMEA_UNKNOWN;
}
bool minmea_parse_gprmc(struct minmea_gprmc *frame, const char *sentence)
{
// $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
char type[6];
char validity;
int latitude_direction;
int longitude_direction;
int variation_direction;
if (!minmea_scan(sentence, "tTcfdfdffDfd",
type,
&frame->time,
&validity,
&frame->latitude, &frame->latitude_scale, &latitude_direction,
&frame->longitude, &frame->longitude_scale, &longitude_direction,
&frame->speed, &frame->speed_scale,
&frame->course, &frame->course_scale,
&frame->date,
&frame->variation, &frame->variation_scale, &variation_direction))
return false;
if (strcmp(type, "GPRMC"))
return false;
frame->valid = (validity == 'A');
frame->latitude *= latitude_direction;
frame->longitude *= longitude_direction;
frame->variation *= variation_direction;
return true;
}
bool minmea_parse_gpgga(struct minmea_gpgga *frame, const char *sentence)
{
// $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
char type[6];
int latitude_direction;
int longitude_direction;
if (!minmea_scan(sentence, "tTfdfdiiffcfci_",
type,
&frame->time,
&frame->latitude, &frame->latitude_scale, &latitude_direction,
&frame->longitude, &frame->longitude_scale, &longitude_direction,
&frame->fix_quality,
&frame->satellites_tracked,
&frame->hdop, &frame->hdop_scale,
&frame->altitude, &frame->altitude_scale, &frame->altitude_units,
&frame->height, &frame->height_scale, &frame->height_units,
&frame->dgps_age))
return false;
if (strcmp(type, "GPGGA"))
return false;
frame->latitude *= latitude_direction;
frame->longitude *= longitude_direction;
return true;
}
int minmea_gettimeofday(struct timeval *tv, const struct minmea_date *date, const struct minmea_time *time)
{
if (date->year == -1 || time->hours == -1)
return -1;
struct tm tm;
tm.tm_year = 2000 + date->year - 1900;
tm.tm_mon = date->month - 1;
tm.tm_mday = date->day;
tm.tm_hour = time->hours;
tm.tm_min = time->minutes;
tm.tm_sec = time->seconds;
tm.tm_isdst = 0;
time_t timestamp = timegm(&tm);
if (timestamp != -1) {
tv->tv_sec = timestamp;
tv->tv_usec = time->microseconds;
return 0;
} else {
return -1;
}
}
/* vim: set ts=4 sw=4 et: */

138
minmea.h Normal file
View File

@ -0,0 +1,138 @@
#ifndef MINMEA_H
#define MINMEA_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <time.h>
#include <math.h>
#include <sys/time.h>
#define MINMEA_MAX_LENGTH 80
enum minmea_type {
MINMEA_INVALID = -1,
MINMEA_UNKNOWN = 0,
MINMEA_GPRMC,
MINMEA_GPGGA,
};
struct minmea_date {
int day;
int month;
int year;
};
struct minmea_time {
int hours;
int minutes;
int seconds;
int microseconds;
};
struct minmea_gprmc {
struct minmea_time time;
bool valid;
int latitude, latitude_scale;
int longitude, longitude_scale;
int speed, speed_scale;
int course, course_scale;
struct minmea_date date;
int variation, variation_scale;
};
struct minmea_gpgga {
struct minmea_time time;
int latitude, latitude_scale;
int longitude, longitude_scale;
int fix_quality;
int satellites_tracked;
int hdop, hdop_scale;
int altitude, altitude_scale; char altitude_units;
int height, height_scale; char height_units;
int dgps_age;
};
/**
* Check sequence validity and checksum. Returns true for valid sequences.
*/
bool minmea_check(const char *sentence);
/**
* Determine sequence type.
*/
enum minmea_type minmea_type(const char *sequence);
/**
* Scanf-like processor for NMEA sentences. Supports the following formats:
* c - single character (char *)
* d - direction, returned as 1/-1, default 0 (int *)
* f - fractional, returned as value + scale (int *, int *)
* i - decimal, default zero (int *)
* s - string (char *)
* t - talker identifier and type (char *)
* T - date/time stamp (int *, int *, int *)
* Returns true on success. See library source code for details.
*/
bool minmea_scan(const char *sentence, const char *format, ...);
/*
* Parse a specific type of frame. Return true on success.
*/
bool minmea_parse_gprmc(struct minmea_gprmc *frame, const char *sentence);
bool minmea_parse_gpgga(struct minmea_gpgga *frame, const char *sentence);
/**
* Convert GPS UTC date/time representation to a UNIX timestamp.
*/
int minmea_gettimeofday(struct timeval *tv, const struct minmea_date *date, const struct minmea_time *time);
/**
* Rescale signed integer value to a different full-scale value, with rounding.
* The "from" value in the numerator cancels out with the denominator, leaving
* just 1/2 - which makes integer truncation act as rounding. Returns zero for
* invalid values.
*/
static inline int minmea_rescale(int value, int from, int to)
{
if (from == 0)
return 0;
return (value * to + from / 2) / from;
}
/**
* Convert a fixed-point value to a floating-point value.
* Returns NaN for "unknown" values.
*/
static inline float minmea_float(int value, int scale)
{
if (scale == 0)
return NAN;
return (float) value / (float) scale;
}
/**
* Convert a raw coordinate to a floating point DD.DDD... value.
* Returns NaN for "unknown" values.
*/
static inline float minmea_coord(int value, int scale)
{
if (scale == 0)
return NAN;
int degrees = value / (scale * 100);
int minutes = value % (scale * 100);
return (float) degrees + (float) minutes / (60 * scale);
}
#ifdef __cplusplus
}
#endif
#endif /* MINMEA_H */
/* vim: set ts=4 sw=4 et: */

447
tests.c Normal file
View File

@ -0,0 +1,447 @@
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <check.h>
#include "minmea.h"
static const char *valid_sequences[] = {
"$GPTXT,xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"$GPTXT,01,01,02,ANTSTATUS=INIT*25",
"$GPRMC,,V,,,,,,,,,,N*53",
"$GPVTG,,,,,,,,,N*30",
"$GPGGA,,,,,,0,00,99.99,,,,,,*48",
"$GPGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*30",
"$GPGLL,,,,,,V,N*64",
NULL,
};
static const char *invalid_sequences[] = {
"$GPTXT,xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
"xxxxxxxxxxxxxxxxxxxxxxxxxxx",
"$GPTXT,01,01,02,ANTSTATUS=INIT*26",
"$GPRMC,,V,,,,,,,,,,N*532",
"$GPVTG,,,,\xff,,,,,N*30",
"$$GPGGA,,,,,,0,00,99.99,,,,,,*48",
"GPGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*30",
"gps: $GPGLL,,,,,,V,N",
NULL,
};
START_TEST(test_minmea_check)
{
for (const char **sequence=valid_sequences; *sequence; sequence++)
ck_assert_msg(minmea_check(*sequence) == true, *sequence);
for (const char **sequence=invalid_sequences; *sequence; sequence++)
ck_assert_msg(minmea_check(*sequence) == false, *sequence);
}
END_TEST
START_TEST(test_minmea_scan_c)
{
char ch;
ck_assert(minmea_scan("A,123.45", "c", &ch) == true);
ck_assert_int_eq(ch, 'A');
ck_assert(minmea_scan("WUT,123.45", "c", &ch) == true);
ck_assert_int_eq(ch, 'W');
ck_assert(minmea_scan(",123.45", "c", &ch) == true);
ck_assert_int_eq(ch, '\0');
}
END_TEST
START_TEST(test_minmea_scan_d)
{
int direction;
ck_assert(minmea_scan("K", "d", &direction) == false);
ck_assert(minmea_scan("", "d", &direction) == true);
ck_assert(minmea_scan(",foo", "d", &direction) == true);
ck_assert_int_eq(direction, 0);
ck_assert(minmea_scan("N", "d", &direction) == true);
ck_assert_int_eq(direction, 1);
ck_assert(minmea_scan("S,foo", "d", &direction) == true);
ck_assert_int_eq(direction, -1);
ck_assert(minmea_scan("W", "d", &direction) == true);
ck_assert_int_eq(direction, -1);
ck_assert(minmea_scan("E,foo", "d", &direction) == true);
ck_assert_int_eq(direction, 1);
}
END_TEST
START_TEST(test_minmea_scan_f)
{
int value, scale;
ck_assert(minmea_scan("-", "f", &value, &scale) == false);
ck_assert(minmea_scan("10-", "f", &value, &scale) == false);
ck_assert(minmea_scan("+-10", "f", &value, &scale) == false);
ck_assert(minmea_scan("12..45", "f", &value, &scale) == false);
ck_assert(minmea_scan("blah", "f", &value, &scale) == false);
ck_assert(minmea_scan("12.3.4", "f", &value, &scale) == false);
ck_assert(minmea_scan(",", "f", &value, &scale) == true);
ck_assert_int_eq(scale, 0);
ck_assert(minmea_scan("", "f", &value, &scale) == true);
ck_assert_int_eq(scale, 0);
ck_assert(minmea_scan("15.345", "f", &value, &scale) == true);
ck_assert_int_eq(value, 15345);
ck_assert_int_eq(scale, 1000);
ck_assert(minmea_scan("-1.23,V", "f", &value, &scale) == true);
ck_assert_int_eq(value, -123);
ck_assert_int_eq(scale, 100);
}
END_TEST
START_TEST(test_minmea_scan_s)
{
char value[MINMEA_MAX_LENGTH];
ck_assert(minmea_scan("foo,bar,baz", "s", value) == true);
ck_assert_str_eq(value, "foo");
ck_assert(minmea_scan(",bar,baz", "s", value) == true);
ck_assert_str_eq(value, "");
}
END_TEST
START_TEST(test_minmea_scan_t)
{
char buf[7];
buf[sizeof(buf)-1] = 0x42;
ck_assert(minmea_scan("$GPRM,foo,bar,baz", "t", buf) == false);
ck_assert(minmea_scan("GPRMC,foo,bar,baz", "t", buf) == false);
ck_assert(minmea_scan("$GPRMC,foo,bar,baz", "t", buf) == true);
ck_assert_str_eq(buf, "GPRMC");
ck_assert(buf[sizeof(buf)-1] == 0x42);
}
END_TEST
START_TEST(test_minmea_scan_D)
{
struct minmea_date date;
ck_assert(minmea_scan("$GPXXX,311299", "_D", &date) == true);
ck_assert_int_eq(date.day, 31);
ck_assert_int_eq(date.month, 12);
ck_assert_int_eq(date.year, 99);
ck_assert(minmea_scan("$GPXXX,,,,,,,,,nope", "_D", &date) == true);
ck_assert_int_eq(date.day, -1);
ck_assert_int_eq(date.month, -1);
ck_assert_int_eq(date.year, -1);
}
END_TEST
START_TEST(test_minmea_scan_T)
{
struct minmea_time time;
ck_assert(minmea_scan("$GPXXX,235960", "_T", &time) == true);
ck_assert_int_eq(time.hours, 23);
ck_assert_int_eq(time.minutes, 59);
ck_assert_int_eq(time.seconds, 60);
ck_assert_int_eq(time.microseconds, 0);
ck_assert(minmea_scan("$GPXXX,213700.001", "_T", &time) == true);
ck_assert_int_eq(time.hours, 21);
ck_assert_int_eq(time.minutes, 37);
ck_assert_int_eq(time.seconds, 0);
ck_assert_int_eq(time.microseconds, 1000);
ck_assert(minmea_scan("$GPXXX,,,,,,,nope", "_T", &time) == true);
ck_assert_int_eq(time.hours, -1);
ck_assert_int_eq(time.minutes, -1);
ck_assert_int_eq(time.seconds, -1);
ck_assert_int_eq(time.microseconds, -1);
}
END_TEST
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";
char type[6];
struct minmea_time time;
int latitude, latitude_scale, latitude_direction;
int longitude, longitude_scale, longitude_direction;
int fix_quality;
int satellites;
int hdop, hdop_scale;
int altitude, altitude_scale; char altitude_units;
int height, height_scale; char height_units;
ck_assert(minmea_scan(sentence, "tTfdfdiiffcfc__",
type,
&time,
&latitude, &latitude_scale, &latitude_direction,
&longitude, &longitude_scale, &longitude_direction,
&fix_quality,
&satellites,
&hdop, &hdop_scale,
&altitude, &altitude_scale, &altitude_units,
&height, &height_scale, &height_units) == true);
ck_assert_str_eq(type, "GPGGA");
ck_assert_int_eq(time.hours, 12);
ck_assert_int_eq(time.minutes, 35);
ck_assert_int_eq(time.seconds, 19);
ck_assert_int_eq(latitude, 4807038);
ck_assert_int_eq(latitude_scale, 1000);
ck_assert_int_eq(latitude_direction, 1);
ck_assert_int_eq(longitude, 1131000);
ck_assert_int_eq(longitude_scale, 1000);
ck_assert_int_eq(longitude_direction, 1);
ck_assert_int_eq(fix_quality, 1);
ck_assert_int_eq(satellites, 8);
ck_assert_int_eq(hdop, 9);
ck_assert_int_eq(hdop_scale, 10);
ck_assert_int_eq(altitude, 5454);
ck_assert_int_eq(altitude_scale, 10);
ck_assert_int_eq(altitude_units, 'M');
ck_assert_int_eq(height, 469);
ck_assert_int_eq(height_scale, 10);
ck_assert_int_eq(height_units, 'M');
}
END_TEST
START_TEST(test_minmea_scan_complex2)
{
const char *sentence = "$GPBWC,081837,,,,,,T,,M,,N,*13";
char type[6];
struct minmea_time time;
int latitude, latitude_scale, latitude_direction;
int longitude, longitude_scale, longitude_direction;
int bearing_true, bearing_true_scale; char bearing_true_mark;
int bearing_magnetic, bearing_magnetic_scale; char bearing_magnetic_mark;
int distance, distance_scale; char distance_units;
char name[MINMEA_MAX_LENGTH];
ck_assert(minmea_scan(sentence, "tTfdfdfcfcfcs",
type,
&time,
&latitude, &latitude_scale, &latitude_direction,
&longitude, &longitude_scale, &longitude_direction,
&bearing_true, &bearing_true_scale, &bearing_true_mark,
&bearing_magnetic, &bearing_magnetic_scale, &bearing_magnetic_mark,
&distance, &distance_scale, &distance_units,
name) == true);
ck_assert_str_eq(type, "GPBWC");
ck_assert_int_eq(time.hours, 8);
ck_assert_int_eq(time.minutes, 18);
ck_assert_int_eq(time.seconds, 37);
ck_assert_int_eq(latitude_scale, 0);
ck_assert_int_eq(latitude_direction, 0);
ck_assert_int_eq(longitude_scale, 0);
ck_assert_int_eq(longitude_direction, 0);
ck_assert_int_eq(bearing_true_scale, 0);
ck_assert_int_eq(bearing_true_mark, 'T');
ck_assert_int_eq(bearing_magnetic_scale, 0);
ck_assert_int_eq(bearing_magnetic_mark, 'M');
ck_assert_int_eq(distance_scale, 0);
ck_assert_int_eq(distance_units, 'N');
ck_assert_str_eq(name, "");
}
END_TEST
START_TEST(test_minmea_parse_gprmc1)
{
const char *sentence = "$GPRMC,081836.75,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E";
struct minmea_gprmc frame = {};
struct minmea_gprmc expected = {
.time = { 8, 18, 36, 750000 },
.valid = true,
.latitude = -375165,
.latitude_scale = 100,
.longitude = 1450736,
.longitude_scale = 100,
.speed = 0,
.speed_scale = 10,
.course = 3600,
.course_scale = 10,
.date = { 13, 9, 98 },
.variation = 113,
.variation_scale = 10,
};
ck_assert(minmea_check(sentence) == true);
ck_assert(minmea_parse_gprmc(&frame, sentence) == true);
ck_assert(!memcmp(&frame, &expected, sizeof(frame)));
}
END_TEST
START_TEST(test_minmea_parse_gprmc2)
{
const char *sentence = "$GPRMC,,A,3751.65,N,14507.36,W,,,,,";
struct minmea_gprmc frame = {};
struct minmea_gprmc expected = {
.time = { -1, -1, -1, -1 },
.valid = true,
.latitude = 375165,
.latitude_scale = 100,
.longitude = -1450736,
.longitude_scale = 100,
.date = { -1, -1, -1 },
};
ck_assert(minmea_check(sentence) == true);
ck_assert(minmea_parse_gprmc(&frame, sentence) == true);
ck_assert(!memcmp(&frame, &expected, sizeof(frame)));
}
END_TEST
START_TEST(test_minmea_parse_gpgga1)
{
const char *sentence = "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47";
struct minmea_gpgga frame = {};
struct minmea_gpgga expected = {
.time = { 12, 35, 19, 0 },
.latitude = 4807038,
.latitude_scale = 1000,
.longitude = 1131000,
.longitude_scale = 1000,
.fix_quality = 1,
.satellites_tracked = 8,
.hdop = 9,
.hdop_scale = 10,
.altitude = 5454,
.altitude_scale = 10,
.altitude_units = 'M',
.height = 469,
.height_scale = 10,
.height_units = 'M',
.dgps_age = 0,
};
ck_assert(minmea_check(sentence) == true);
ck_assert(minmea_parse_gpgga(&frame, sentence) == true);
ck_assert(!memcmp(&frame, &expected, sizeof(frame)));
}
END_TEST
START_TEST(test_minmea_usage1)
{
const char *sentences[] = {
"$GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62",
"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47",
NULL,
};
for (const char **sentence=sentences; *sentence; sentence++) {
switch (minmea_type(*sentence)) {
case MINMEA_GPRMC: {
struct minmea_gprmc frame;
ck_assert(minmea_parse_gprmc(&frame, *sentence) == true);
} break;
case MINMEA_GPGGA: {
struct minmea_gpgga frame;
ck_assert(minmea_parse_gpgga(&frame, *sentence) == true);
} break;
default: {
} break;
}
}
}
END_TEST
START_TEST(test_minmea_gettimeofday)
{
struct minmea_date date = { 14, 2, 14 };
struct minmea_time time = { 13, 0, 9, 123456 };
struct timeval tv;
ck_assert(minmea_gettimeofday(&tv, &date, &time) == 0);
ck_assert_int_eq(tv.tv_sec, 1392382809);
ck_assert_int_eq(tv.tv_usec, 123456);
date.year = -1;
ck_assert(minmea_gettimeofday(&tv, &date, &time) != 0);
date.year = 2014;
time.hours = -1;
ck_assert(minmea_gettimeofday(&tv, &date, &time) != 0);
}
END_TEST
START_TEST(test_minmea_rescale)
{
ck_assert(minmea_rescale(42, 0, 3) == 0);
ck_assert(minmea_rescale(1234, 10, 1) == 123);
ck_assert(minmea_rescale(1235, 10, 1) == 124);
ck_assert(minmea_rescale(1234, 10, 1000) == 123400);
}
END_TEST
START_TEST(test_minmea_float)
{
ck_assert(isnan(minmea_float(42, 0)));
ck_assert(minmea_float(7, 1) == 7.0);
ck_assert(minmea_float(-200, 100) == -2.0);
ck_assert(minmea_float(15, 10) == 1.5);
}
END_TEST
START_TEST(test_minmea_coord)
{
ck_assert(isnan(minmea_coord(42, 0)));
ck_assert(minmea_coord(4200, 1) == 42.0);
ck_assert(minmea_coord(420000, 100) == 42.0);
ck_assert(minmea_coord(423000, 100) == 42.5);
}
END_TEST
Suite *minmea_suite(void)
{
Suite *s = suite_create ("minmea");
TCase *tc_check = tcase_create("minmea_check");
tcase_add_test(tc_check, test_minmea_check);
suite_add_tcase(s, tc_check);
TCase *tc_scan = tcase_create("minmea_scan");
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_f);
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_D);
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_complex2);
suite_add_tcase(s, tc_scan);
TCase *tc_parse = tcase_create("minmea_parse");
tcase_add_test(tc_parse, test_minmea_parse_gprmc1);
tcase_add_test(tc_parse, test_minmea_parse_gprmc2);
tcase_add_test(tc_parse, test_minmea_parse_gpgga1);
suite_add_tcase(s, tc_parse);
TCase *tc_usage = tcase_create("minmea_usage");
tcase_add_test(tc_usage, test_minmea_usage1);
suite_add_tcase(s, tc_usage);
TCase *tc_utils = tcase_create("minmea_utils");
tcase_add_test(tc_utils, test_minmea_gettimeofday);
tcase_add_test(tc_utils, test_minmea_rescale);
tcase_add_test(tc_utils, test_minmea_float);
tcase_add_test(tc_utils, test_minmea_coord);
suite_add_tcase(s, tc_utils);
return s;
}
int main()
{
int number_failed;
Suite *s = minmea_suite();
SRunner *sr = srunner_create(s);
srunner_run_all(sr, CK_NORMAL);
number_failed = srunner_ntests_failed(sr);
srunner_free(sr);
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}
/* vim: set ts=4 sw=4 et: */