SELKIELogger  1.0.0
LoggerGPS.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 "Logger.h"
22 
23 #include "LoggerGPS.h"
24 #include "LoggerSignals.h"
25 
42 void *gps_setup(void *ptargs) {
43  log_thread_args_t *args = (log_thread_args_t *)ptargs;
44  gps_params *gpsInfo = (gps_params *)args->dParams;
45 
46  gpsInfo->handle = ubx_openConnection(gpsInfo->portName, gpsInfo->initialBaud);
47  if (gpsInfo->handle < 0) {
48  log_error(args->pstate, "[GPS:%s] Unable to open a connection", args->tag);
49  args->returnCode = -1;
50  return NULL;
51  }
52 
53  log_info(args->pstate, 1, "[GPS:%s] Configuring GPS...", args->tag);
54  {
55  bool msgSuccess = true;
56  errno = 0;
57  log_info(args->pstate, 2, "[GPS:%s] Enabling log messages", args->tag);
58  msgSuccess &= ubx_enableLogMessages(gpsInfo->handle);
59  // The usleep commands are probably not required, but mindful
60  // that there's a limited RX buffer on the GPS module
61  usleep(5E3);
62  msgSuccess &= ubx_enableGalileo(gpsInfo->handle);
63  log_info(args->pstate, 2, "[GPS:%s] Enabling Galileo (GNSS Reset)", args->tag);
64 
65  // This one *is* necessary, as enabling Galileo can require a GNSS reset
66  sleep(3);
67  // 500ms Update rate, new output each time
68  msgSuccess &= ubx_setNavigationRate(gpsInfo->handle, 500, 1);
69  usleep(5E3);
70  msgSuccess &= ubx_setI2CAddress(gpsInfo->handle, 0x0a);
71  usleep(5E3);
72  log_info(args->pstate, 2, "[GPS:%s] Configuring message rates", args->tag);
73 
74  // NAV-PVT on every update
75  msgSuccess &= ubx_setMessageRate(gpsInfo->handle, 0x01, 0x07, 1);
76  usleep(5E3);
77 
78  // NAV-SAT on every 100th update
79  msgSuccess &= ubx_setMessageRate(gpsInfo->handle, 0x01, 0x35, 120);
80  usleep(5E3);
81 
82  // NAV-TIMEUTC on every update
83  msgSuccess &= ubx_setMessageRate(gpsInfo->handle, 0x01, 0x21, 1);
84  usleep(5E3);
85 
86  log_info(args->pstate, 2, "[GPS:%s] Polling for status information", args->tag);
87  msgSuccess &= ubx_pollMessage(gpsInfo->handle, 0x0A, 0x04);
88  usleep(5E3);
89 
90  // Poll for GNSS status (MON)
91  msgSuccess &= ubx_pollMessage(gpsInfo->handle, 0x0A, 0x28);
92  usleep(5E3);
93 
94  // Poll again for CFG
95  msgSuccess &= ubx_pollMessage(gpsInfo->handle, 0x06, 0x3E);
96  usleep(5E3);
97 
98  if (!msgSuccess) {
99  log_error(args->pstate, "[GPS:%s] Unable to configure: %s", args->tag,
100  strerror(errno));
101  ubx_closeConnection(gpsInfo->handle);
102  args->returnCode = -2;
103  return NULL;
104  }
105  log_info(args->pstate, 1, "[GPS:%s] Configuration completed", args->tag);
106  }
107  args->returnCode = 0;
108  return NULL;
109 }
110 
122 void *gps_logging(void *ptargs) {
124  log_thread_args_t *args = (log_thread_args_t *)ptargs;
125  gps_params *gpsInfo = (gps_params *)args->dParams;
126 
127  log_info(args->pstate, 1, "[GPS:%s] Logging thread started", args->tag);
128 
129  uint8_t *buf = calloc(UBX_SERIAL_BUFF, sizeof(uint8_t));
130  int ubx_index = 0;
131  int ubx_hw = 0;
132  while (!shutdownFlag) {
133  ubx_message out = {0};
134  if (ubx_readMessage_buf(gpsInfo->handle, &out, buf, &ubx_index, &ubx_hw)) {
135  uint8_t *data = NULL;
136  ssize_t len = ubx_flat_array(&out, &data);
137  bool handled = false;
138  if (out.msgClass == UBXNAV && out.msgID == 0x21) {
139  // Extract GPS ToW
140  uint32_t ts = out.data[0] + (out.data[1] << 8) +
141  (out.data[2] << 16) + (out.data[3] << 24);
142  msg_t *utc =
144  if (!queue_push(args->logQ, utc)) {
145  log_error(args->pstate,
146  "[GPS:%s] Error pushing message to queue",
147  args->tag);
148  msg_destroy(utc);
149  args->returnCode = -1;
150  pthread_exit(&(args->returnCode));
151  }
152  handled = true;
153  } else if (out.msgClass == UBXNAV && out.msgID == 0x07) {
154  // NAV-PVT
155  ubx_nav_pvt nav = {0};
156  if (!ubx_decode_nav_pvt(&out, &nav)) {
157  log_error(args->pstate,
158  "[GPS:%s] Unable to decode NAV-PVT message",
159  args->tag);
160  } else {
161  float posData[6] = {
162  nav.longitude, nav.latitude,
163  nav.height * 1E-3, nav.ASL * 1E-3,
164  nav.horizAcc * 1E-3, nav.vertAcc * 1E-3};
165 
166  float velData[7] = {
167  nav.northV * 1E-3, nav.eastV * 1E-3,
168  nav.downV * 1E-3, nav.groundSpeed * 1E-3,
169  nav.heading, nav.speedAcc * 1E-3,
170  nav.headingAcc};
171 
172  float dt[8] = {nav.year, nav.month, nav.day,
173  nav.hour, nav.minute, nav.second,
174  nav.nanosecond, nav.accuracy};
175 
176  // clang-format off
177  msg_t *mnav = msg_new_float_array(gpsInfo->sourceNum, 4, 6, posData);
178  msg_t *mvel = msg_new_float_array(gpsInfo->sourceNum, 5, 7, velData);
179  msg_t *mdt = msg_new_float_array(gpsInfo->sourceNum, 6, 8, dt);
180  // clang-format on
181 
182  if (!queue_push(args->logQ, mnav) ||
183  !queue_push(args->logQ, mvel) ||
184  !queue_push(args->logQ, mdt)) {
185  log_error(
186  args->pstate,
187  "[GPS:%s] Error pushing messages to queue",
188  args->tag);
189  msg_destroy(mnav);
190  msg_destroy(mvel);
191  msg_destroy(mdt);
192  args->returnCode = -1;
193  pthread_exit(&(args->returnCode));
194  }
195  handled = true;
196  }
197  }
198  if (!handled || gpsInfo->dumpAll) {
199  msg_t *sm = msg_new_bytes(gpsInfo->sourceNum, 3, len, data);
200  if (!queue_push(args->logQ, sm)) {
201  log_error(args->pstate,
202  "[GPS:%s] Error pushing message to queue",
203  args->tag);
204  msg_destroy(sm);
205  args->returnCode = -1;
206  pthread_exit(&(args->returnCode));
207  }
208  }
209  if (data) {
210  // Copied into message, so can safely free here
211  free(data);
212  }
213  // Do not destroy or free sm (or other msg_t objects) here
214  // After pushing it to the queue, it is the responsibility of the
215  // consumer to dispose of it after use.
216  } else {
217  if (!(out.sync1 == 0xFF || out.sync1 == 0xFD || out.sync1 == 0xEE)) {
218  // 0xFF, 0xFD and 0xEE are used to signal recoverable
219  // states that resulted in no valid message.
220  //
221  // 0xFF and 0xFD indicate an out of data error, which is
222  // not a problem for serial monitoring, but might indicate
223  // EOF when reading from file
224  //
225  // 0xEE indicates an invalid message following valid sync bytes
226  log_error(args->pstate,
227  "[GPS:%s] Error signalled from gps_readMessage_buf",
228  args->tag);
229  args->returnCode = -2;
230  free(buf);
231  if (out.extdata) { free(out.extdata); }
232  pthread_exit(&(args->returnCode));
233  }
234  // We've already exited (via pthread_exit) for error
235  // cases, so at this point sleep briefly and wait for
236  // more data
237  usleep(SERIAL_SLEEP);
238  }
239 
240  // Clean up before next iteration
241  if (out.extdata) { // Similarly, we are finished with this copy
242  free(out.extdata);
243  }
244  }
245  free(buf);
246  log_info(args->pstate, 1, "[GPS:%s] Logging thread exiting", args->tag);
247  pthread_exit(NULL);
248  return NULL; // Superfluous, as returning zero via pthread_exit above
249 }
250 
257 void *gps_shutdown(void *ptargs) {
258  log_thread_args_t *args = (log_thread_args_t *)ptargs;
259  gps_params *gpsInfo = (gps_params *)args->dParams;
260 
261  if (gpsInfo->handle >= 0) { // Admittedly 0 is unlikely
262  ubx_closeConnection(gpsInfo->handle);
263  }
264  gpsInfo->handle = -1;
265  if (gpsInfo->sourceName) {
266  free(gpsInfo->sourceName);
267  gpsInfo->sourceName = NULL;
268  }
269  if (gpsInfo->portName) {
270  free(gpsInfo->portName);
271  gpsInfo->portName = NULL;
272  }
273  return NULL;
274 }
275 
284 void *gps_channels(void *ptargs) {
285  log_thread_args_t *args = (log_thread_args_t *)ptargs;
286  gps_params *gpsInfo = (gps_params *)args->dParams;
287 
288  msg_t *m_sn = msg_new_string(gpsInfo->sourceNum, SLCHAN_NAME, strlen(gpsInfo->sourceName),
289  gpsInfo->sourceName);
290 
291  if (!queue_push(args->logQ, m_sn)) {
292  log_error(args->pstate, "[GPS:%s] Error pushing channel name to queue", args->tag);
293  msg_destroy(m_sn);
294  args->returnCode = -1;
295  pthread_exit(&(args->returnCode));
296  }
297 
298  strarray *channels = sa_new(7);
299  sa_create_entry(channels, SLCHAN_NAME, 4, "Name");
300  sa_create_entry(channels, SLCHAN_MAP, 8, "Channels");
301  sa_create_entry(channels, SLCHAN_TSTAMP, 9, "Timestamp");
302  sa_create_entry(channels, SLCHAN_RAW, 7, "Raw UBX");
303  sa_create_entry(channels, 4, 8, "Position");
304  sa_create_entry(channels, 5, 8, "Velocity");
305  sa_create_entry(channels, 6, 8, "DateTime");
306 
307  msg_t *m_cmap = msg_new_string_array(gpsInfo->sourceNum, SLCHAN_MAP, channels);
308 
309  if (!queue_push(args->logQ, m_cmap)) {
310  log_error(args->pstate, "[GPS:%s] Error pushing channel map to queue", args->tag);
311  msg_destroy(m_cmap);
312  sa_destroy(channels);
313  free(channels);
314  args->returnCode = -1;
315  pthread_exit(&(args->returnCode));
316  }
317 
318  sa_destroy(channels);
319  free(channels);
320  return NULL;
321 }
322 
328  .logging = &gps_logging,
329  .shutdown = &gps_shutdown,
330  .channels = &gps_channels};
331  return cb;
332 }
333 
338  gps_params gp = {.portName = NULL,
339  .sourceNum = SLSOURCE_GPS,
340  .initialBaud = 9600,
341  .targetBaud = 115200,
342  .handle = -1,
343  .dumpAll = false};
344  return gp;
345 }
346 
353  if (lta->dParams) {
354  log_error(lta->pstate, "[GPS:%s] Refusing to reconfigure", lta->tag);
355  return false;
356  }
357 
358  gps_params *gp = calloc(1, sizeof(gps_params));
359  if (!gp) {
360  log_error(lta->pstate, "[GPS:%s] Unable to allocate memory for device parameters",
361  lta->tag);
362  return false;
363  }
364  (*gp) = gps_getParams();
365 
366  config_kv *t = NULL;
367  if ((t = config_get_key(s, "name"))) {
368  gp->sourceName = config_qstrdup(t->value);
369  } else {
370  // Must set a name, so nick the tag value
371  gp->sourceName = strdup(lta->tag);
372  }
373  t = NULL;
374 
375  if ((t = config_get_key(s, "port"))) { gp->portName = config_qstrdup(t->value); }
376  t = NULL;
377 
378  if ((t = config_get_key(s, "dumpall"))) { gp->dumpAll = config_parse_bool(t->value); }
379  t = NULL;
380 
381  if ((t = config_get_key(s, "baud"))) {
382  errno = 0;
383  gp->targetBaud = strtol(t->value, NULL, 0);
384  if (errno) {
385  log_error(lta->pstate, "[GPS:%s] Error parsing baud rate: %s", lta->tag,
386  strerror(errno));
387  free(gp);
388  return false;
389  }
390  }
391  t = NULL;
392 
393  if ((t = config_get_key(s, "initialbaud"))) {
394  errno = 0;
395  gp->initialBaud = strtol(t->value, NULL, 0);
396  if (errno) {
397  log_error(lta->pstate, "[GPS:%s] Error parsing initial baud rate: %s",
398  lta->tag, strerror(errno));
399  free(gp);
400  return false;
401  }
402  }
403  t = NULL;
404 
405  if ((t = config_get_key(s, "sourcenum"))) {
406  errno = 0;
407  int sn = strtol(t->value, NULL, 0);
408  if (errno) {
409  log_error(lta->pstate, "[GPS:%s] Error parsing source number: %s",
410  lta->tag, strerror(errno));
411  free(gp);
412  return false;
413  }
414  if (sn < 0) {
415  log_error(lta->pstate, "[GPS:%s] Invalid source number (%s)", lta->tag,
416  t->value);
417  free(gp);
418  return false;
419  }
420  if (sn < 10) {
421  gp->sourceNum += sn;
422  } else {
423  gp->sourceNum = sn;
424  if (sn < SLSOURCE_GPS || sn > (SLSOURCE_GPS + 0x0F)) {
425  log_warning(
426  lta->pstate,
427  "[GPS:%s] Unexpected Source ID number (0x%02x)- this may cause analysis problems",
428  lta->tag, sn);
429  }
430  }
431  }
432  t = NULL;
433  lta->dParams = gp;
434  return true;
435 }
int config_parse_bool(const char *b)
Parse string to boolean.
Definition: LoggerConfig.c:240
char * config_qstrdup(const char *c)
Duplicate string, stripping optional leading/trailing quote marks.
Definition: LoggerConfig.c:271
config_kv * config_get_key(const config_section *cs, const char *kn)
Find configugration key within specific section, by name.
Definition: LoggerConfig.c:204
void signalHandlersBlock(void)
Block signals that we have handlers for.
msg_t * msg_new_string(const uint8_t source, const uint8_t type, const size_t len, const char *str)
Create a new message with a single string embedded.
Definition: messages.c:84
void msg_destroy(msg_t *msg)
Destroy a message.
Definition: messages.c:349
msg_t * msg_new_string_array(const uint8_t source, const uint8_t type, const strarray *array)
Create a new message containing an array of strings.
Definition: messages.c:116
msg_t * msg_new_bytes(const uint8_t source, const uint8_t type, const size_t len, const uint8_t *bytes)
Create a new message containing raw binary data.
Definition: messages.c:147
msg_t * msg_new_timestamp(const uint8_t source, const uint8_t type, const uint32_t ts)
Create a timestamp message.
Definition: messages.c:57
msg_t * msg_new_float_array(const uint8_t source, const uint8_t type, const size_t entries, const float *array)
Create a new message containing an array of floating point data.
Definition: messages.c:176
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
#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
#define SLCHAN_TSTAMP
Source timestamp (milliseconds, arbitrary epoch)
Definition: sources.h:99
#define SLCHAN_RAW
Raw device data (Not mandatory)
Definition: sources.h:100
#define SLCHAN_MAP
Channel name map (excludes log channels)
Definition: sources.h:98
#define SLCHAN_NAME
Name of source device.
Definition: sources.h:97
void * gps_channels(void *ptargs)
GPS Channel map.
Definition: LoggerGPS.c:284
void * gps_setup(void *ptargs)
GPS Setup.
Definition: LoggerGPS.c:42
void * gps_shutdown(void *ptargs)
GPS Shutdown.
Definition: LoggerGPS.c:257
device_callbacks gps_getCallbacks()
Fill out device callback functions for logging.
Definition: LoggerGPS.c:326
gps_params gps_getParams()
Fill out default GPS parameters.
Definition: LoggerGPS.c:337
bool gps_parseConfig(log_thread_args_t *lta, config_section *s)
Parse configuration section.
Definition: LoggerGPS.c:352
void * gps_logging(void *ptargs)
GPS logging (with pthread function signature)
Definition: LoggerGPS.c:122
#define SLSOURCE_GPS
GPS (or other satellite navigation) sources.
Definition: sources.h:62
atomic_bool shutdownFlag
Trigger clean software shutdown.
Definition: LoggerSignals.c:34
#define SERIAL_SLEEP
Default serial wait time.
Definition: Logger.h.in:51
void log_info(const program_state *s, const int level, const char *format,...)
Output formatted information message at a given level.
Definition: logging.c:125
void log_warning(const program_state *s, const char *format,...)
Output formatted warning message.
Definition: logging.c:86
void log_error(const program_state *s, const char *format,...)
Output formatted error message.
Definition: logging.c:47
bool queue_push(msgqueue *queue, msg_t *msg)
Add a message to the tail of the queue.
Definition: queue.c:103
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
bool ubx_pollMessage(const int handle, const uint8_t msgClass, const uint8_t msgID)
Request specific message by class and ID.
Definition: GPSCommands.c:128
bool ubx_setI2CAddress(const int handle, const uint8_t addr)
Set I2C address.
Definition: GPSCommands.c:250
bool ubx_setNavigationRate(const int handle, const uint16_t interval, const uint16_t outputRate)
Set UBX navigation calculation and reporting rate.
Definition: GPSCommands.c:171
bool ubx_enableGalileo(const int handle)
Enable Galileo constellation use.
Definition: GPSCommands.c:140
bool ubx_enableLogMessages(const int handle)
Enable log/information messages from GPS device.
Definition: GPSCommands.c:199
bool ubx_setMessageRate(const int handle, const uint8_t msgClass, const uint8_t msgID, const uint8_t rate)
Send UBX rate command to enable/disable message types.
Definition: GPSCommands.c:95
bool ubx_decode_nav_pvt(const ubx_message *msg, ubx_nav_pvt *out)
Decode UBX NAV-PVT message.
Definition: GPSMessages.c:175
size_t ubx_flat_array(const ubx_message *msg, uint8_t **out)
Convert UBX message to flat array of bytes.
Definition: GPSMessages.c:100
Represent a key=value pair.
Definition: LoggerConfig.h:42
char * value
Configuration item value.
Definition: LoggerConfig.h:44
Configuration file section.
Definition: LoggerConfig.h:54
Device specific function information.
Definition: Logger.h.in:72
device_fn startup
Called serially at startup, opens devices etc.
Definition: Logger.h.in:73
GPS Device specific parameters.
Definition: LoggerGPS.h:50
int handle
Handle for currently opened device.
Definition: LoggerGPS.h:56
char * portName
Target port name.
Definition: LoggerGPS.h:51
bool dumpAll
Dump all GPS messages to output queue.
Definition: LoggerGPS.h:57
int targetBaud
Baud rate for operations (currently unused)
Definition: LoggerGPS.h:55
char * sourceName
User defined name for this GPS.
Definition: LoggerGPS.h:52
uint8_t sourceNum
Source ID for messages.
Definition: LoggerGPS.h:53
int initialBaud
Baud rate for initial configuration.
Definition: LoggerGPS.h:54
Logging thread information.
Definition: Logger.h.in:86
msgqueue * logQ
Main message queue. Pushed to by threads, consumed by main()
Definition: Logger.h.in:89
char * tag
Tag/source name for messages etc.
Definition: Logger.h.in:87
void * dParams
Device/Thread specific data.
Definition: Logger.h.in:92
program_state * pstate
Current program state, used for logging.
Definition: Logger.h.in:90
int returnCode
Thread return code (output)
Definition: Logger.h.in:93
Queuable message.
Definition: messages.h:71
Array of strings.
Definition: strarray.h:43
Internal representation of a UBX message.
Definition: GPSTypes.h:80
uint8_t * extdata
Definition: GPSTypes.h:89
uint8_t sync1
Should always be 0xB5.
Definition: GPSTypes.h:81
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
Represent decoded NAV-PVT message.
Definition: GPSTypes.h:101
int32_t downV
Velocity (Down, mm/s)
Definition: GPSTypes.h:126
uint8_t second
Second (UTC)
Definition: GPSTypes.h:108
uint8_t minute
Minute (UTC)
Definition: GPSTypes.h:107
int32_t ASL
Height above mean sea level (?datum)
Definition: GPSTypes.h:121
uint32_t accuracy
Estimated time accuracy (ns)
Definition: GPSTypes.h:112
float latitude
WGS84 Latitude.
Definition: GPSTypes.h:119
uint32_t vertAcc
Vertical accuracy estimate.
Definition: GPSTypes.h:123
int32_t headingAcc
Heading accuracy estimate.
Definition: GPSTypes.h:130
int32_t speedAcc
Speed/velocity accuracy estimate.
Definition: GPSTypes.h:129
uint16_t year
Calendar year.
Definition: GPSTypes.h:103
int32_t groundSpeed
Ground Speed (mm/s)
Definition: GPSTypes.h:127
int32_t northV
Velocity (North, mm/s)
Definition: GPSTypes.h:124
int32_t height
WGS84 Height.
Definition: GPSTypes.h:120
uint32_t horizAcc
Horizontal accuracy estimate (mm)
Definition: GPSTypes.h:122
float heading
Motion heading.
Definition: GPSTypes.h:128
int32_t eastV
Velocity (East, mm/s)
Definition: GPSTypes.h:125
int32_t nanosecond
+/- nanosecond (UTC)
Definition: GPSTypes.h:113
uint8_t month
Calendar month.
Definition: GPSTypes.h:104
float longitude
WGS84 Longitude.
Definition: GPSTypes.h:118
uint8_t day
Calendar day.
Definition: GPSTypes.h:105
uint8_t hour
Hour (UTC)
Definition: GPSTypes.h:106