SELKIELogger  1.0.0
NMEAMessages.c
1 /*
2  * Copyright (C) 2023 Swansea University
3  *
4  * This file is part of the SELKIELogger suite of tools.
5  *
6  * SELKIELogger is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the Free
8  * Software Foundation, either version 3 of the License, or (at your option)
9  * any later version.
10  *
11  * SELKIELogger is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14  * more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this SELKIELogger product.
18  * If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 #include <stddef.h>
22 #include <stdint.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 
27 #include <errno.h>
28 
29 #include "NMEAMessages.h"
30 
37 void nmea_calc_checksum(const nmea_msg_t *msg, uint8_t *cs) {
38  uint8_t a = 0;
39 
40  // Checksum does not include start byte
41  a ^= msg->talker[0];
42  a ^= msg->talker[1];
43  if (msg->talker[0] == 'P') {
44  a ^= msg->talker[2];
45  a ^= msg->talker[3];
46  }
47  a ^= msg->message[0];
48  a ^= msg->message[1];
49  a ^= msg->message[2];
50 
51  if (msg->fields.entries > 0) {
52  for (int fi = 0; fi < msg->fields.entries; fi++) {
53  a ^= ',';
54  for (unsigned int ffi = 0; ffi < msg->fields.strings[fi].length; ffi++) {
55  a ^= msg->fields.strings[fi].data[ffi];
56  }
57  }
58  } else {
59  a ^= ',';
60  for (int ri = 0; ri < msg->rawlen; ri++) {
61  a ^= msg->raw[ri];
62  }
63  }
64  *cs = a;
65  return;
66 }
67 
75  nmea_calc_checksum(msg, &(msg->checksum));
76 }
77 
85 bool nmea_check_checksum(const nmea_msg_t *msg) {
86  uint8_t a = 0;
87  nmea_calc_checksum(msg, &a);
88  if (msg->checksum == a) { return true; }
89  return false;
90 }
91 
99 size_t nmea_message_length(const nmea_msg_t *msg) {
100  size_t asize = 3; // Minimum - start byte + CRLF
101  asize += 2; // Minimum talker size
102  if (msg->talker[0] == 'P') {
103  asize += 2; // Bring up to 4 for proprietary messages
104  }
105  asize += 3; // Message type
106  if (msg->fields.entries > 0) {
107  for (int fi = 0; fi < msg->fields.entries; fi++) {
108  asize += msg->fields.strings[fi].length + 1; // +1 for field delimiter
109  }
110  } else {
111  asize += msg->rawlen;
112  }
113  asize += 5; // Checksum and delimiter, CR, LF
114  return asize;
115 }
116 
127 size_t nmea_flat_array(const nmea_msg_t *msg, char **out) {
128  char *outarray = calloc(nmea_message_length(msg), 1);
129  size_t ix = 0;
130  outarray[ix++] = msg->encapsulated ? NMEA_START_BYTE2 : NMEA_START_BYTE1;
131  outarray[ix++] = msg->talker[0];
132  outarray[ix++] = msg->talker[1];
133  if (msg->talker[0] == 'P') {
134  outarray[ix++] = msg->talker[2];
135  outarray[ix++] = msg->talker[3];
136  }
137  outarray[ix++] = msg->message[0];
138  outarray[ix++] = msg->message[1];
139  outarray[ix++] = msg->message[2];
140  if (msg->fields.entries > 0) {
141  for (int fi = 0; fi < msg->fields.entries; fi++) {
142  outarray[ix++] = ',';
143  for (unsigned int ffi = 0; ffi < msg->fields.strings[fi].length; ffi++) {
144  outarray[ix++] = msg->fields.strings[fi].data[ffi];
145  }
146  }
147  } else {
148  outarray[ix++] = ',';
149  for (int ri = 0; ri < msg->rawlen; ri++) {
150  outarray[ix++] = msg->raw[ri];
151  }
152  }
153  outarray[ix++] = NMEA_CSUM_MARK;
154 
155  const char *hd = "0123456789ABCDEF";
156  outarray[ix++] = hd[(msg->checksum >> 8) & 0xF];
157  outarray[ix++] = hd[msg->checksum & 0xF];
158  (*out) = outarray;
159  return ix;
160 }
161 
174 char *nmea_string_hex(const nmea_msg_t *msg) {
175  char *str = NULL;
176  size_t s = nmea_flat_array(msg, &str);
177  str = realloc(str, s + 1);
178  str[s] = 0x00; // Null terminate string representation!
179  return str;
180 }
181 
188 void nmea_print_hex(const nmea_msg_t *msg) {
189  char *out = nmea_string_hex(msg);
190  if (out) {
191  printf("%s\n", out);
192  free(out);
193  out = NULL;
194  }
195 }
196 
204  const uint8_t *fields[80] = {0};
205  int lengths[80] = {0};
206  int fc = 0;
207  const uint8_t *sp = nmsg->raw;
208  for (int fp = 0; fp < nmsg->rawlen; fp++) {
209  if ((nmsg->raw[fp] == ',') || (nmsg->raw[fp] == 0)) {
210  fields[fc] = sp;
211  lengths[fc] = &(nmsg->raw[fp]) - sp;
212  fc++;
213  sp = &(nmsg->raw[fp + 1]);
214  }
215  }
216  fields[fc] = sp;
217  lengths[fc] = &(nmsg->raw[nmsg->rawlen]) - sp;
218  fc++;
219 
220  strarray *sa = sa_new(fc);
221  if (sa == NULL) { return false; }
222  for (int fn = 0; fn < fc; fn++) {
223  if (!sa_create_entry(sa, fn, lengths[fn], (const char *)fields[fn])) {
224  sa_destroy(sa);
225  free(sa);
226  return NULL;
227  }
228  }
229  return sa;
230 }
231 
240 struct tm *nmea_parse_zda(const nmea_msg_t *msg) {
241  const char *tk = "II";
242  const char *mt = "ZDA";
243  if ((strncmp(msg->talker, tk, 2) != 0) || (strncmp(msg->message, mt, 3) != 0)) { return NULL; }
244  strarray *sa = nmea_parse_fields(msg);
245  if (sa == NULL) { return NULL; }
246  if (sa->entries != 6) {
247  sa_destroy(sa);
248  free(sa);
249  return NULL;
250  }
251  /*
252  * Should have 6 fields:
253  * - HHMMSS[.sss]
254  * - DD
255  * - MM
256  * - YYYY
257  * - TZ Hours+-
258  * - TZ Minutes+-
259  *
260  * struct tm has no fractional seconds, so those will be discarded if present
261  */
262 
263  // Check lengths of required fields
264  if (sa->strings[0].length < 6 || sa->strings[1].length < 1 || sa->strings[2].length < 1 ||
265  sa->strings[3].length != 4) {
266  sa_destroy(sa);
267  free(sa);
268  return NULL;
269  }
270 
271  struct tm *tout = calloc(1, sizeof(struct tm));
272  if (tout == NULL) {
273  sa_destroy(sa);
274  free(sa);
275  return NULL;
276  }
277 
278  {
279  errno = 0;
280  int day = strtol(sa->strings[1].data, NULL, 10);
281  if (errno || day < 0 || day > 31) {
282  free(tout);
283  sa_destroy(sa);
284  free(sa);
285  return NULL;
286  }
287  tout->tm_mday = day;
288  }
289  {
290  errno = 0;
291  int mon = strtol(sa->strings[2].data, NULL, 10);
292  if (errno || mon < 0 || mon > 12) {
293  free(tout);
294  sa_destroy(sa);
295  free(sa);
296  return NULL;
297  }
298  tout->tm_mon = mon - 1; // Months since January 1, not month number
299  }
300  {
301  errno = 0;
302  int year = strtol(sa->strings[3].data, NULL, 10);
303  // Year boundaries are arbitrary, but allows for some historical and
304  // future usage If anyone is using this software in the year 2100 then: a)
305  // I'm surprised! b) Update the upper bound below
306  if (errno || year < 1970 || year > 2100) {
307  free(tout);
308  sa_destroy(sa);
309  free(sa);
310  return NULL;
311  }
312  tout->tm_year = year - 1900;
313  }
314 
315  /*
316  * At this point, we take advantage of mktime to tidy things up and
317  * fill out some of the other tm_ fields for us rather than working out
318  * day of week, year etc.
319  *
320  * mktime would obliterate timezone details though, so we'll set the
321  * time and zone information afterwards.
322  */
323  if (mktime(tout) == (time_t)(-1)) {
324  free(tout);
325  sa_destroy(sa);
326  free(sa);
327  return NULL;
328  }
329 
330  {
331  errno = 0;
332  int time = strtol(sa->strings[0].data, NULL, 10);
333  if (errno || time < 0 || time > 235960) {
334  free(tout);
335  sa_destroy(sa);
336  free(sa);
337  return NULL;
338  }
339  tout->tm_hour = time / 10000;
340  tout->tm_min = (time % 10000) / 100;
341  tout->tm_sec = (time % 100);
342  }
343 
344  {
345  errno = 0;
346  int tzhours = 0;
347  int tzmins = 0;
348 
349  // Assume no offset if strings not present
350  if (sa->strings[4].length > 0) { strtol(sa->strings[4].data, NULL, 10); }
351  if (sa->strings[5].length > 0) { strtol(sa->strings[5].data, NULL, 10); }
352  if (errno || tzhours < -13 || tzhours > 13 || tzmins < -60 || tzmins > 60) {
353  free(tout);
354  sa_destroy(sa);
355  free(sa);
356  return NULL;
357  }
358 
359  tout->tm_gmtoff = 3600 * tzhours + 60 * tzmins;
360  }
361 
362  sa_destroy(sa);
363  free(sa);
364 
365  return tout;
366 }
#define NMEA_CSUM_MARK
NMEA Checksum Delimiter.
Definition: NMEATypes.h:50
#define NMEA_START_BYTE2
NMEA Start Byte 2.
Definition: NMEATypes.h:47
#define NMEA_START_BYTE1
NMEA0183 Start Byte 1.
Definition: NMEATypes.h:44
size_t nmea_message_length(const nmea_msg_t *msg)
Calculate number of bytes required to represent message.
Definition: NMEAMessages.c:99
struct tm * nmea_parse_zda(const nmea_msg_t *msg)
Get date/time from NMEA ZDA message.
Definition: NMEAMessages.c:240
void nmea_print_hex(const nmea_msg_t *msg)
Print NMEA message.
Definition: NMEAMessages.c:188
char * nmea_string_hex(const nmea_msg_t *msg)
Return NMEA message as string.
Definition: NMEAMessages.c:174
void nmea_calc_checksum(const nmea_msg_t *msg, uint8_t *cs)
Calculate checksum for NMEA message.
Definition: NMEAMessages.c:37
size_t nmea_flat_array(const nmea_msg_t *msg, char **out)
Convert NMEA message to array of bytes for transmission.
Definition: NMEAMessages.c:127
void nmea_set_checksum(nmea_msg_t *msg)
Set checksum bytes for NMEA message.
Definition: NMEAMessages.c:74
strarray * nmea_parse_fields(const nmea_msg_t *nmsg)
Parse raw data into fields.
Definition: NMEAMessages.c:203
bool nmea_check_checksum(const nmea_msg_t *msg)
Verify checksum bytes of NMEA message.
Definition: NMEAMessages.c:85
void sa_destroy(strarray *sa)
Destroy array and contents.
Definition: strarray.c:182
strarray * sa_new(int entries)
Allocate storage for a new array.
Definition: strarray.c:37
bool sa_create_entry(strarray *array, const int index, const size_t len, const char *src)
Create an string in a given position from a character array and length.
Definition: strarray.c:149
Generic NMEA message structure.
Definition: NMEATypes.h:78
uint8_t raw[80]
Definition: NMEATypes.h:83
strarray fields
If parsed, array of fields.
Definition: NMEATypes.h:85
bool encapsulated
Encapsulated message (all data in raw)
Definition: NMEATypes.h:79
uint8_t checksum
Message Checksum.
Definition: NMEATypes.h:86
char message[3]
Message ID.
Definition: NMEATypes.h:81
char talker[4]
Talker ID (2-4 characters)
Definition: NMEATypes.h:80
uint8_t rawlen
Length of data stored in raw.
Definition: NMEATypes.h:82
Array of strings.
Definition: strarray.h:43
int entries
Maximum number of strings in array, set when calling sa_new()
Definition: strarray.h:44
string * strings
Simple array of string structures.
Definition: strarray.h:45
char * data
Character array, should be null terminated.
Definition: strarray.h:39
size_t length
This should include a terminating null byte where possible.
Definition: strarray.h:38