minmea.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. #include "minmea.h"
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <ctype.h>
  5. #include <stdarg.h>
  6. #define boolstr(s) ((s) ? "true" : "false")
  7. static int hex2int(char c)
  8. {
  9. if (c >= '0' && c <= '9')
  10. return c - '0';
  11. if (c >= 'A' && c <= 'F')
  12. return c - 'A';
  13. if (c >= 'a' && c <= 'f')
  14. return c - 'a';
  15. return -1;
  16. }
  17. bool minmea_check(const char *sentence)
  18. {
  19. uint8_t checksum = 0x00;
  20. // Sequence length is limited.
  21. if (strlen(sentence) > MINMEA_MAX_LENGTH + 3)
  22. return false;
  23. // A valid sentence starts with "$".
  24. if (*sentence++ != '$')
  25. return false;
  26. // The optional checksum is an XOR of all bytes between "$" and "*".
  27. while (*sentence && *sentence != '*' && isprint(*sentence))
  28. checksum ^= *sentence++;
  29. if (*sentence == '*') {
  30. // Extract checksum.
  31. sentence++;
  32. int upper = hex2int(*sentence++);
  33. if (upper == -1)
  34. return false;
  35. int lower = hex2int(*sentence++);
  36. if (lower == -1)
  37. return false;
  38. int expected = upper << 4 | lower;
  39. // Check for checksum mismatch.
  40. if (checksum != expected)
  41. return false;
  42. }
  43. // The only stuff allowed at this point is a newline.
  44. if (*sentence && strcmp(sentence, "\n") && strcmp(sentence, "\r\n"))
  45. return false;
  46. return true;
  47. }
  48. static inline bool minmea_isfield(char c) {
  49. return isprint(c) && c != ',' && c != '*';
  50. }
  51. bool minmea_scan(const char *sentence, const char *format, ...)
  52. {
  53. bool result = false;
  54. va_list ap;
  55. va_start(ap, format);
  56. const char *field = sentence;
  57. #define next_field() \
  58. do { \
  59. while (minmea_isfield(*sentence++)) {} \
  60. field = sentence; \
  61. } while (0)
  62. while (*format) {
  63. char type = *format++;
  64. switch (type) {
  65. case 'c': { // Single character field (char).
  66. char value = '\0';
  67. if (minmea_isfield(*field))
  68. value = *field;
  69. else
  70. value = '\0';
  71. *va_arg(ap, char *) = value;
  72. } break;
  73. case 'd': { // Single character direction field (int).
  74. int value = 0;
  75. if (minmea_isfield(*field)) {
  76. switch (*field) {
  77. case 'N':
  78. case 'E':
  79. value = 1;
  80. break;
  81. case 'S':
  82. case 'W':
  83. value = -1;
  84. break;
  85. default:
  86. goto end;
  87. }
  88. }
  89. *va_arg(ap, int *) = value;
  90. } break;
  91. case 'f': { // Fractional value with scale (int, int).
  92. int sign = 0;
  93. int value = -1;
  94. int scale = 0;
  95. while (minmea_isfield(*field)) {
  96. if (*field == '+' && !sign && value == -1) {
  97. sign = 1;
  98. } else if (*field == '-' && !sign && value == -1) {
  99. sign = -1;
  100. } else if (isdigit(*field)) {
  101. if (value == -1)
  102. value = 0;
  103. value = (10 * value) + (*field - '0');
  104. if (scale)
  105. scale *= 10;
  106. } else if (*field == '.' && scale == 0) {
  107. scale = 1;
  108. } else {
  109. goto end;
  110. }
  111. field++;
  112. }
  113. if ((sign || scale) && value == -1)
  114. goto end;
  115. if (value == -1) {
  116. value = 0;
  117. scale = 0;
  118. }
  119. if (sign)
  120. value *= sign;
  121. *va_arg(ap, int *) = value;
  122. *va_arg(ap, int *) = scale;
  123. } break;
  124. case 'i': { // Integer value, default 0 (int).
  125. int value;
  126. char *endptr;
  127. value = strtol(field, &endptr, 10);
  128. if (minmea_isfield(*endptr))
  129. goto end;
  130. *va_arg(ap, int *) = value;
  131. } break;
  132. case 's': { // String value (char *).
  133. char *buf = va_arg(ap, char *);
  134. while (minmea_isfield(*field))
  135. *buf++ = *field++;
  136. *buf = '\0';
  137. } break;
  138. case 't': { // NMEA talker+sequence identifier (char *).
  139. if (field[0] != '$')
  140. goto end;
  141. for (int i=0; i<5; i++)
  142. if (!minmea_isfield(field[1+i]))
  143. goto end;
  144. char *buf = va_arg(ap, char *);
  145. memcpy(buf, field+1, 5);
  146. buf[5] = '\0';
  147. } break;
  148. case 'D': { // Date (int, int, int), -1 if empty.
  149. struct minmea_date *date = va_arg(ap, struct minmea_date *);
  150. int d = -1, m = -1, y = -1;
  151. // Always six digits.
  152. for (int i=0; i<6; i++)
  153. if (!isdigit(field[i]))
  154. goto end_D;
  155. d = strtol((char[]) {field[0], field[1], '\0'}, NULL, 10);
  156. m = strtol((char[]) {field[2], field[3], '\0'}, NULL, 10);
  157. y = strtol((char[]) {field[4], field[5], '\0'}, NULL, 10);
  158. end_D:
  159. date->day = d;
  160. date->month = m;
  161. date->year = y;
  162. } break;
  163. case 'T': { // Time (int, int, int, int), -1 if empty.
  164. struct minmea_time *time = va_arg(ap, struct minmea_time *);
  165. int h = -1, i = -1, s = -1, u = -1;
  166. // Minimum required: integer time.
  167. for (int i=0; i<6; i++)
  168. if (!isdigit(field[i]))
  169. goto end_T;
  170. h = strtol((char[]) {field[0], field[1], '\0'}, NULL, 10);
  171. i = strtol((char[]) {field[2], field[3], '\0'}, NULL, 10);
  172. s = strtol((char[]) {field[4], field[5], '\0'}, NULL, 10);
  173. field += 6;
  174. // Extra: fractional time. Saved as microseconds.
  175. if (*field++ == '.') {
  176. int value = 0;
  177. int scale = 1000000;
  178. while (isdigit(*field) && scale > 1) {
  179. value = (value * 10) + (*field++ - '0');
  180. scale /= 10;
  181. }
  182. u = value * scale;
  183. } else {
  184. u = 0;
  185. }
  186. end_T:
  187. time->hours = h;
  188. time->minutes = i;
  189. time->seconds = s;
  190. time->microseconds = u;
  191. } break;
  192. case '_': { // Ignore the field.
  193. } break;
  194. default: { // Unknown.
  195. goto end;
  196. } break;
  197. }
  198. // Advance to next field.
  199. next_field();
  200. }
  201. result = true;
  202. end:
  203. va_end(ap);
  204. return result;
  205. }
  206. enum minmea_type minmea_type(const char *sentence)
  207. {
  208. if (!minmea_check(sentence))
  209. return MINMEA_INVALID;
  210. char type[6];
  211. if (!minmea_scan(sentence, "t", type))
  212. return MINMEA_INVALID;
  213. if (!strcmp(type, "GPRMC"))
  214. return MINMEA_GPRMC;
  215. if (!strcmp(type, "GPGGA"))
  216. return MINMEA_GPGGA;
  217. return MINMEA_UNKNOWN;
  218. }
  219. bool minmea_parse_gprmc(struct minmea_gprmc *frame, const char *sentence)
  220. {
  221. // $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
  222. char type[6];
  223. char validity;
  224. int latitude_direction;
  225. int longitude_direction;
  226. int variation_direction;
  227. if (!minmea_scan(sentence, "tTcfdfdffDfd",
  228. type,
  229. &frame->time,
  230. &validity,
  231. &frame->latitude, &frame->latitude_scale, &latitude_direction,
  232. &frame->longitude, &frame->longitude_scale, &longitude_direction,
  233. &frame->speed, &frame->speed_scale,
  234. &frame->course, &frame->course_scale,
  235. &frame->date,
  236. &frame->variation, &frame->variation_scale, &variation_direction))
  237. return false;
  238. if (strcmp(type, "GPRMC"))
  239. return false;
  240. frame->valid = (validity == 'A');
  241. frame->latitude *= latitude_direction;
  242. frame->longitude *= longitude_direction;
  243. frame->variation *= variation_direction;
  244. return true;
  245. }
  246. bool minmea_parse_gpgga(struct minmea_gpgga *frame, const char *sentence)
  247. {
  248. // $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
  249. char type[6];
  250. int latitude_direction;
  251. int longitude_direction;
  252. if (!minmea_scan(sentence, "tTfdfdiiffcfci_",
  253. type,
  254. &frame->time,
  255. &frame->latitude, &frame->latitude_scale, &latitude_direction,
  256. &frame->longitude, &frame->longitude_scale, &longitude_direction,
  257. &frame->fix_quality,
  258. &frame->satellites_tracked,
  259. &frame->hdop, &frame->hdop_scale,
  260. &frame->altitude, &frame->altitude_scale, &frame->altitude_units,
  261. &frame->height, &frame->height_scale, &frame->height_units,
  262. &frame->dgps_age))
  263. return false;
  264. if (strcmp(type, "GPGGA"))
  265. return false;
  266. frame->latitude *= latitude_direction;
  267. frame->longitude *= longitude_direction;
  268. return true;
  269. }
  270. int minmea_gettimeofday(struct timeval *tv, const struct minmea_date *date, const struct minmea_time *time)
  271. {
  272. if (date->year == -1 || time->hours == -1)
  273. return -1;
  274. struct tm tm;
  275. tm.tm_year = 2000 + date->year - 1900;
  276. tm.tm_mon = date->month - 1;
  277. tm.tm_mday = date->day;
  278. tm.tm_hour = time->hours;
  279. tm.tm_min = time->minutes;
  280. tm.tm_sec = time->seconds;
  281. tm.tm_isdst = 0;
  282. time_t timestamp = timegm(&tm);
  283. if (timestamp != -1) {
  284. tv->tv_sec = timestamp;
  285. tv->tv_usec = time->microseconds;
  286. return 0;
  287. } else {
  288. return -1;
  289. }
  290. }
  291. /* vim: set ts=4 sw=4 et: */