SELKIELogger  1.0.0
NMEASerial.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 <errno.h>
22 #include <fcntl.h>
23 #include <stdbool.h>
24 #include <stdint.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <termios.h>
29 #include <time.h>
30 #include <unistd.h>
31 
32 #include "NMEAMessages.h"
33 #include "NMEASerial.h"
34 #include "NMEATypes.h"
35 
43 int nmea_openConnection(const char *port, const int baudRate) {
44  return openSerialConnection(port, baudRate);
45 }
46 
52 void nmea_closeConnection(int handle) {
53  close(handle);
54 }
55 
66 bool nmea_readMessage(int handle, nmea_msg_t *out) {
67  static uint8_t buf[NMEA_SERIAL_BUFF];
68  static int index = 0; // Current read position
69  static int hw = 0; // Current array end
70  return nmea_readMessage_buf(handle, out, buf, &index, &hw);
71 }
72 
98 bool nmea_readMessage_buf(int handle, nmea_msg_t *out, uint8_t buf[NMEA_SERIAL_BUFF], int *index, int *hw) {
99  int ti = 0;
100  if ((*hw) < NMEA_SERIAL_BUFF - 1) {
101  errno = 0;
102  ti = read(handle, &(buf[(*hw)]), NMEA_SERIAL_BUFF - (*hw));
103  if (ti >= 0) {
104  (*hw) += ti;
105  } else {
106  if (errno != EAGAIN) {
107  fprintf(stderr, "Unexpected error while reading from serial port (handle ID: 0x%02x)\n",
108  handle);
109  fprintf(stderr, "read returned \"%s\" in readMessage\n", strerror(errno));
110  out->rawlen = 1;
111  out->raw[0] = 0xAA;
112  return false;
113  }
114  }
115  }
116  if (((*hw) == NMEA_SERIAL_BUFF) && (*index) > 0 && (*index) > (*hw) - 8) {
117  // Full buffer, very close to the fill limit
118  // Assume we're full of garbage before index
119  memmove(buf, &(buf[(*index)]), NMEA_SERIAL_BUFF - (*index));
120  (*hw) -= (*index);
121  (*index) = 0;
122  out->rawlen = 1;
123  out->raw[0] = 0xFF;
124  if (ti == 0) { out->raw[0] = 0xFD; }
125  return false;
126  }
127  // Check buf[index] matches either of the valid start bytes
128  while (!(buf[(*index)] == NMEA_START_BYTE1 || buf[(*index)] == NMEA_START_BYTE2) && (*index) < (*hw)) {
129  (*index)++; // Current byte cannot be start of a message, so advance
130  }
131  if ((*index) == (*hw)) {
132  if ((*hw) > 0 && (*index) > 0) {
133  // Move data from index back to zero position
134  memmove(buf, &(buf[(*index)]), NMEA_SERIAL_BUFF - (*index));
135  (*hw) -= (*index);
136  (*index) = 0;
137  }
138  out->rawlen = 1;
139  out->raw[0] = 0xFF;
140  if (ti == 0) { out->raw[0] = 0xFD; }
141  return false;
142  }
143 
144  if (((*hw) - (*index)) < 8) {
145  // Not enough data for any valid message, come back later
146  out->rawlen = 1;
147  out->raw[0] = 0xFF;
148  if (ti == 0) { out->raw[0] = 0xFD; }
149  return false;
150  }
151 
152  int eom = (*index) + 1;
153  // Spec says \r\n, but the USB gateway seems to do \n\n at startup
154  while (!(buf[eom] == NMEA_END_BYTE2 && (buf[eom - 1] == NMEA_END_BYTE1 || buf[eom - 1] == NMEA_END_BYTE2)) &&
155  eom < (*hw)) {
156  eom++; // Current byte cannot be start of a message, so advance
157  }
158 
159  if ((eom - (*index)) > 82) {
160  // No end of message found, so mark as invalid and increment index
161  (*index)++;
162  out->rawlen = 1;
163  out->raw[0] = 0xEE;
164  return false;
165  }
166 
167  if (eom == (*hw)) {
168  // Not incrementing index or marking invalid, as could still be
169  // a valid message once we read the rest of it in.
170  out->rawlen = 1;
171  out->raw[0] = 0xFF;
172  if (ti == 0) { out->raw[0] = 0xFD; }
173  return false;
174  }
175 
176  // So we now have a candidate message that starts at (*index) and ends at eom
177  int som = (*index);
178 
179  // Can only be one of the start bytes, so no explicit check for BYTE1
180  out->encapsulated = (buf[som++] == NMEA_START_BYTE2);
181 
182  out->talker[0] = buf[som++];
183  out->talker[1] = buf[som++];
184  if (out->talker[0] == 'P') {
185  out->talker[2] = buf[som++];
186  out->talker[3] = buf[som++];
187  }
188  out->message[0] = buf[som++];
189  out->message[1] = buf[som++];
190  out->message[2] = buf[som++];
191 
192  if (buf[som++] != ',') {
193  out->rawlen = 1;
194  out->raw[0] = 0xEE; // Invalid message
195  (*index)++;
196  return false;
197  }
198 
199  out->rawlen = 0;
200  while (buf[som] != NMEA_CSUM_MARK && buf[som] != NMEA_END_BYTE1 && buf[som + 1] != NMEA_END_BYTE2) {
201  out->raw[out->rawlen++] = buf[som++];
202  }
203 
204  if (buf[som] == NMEA_CSUM_MARK) {
205  som++; // Skip the delimiter
206  uint8_t cs = 0;
207  char csA = buf[som++];
208  char csB = buf[som++];
209  bool valid = true;
210 
211  switch (csA) {
212  case 'f':
213  case 'e':
214  case 'd':
215  case 'c':
216  case 'b':
217  case 'a':
218  cs = (csA - 'a') + 10;
219  break;
220  case 'F':
221  case 'E':
222  case 'D':
223  case 'C':
224  case 'B':
225  case 'A':
226  cs = (csA - 'A') + 10;
227  break;
228  case '9':
229  case '8':
230  case '7':
231  case '6':
232  case '5':
233  case '4':
234  case '3':
235  case '2':
236  case '1':
237  case '0':
238  cs = (csA - '0');
239  break;
240  default:
241  valid = false;
242  break;
243  }
244 
245  cs = (cs << 4);
246  switch (csB) {
247  case 'f':
248  case 'e':
249  case 'd':
250  case 'c':
251  case 'b':
252  case 'a':
253  cs += (csB - 'a') + 10;
254  break;
255  case 'F':
256  case 'E':
257  case 'D':
258  case 'C':
259  case 'B':
260  case 'A':
261  cs += (csB - 'A') + 10;
262  break;
263  case '9':
264  case '8':
265  case '7':
266  case '6':
267  case '5':
268  case '4':
269  case '3':
270  case '2':
271  case '1':
272  case '0':
273  cs += (csB - '0');
274  break;
275  default:
276  valid = false;
277  break;
278  }
279  out->checksum = cs;
280  cs = 0;
281  nmea_calc_checksum(out, &cs);
282  if (cs != out->checksum) { valid = false; }
283 
284  if (!valid) {
285  out->rawlen = 1;
286  out->raw[0] = 0xEE;
287  (*index)++;
288  return false;
289  }
290  }
291 
292  if ((eom - som) > 1) {
293  // Urmmm....Oops?
294  out->rawlen = 1;
295  out->raw[0] = 0xEE;
296  (*index)++;
297  return false;
298  }
299 
300  (*index) = eom++;
301  if ((*hw) > 0) {
302  // Move data from index back to zero position
303  memmove(buf, &(buf[(*index)]), NMEA_SERIAL_BUFF - (*index));
304  (*hw) -= (*index);
305  (*index) = 0;
306  }
307  return true;
308 }
309 
318 bool nmea_writeMessage(int handle, const nmea_msg_t *out) {
319  char *buf = NULL;
320  size_t size = nmea_flat_array(out, &buf);
321  int ret = write(handle, buf, size);
322  free(buf);
323  return (ret == (ssize_t) size);
324 }
int openSerialConnection(const char *port, const int baudRate)
Open a serial connection at a given baud rate.
Definition: serial.c:44
#define NMEA_CSUM_MARK
NMEA Checksum Delimiter.
Definition: NMEATypes.h:50
bool nmea_readMessage_buf(int handle, nmea_msg_t *out, uint8_t buf[NMEA_SERIAL_BUFF], int *index, int *hw)
Read data from handle, and parse message if able.
Definition: NMEASerial.c:98
#define NMEA_END_BYTE2
NMEA End Byte 1: Line Feed.
Definition: NMEATypes.h:56
#define NMEA_START_BYTE2
NMEA Start Byte 2.
Definition: NMEATypes.h:47
bool nmea_writeMessage(int handle, const nmea_msg_t *out)
Send message to attached device.
Definition: NMEASerial.c:318
bool nmea_readMessage(int handle, nmea_msg_t *out)
Static wrapper around mp_readMessage_buf.
Definition: NMEASerial.c:66
int nmea_openConnection(const char *port, const int baudRate)
Set up a connection to the specified port.
Definition: NMEASerial.c:43
#define NMEA_END_BYTE1
NMEA End Byte 1: Carriage Return.
Definition: NMEATypes.h:53
void nmea_closeConnection(int handle)
Close existing connection.
Definition: NMEASerial.c:52
#define NMEA_START_BYTE1
NMEA0183 Start Byte 1.
Definition: NMEATypes.h:44
#define NMEA_SERIAL_BUFF
Default serial buffer allocation size.
Definition: NMEASerial.h:38
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
Generic NMEA message structure.
Definition: NMEATypes.h:78
uint8_t raw[80]
Definition: NMEATypes.h:83
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