SELKIELogger  1.0.0
GPSSerial.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 // Open serial port and communicate with UBlox GPS
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <stdbool.h>
25 #include <stdint.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <termios.h>
30 #include <time.h>
31 #include <unistd.h>
32 
33 #include "GPSCommands.h"
34 #include "GPSMessages.h"
35 #include "GPSSerial.h"
36 #include "GPSTypes.h"
37 
51 int ubx_openConnection(const char *port, const int initialBaud) {
52  // Use base library function to get initial connection
53  int handle = openSerialConnection(port, initialBaud);
54 
55  // The set baud rate command also disables NMEA and enabled UBX output on UART1
56  if (!ubx_setBaudRate(handle, 115200)) {
57  fprintf(stderr, "Unable to command baud rate change");
58  perror("openConnection");
59  return -1;
60  }
61 
62  fsync(handle);
63  usleep(5E4);
64 
65  // Assuming that we're now configured for 115200 baud, adjust serial settings to match
66  struct termios options;
67  tcgetattr(handle, &options);
68  cfsetispeed(&options, B115200);
69  cfsetospeed(&options, B115200);
70 
71  // Set options using TCSADRAIN in case commands not yet set
72  if (tcsetattr(handle, TCSADRAIN, &options)) { fprintf(stderr, "tcsetattr() failed!\n"); }
73 
74  // The GPS module will stop listening for 1 second if the wrong baud rate is used
75  // i.e. It was already in high rate mode
76  sleep(1);
77 
78  {
79  struct termios check;
80  tcgetattr(handle, &check);
81  if (cfgetispeed(&check) != B115200) {
82  fprintf(stderr, "Unable to set target baud. Wanted %d, got %d\n", 115200,
83  flag_to_baud(cfgetispeed(&check)));
84  // return -1;
85  }
86  }
87 
88  /* Send the command again, to make sure the protocol settings get applied
89  *
90  * If we were already in high rate mode the initial rate change command
91  * would fail silently, and the GPS might be transmitting at the right
92  * baud but with the wrong protocols
93  */
94  if (!ubx_setBaudRate(handle, 115200)) {
95  fprintf(stderr, "Unable to command baud rate change");
96  perror("openConnection");
97  return -1;
98  }
99  usleep(5E4);
100 
101  return handle;
102 }
103 
111 void ubx_closeConnection(int handle) {
112  close(handle);
113 }
114 
125 bool ubx_readMessage(int handle, ubx_message *out) {
126  static uint8_t buf[UBX_SERIAL_BUFF];
127  static int index = 0; // Current read position
128  static int hw = 0; // Current array end
129  return ubx_readMessage_buf(handle, out, buf, &index, &hw);
130 }
131 
158 bool ubx_readMessage_buf(int handle, ubx_message *out, uint8_t buf[UBX_SERIAL_BUFF], int *index, int *hw) {
159  int ti = 0;
160  if ((*hw) < UBX_SERIAL_BUFF - 1) {
161  errno = 0;
162  ti = read(handle, &(buf[(*hw)]), UBX_SERIAL_BUFF - (*hw));
163  if (ti >= 0) {
164  (*hw) += ti;
165  } else {
166  if (errno != EAGAIN) {
167  fprintf(stderr, "Unexpected error while reading from serial port (handle ID: 0x%02x)\n",
168  handle);
169  fprintf(stderr, "read returned \"%s\" in readMessage\n", strerror(errno));
170  out->sync1 = 0xAA;
171  return false;
172  }
173  }
174  }
175 
176  // Check buf[index] is valid ID
177  while (!(buf[(*index)] == 0xB5) && (*index) < (*hw)) {
178  (*index)++; // Current byte cannot be start of a message, so advance
179  }
180  if ((*index) == (*hw)) {
181  if ((*hw) > 0 && (*index) > 0) {
182  // Move data from index back to zero position
183  memmove(buf, &(buf[(*index)]), UBX_SERIAL_BUFF - (*index));
184  (*hw) -= (*index);
185  (*index) = 0;
186  }
187  // fprintf(stderr, "Buffer empty - returning\n");
188  out->sync1 = 0xFF;
189  if (ti == 0) { out->sync1 = 0xFD; }
190  return false;
191  }
192 
193  if (((*hw) - (*index)) < 8) {
194  // Not enough data for any valid message, come back later
195  out->sync1 = 0xFF;
196  return false;
197  }
198 
199  // Set length of data required for message
200 
201  out->sync1 = buf[(*index)];
202  out->sync2 = buf[(*index) + 1];
203  out->msgClass = buf[(*index) + 2];
204  out->msgID = buf[(*index) + 3];
205  out->length = buf[(*index) + 4] + (buf[(*index) + 5] << 8);
206 
207  if (out->sync2 != 0x62) {
208  // Found first sync byte, but second not valid
209  // Advance the index so we skip this message and go back around
210  (*index)++;
211  out->sync1 = 0xFF;
212  return false;
213  }
214 
215  if (((*hw) - (*index)) < (out->length + 8)) {
216  // Not enough data for this message yet, so mark output invalid
217  out->sync1 = 0xFF;
218  if (ti == 0) { out->sync1 = 0xFD; }
219  // Go back around, but leave index where it is so we will pick up
220  // from the same point in the buffer
221  return false;
222  }
223 
224  out->extdata = NULL;
225  if (out->length <= 256) {
226  for (uint8_t i = 0; i < out->length; i++) {
227  out->data[i] = buf[(*index) + 6 + i];
228  }
229  } else {
230  out->extdata = calloc(out->length, 1);
231  for (uint16_t i = 0; i < out->length; i++) {
232  out->extdata[i] = buf[(*index) + 6 + i];
233  }
234  }
235 
236  out->csumA = buf[(*index) + 6 + out->length];
237  out->csumB = buf[(*index) + 7 + out->length];
238 
239  bool valid = ubx_check_checksum(out);
240  if (valid) {
241  (*index) += 8 + out->length;
242  } else {
243  out->sync1 = 0xEE; // Use 0xEE as "Found, but invalid", leaving 0xFF as
244  // "No message"
245  if (out->extdata) {
246  free(out->extdata);
247  out->extdata = NULL;
248  }
249  (*index)++;
250  }
251  if ((*hw) > 0) {
252  // Move data from index back to zero position
253  memmove(buf, &(buf[(*index)]), UBX_SERIAL_BUFF - (*index));
254  (*hw) -= (*index);
255  (*index) = 0;
256  }
257  return valid;
258 }
259 
276 bool ubx_waitForMessage(const int handle, const uint8_t msgClass, const uint8_t msgID, const int maxDelay,
277  ubx_message *out) {
278  const time_t deadline = time(NULL) + maxDelay;
279  while (time(NULL) < deadline) {
280  bool rms = ubx_readMessage(handle, out);
281  if (rms) {
282  if ((out->msgClass == msgClass) && (out->msgID == msgID)) { return true; }
283  } else {
284  usleep(50);
285  }
286  }
287  return false;
288 }
289 
298 bool ubx_writeMessage(int handle, const ubx_message *out) {
299  if (!ubx_check_checksum(out) || (out->sync1 != 0xB5) || (out->sync2 != 0x62)) {
300  fprintf(stderr, "Attempted to send invalid message\n");
301  return false;
302  }
303 
304  uint8_t head[6] = {out->sync1,
305  out->sync2,
306  out->msgClass,
307  out->msgID,
308  (uint8_t)(out->length & 0xFF),
309  (uint8_t)(out->length >> 8)};
310  uint8_t tail[2] = {out->csumA, out->csumB};
311  ssize_t ret = write(handle, head, 6);
312  if (ret != 6) {
313  perror("writeMessage:head");
314  return false;
315  }
316 
317  /*
318  * The results of a zero length write are undefined for anything that
319  * isn't a regular file. Zero length messages are valid, so guard this
320  * whole section.
321  */
322  if (out->length > 0) {
323  if (out->length <= 256) {
324  ret = write(handle, out->data, out->length);
325  } else {
326  ret = write(handle, out->extdata, out->length);
327  }
328  if (ret != out->length) {
329  perror("writeMessage:data");
330  return false;
331  }
332  }
333 
334  ret = write(handle, tail, 2);
335  if (ret != 2) {
336  perror("writeMessage:tail");
337  return false;
338  }
339  return true;
340 }
int openSerialConnection(const char *port, const int baudRate)
Open a serial connection at a given baud rate.
Definition: serial.c:44
int flag_to_baud(const int flag)
Convert a termios baud rate flag to a numerical value.
Definition: serial.c:166
bool ubx_waitForMessage(const int handle, const uint8_t msgClass, const uint8_t msgID, const int maxDelay, ubx_message *out)
Read (and discard) messages until required message seen or timeout reached.
Definition: GPSSerial.c:276
bool ubx_writeMessage(int handle, const ubx_message *out)
Send message to attached device.
Definition: GPSSerial.c:298
bool ubx_readMessage_buf(int handle, ubx_message *out, uint8_t buf[UBX_SERIAL_BUFF], int *index, int *hw)
Read data from handle, and parse message if able.
Definition: GPSSerial.c:158
bool ubx_readMessage(int handle, ubx_message *out)
Static wrapper around ubx_readMessage_buf()
Definition: GPSSerial.c:125
#define UBX_SERIAL_BUFF
Serial buffer size.
Definition: GPSSerial.h:39
int ubx_openConnection(const char *port, const int initialBaud)
Set up a connection to a UBlox module on a given port.
Definition: GPSSerial.c:51
void ubx_closeConnection(int handle)
Close a connection opened with ubx_openConnection()
Definition: GPSSerial.c:111
bool ubx_setBaudRate(const int handle, const uint32_t baud)
Send UBX port configuration to switch baud rate.
Definition: GPSCommands.c:53
bool ubx_check_checksum(const ubx_message *msg)
Verify checksum bytes of UBX message.
Definition: GPSMessages.c:82
Internal representation of a UBX message.
Definition: GPSTypes.h:80
uint8_t csumB
Checksum part B.
Definition: GPSTypes.h:88
uint8_t * extdata
Definition: GPSTypes.h:89
uint8_t sync1
Should always be 0xB5.
Definition: GPSTypes.h:81
uint8_t sync2
Should always be 0x62.
Definition: GPSTypes.h:82
uint8_t data[256]
Data if length <= 256.
Definition: GPSTypes.h:86
uint8_t msgClass
A value from ubx_class.
Definition: GPSTypes.h:83
uint8_t msgID
Message ID byte.
Definition: GPSTypes.h:84
uint16_t length
Message length.
Definition: GPSTypes.h:85
uint8_t csumA
Checksum part A.
Definition: GPSTypes.h:87