initial commit
This commit is contained in:
commit
56177f0e1c
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
*.o
|
||||
*~
|
||||
minmea
|
||||
tests
|
||||
example
|
23
Makefile
Normal file
23
Makefile
Normal 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
78
README.md
Normal 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
46
example.c
Normal 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
353
minmea.c
Normal 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
138
minmea.h
Normal 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
447
tests.c
Normal 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: */
|
Loading…
Reference in New Issue
Block a user