SELKIELogger  1.0.0
LoggerNMEA.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 "LoggerNMEA.h"
24 #include "LoggerSignals.h"
25 
34 void *nmea_setup(void *ptargs) {
35  log_thread_args_t *args = (log_thread_args_t *)ptargs;
36  nmea_params *nmeaInfo = (nmea_params *)args->dParams;
37 
38  nmeaInfo->handle = nmea_openConnection(nmeaInfo->portName, nmeaInfo->baudRate);
39  if (nmeaInfo->handle < 0) {
40  log_error(args->pstate, "[NMEA:%s] Unable to open a connection", args->tag);
41  args->returnCode = -1;
42  return NULL;
43  }
44 
45  log_info(args->pstate, 2, "[NMEA:%s] Connected", args->tag);
46  args->returnCode = 0;
47  return NULL;
48 }
49 
60 void *nmea_logging(void *ptargs) {
62  log_thread_args_t *args = (log_thread_args_t *)ptargs;
63  nmea_params *nmeaInfo = (nmea_params *)args->dParams;
64 
65  log_info(args->pstate, 1, "[NMEA:%s] Logging thread started", args->tag);
66 
67  uint8_t *buf = calloc(NMEA_SERIAL_BUFF, sizeof(uint8_t));
68  int nmea_index = 0;
69  int nmea_hw = 0;
70  while (!shutdownFlag) {
71  nmea_msg_t out = {0};
72  if (nmea_readMessage_buf(nmeaInfo->handle, &out, buf, &nmea_index, &nmea_hw)) {
73  char *data = NULL;
74  ssize_t len = nmea_flat_array(&out, &data);
75  bool handled = false;
76 
77  if ((strncmp(out.talker, "II", 2) == 0) &&
78  (strncmp(out.message, "ZDA", 3) == 0)) {
79  struct tm *t = nmea_parse_zda(&out);
80  if (t != NULL) {
81  time_t epoch = mktime(t) - t->tm_gmtoff;
82  if (epoch != (time_t)(-1)) {
83  // clang-format off
84  msg_t *tm = msg_new_timestamp(nmeaInfo->sourceNum, 4, epoch);
85  // clang-format on
86  if (!queue_push(args->logQ, tm)) {
87  log_error(
88  args->pstate,
89  "[NMEA:%s] Error pushing message to queue",
90  args->tag);
91  msg_destroy(tm);
92  free(t);
93  args->returnCode = -1;
94  pthread_exit(&(args->returnCode));
95  }
96  handled = true; // Suppress ZDA messages
97  }
98  }
99  }
100  if (!handled) {
101  msg_t *sm = msg_new_bytes(nmeaInfo->sourceNum, 3, len,
102  (uint8_t *)data);
103  if (!queue_push(args->logQ, sm)) {
104  log_error(args->pstate,
105  "[NMEA:%s] Error pushing message to queue",
106  args->tag);
107  msg_destroy(sm);
108  args->returnCode = -1;
109  pthread_exit(&(args->returnCode));
110  }
111  }
112  if (data) {
113  // Copied into message, so can safely free here
114  free(data);
115  }
116  // Do not destroy or free sm here
117  // After pushing it to the queue, it is the responsibility of the
118  // consumer to dispose of it after use.
119  } else {
120  if (!(out.raw[0] == 0xFF || out.raw[0] == 0xFD || out.raw[0] == 0xEE)) {
121  // 0xFF, 0xFD and 0xEE are used to signal recoverable
122  // states that resulted in no valid message.
123  //
124  // 0xFF and 0xFD indicate an out of data error, which is
125  // not a problem for serial monitoring, but might indicate
126  // EOF when reading from file
127  //
128  // 0xEE indicates an invalid message following valid sync
129  // bytes
130  log_error(args->pstate,
131  "[NMEA:%s] Error signalled from nmea_readMessage_buf",
132  args->tag);
133  args->returnCode = -2;
134  free(buf);
135  sa_destroy(&(out.fields));
136  pthread_exit(&(args->returnCode));
137  }
138  // We've already exited (via pthread_exit) for error
139  // cases, so at this point sleep briefly and wait for
140  // more data
141  usleep(SERIAL_SLEEP);
142  }
143 
144  // Clean up before next iteration
145  sa_destroy(&(out.fields));
146  }
147  free(buf);
148  log_info(args->pstate, 1, "[NMEA:%s] Logging thread exiting", args->tag);
149  pthread_exit(NULL);
150  return NULL; // Superfluous, as returning zero via pthread_exit above
151 }
152 
159 void *nmea_shutdown(void *ptargs) {
160  log_thread_args_t *args = (log_thread_args_t *)ptargs;
161  nmea_params *nmeaInfo = (nmea_params *)args->dParams;
162 
163  if (nmeaInfo->handle >= 0) { // Admittedly 0 is unlikely
164  nmea_closeConnection(nmeaInfo->handle);
165  }
166  nmeaInfo->handle = -1;
167  if (nmeaInfo->sourceName) {
168  free(nmeaInfo->sourceName);
169  nmeaInfo->sourceName = NULL;
170  }
171  if (nmeaInfo->portName) {
172  free(nmeaInfo->portName);
173  nmeaInfo->portName = NULL;
174  }
175  return NULL;
176 }
177 
186 void *nmea_channels(void *ptargs) {
187  log_thread_args_t *args = (log_thread_args_t *)ptargs;
188  nmea_params *nmeaInfo = (nmea_params *)args->dParams;
189 
190  msg_t *m_sn = msg_new_string(nmeaInfo->sourceNum, SLCHAN_NAME,
191  strlen(nmeaInfo->sourceName), nmeaInfo->sourceName);
192 
193  if (!queue_push(args->logQ, m_sn)) {
194  log_error(args->pstate, "[NMEA:%s] Error pushing channel name to queue",
195  args->tag);
196  msg_destroy(m_sn);
197  args->returnCode = -1;
198  pthread_exit(&(args->returnCode));
199  }
200 
201  strarray *channels = sa_new(5);
202  sa_create_entry(channels, SLCHAN_NAME, 4, "Name");
203  sa_create_entry(channels, SLCHAN_MAP, 8, "Channels");
204  sa_create_entry(channels, SLCHAN_TSTAMP, 9, "Timestamp");
205  sa_create_entry(channels, SLCHAN_RAW, 8, "Raw NMEA");
206  sa_create_entry(channels, 4, 5, "Epoch");
207 
208  msg_t *m_cmap = msg_new_string_array(nmeaInfo->sourceNum, SLCHAN_MAP, channels);
209 
210  if (!queue_push(args->logQ, m_cmap)) {
211  log_error(args->pstate, "[NMEA:%s] Error pushing channel map to queue", args->tag);
212  msg_destroy(m_cmap);
213  sa_destroy(channels);
214  free(channels);
215  args->returnCode = -1;
216  pthread_exit(&(args->returnCode));
217  }
218 
219  sa_destroy(channels);
220  free(channels);
221  return NULL;
222 }
223 
229  .logging = &nmea_logging,
230  .shutdown = &nmea_shutdown,
231  .channels = &nmea_channels};
232  return cb;
233 }
234 
239  nmea_params gp = {
240  .portName = NULL, .sourceNum = SLSOURCE_NMEA, .baudRate = 115200, .handle = -1};
241  return gp;
242 }
243 
250  if (lta->dParams) {
251  log_error(lta->pstate, "[NMEA:%s] Refusing to reconfigure", lta->tag);
252  return false;
253  }
254 
255  nmea_params *nmp = calloc(1, sizeof(nmea_params));
256  if (!nmp) {
257  log_error(lta->pstate, "[NMEA:%s] Unable to allocate memory for device parameters",
258  lta->tag);
259  return false;
260  }
261  (*nmp) = nmea_getParams();
262 
263  config_kv *t = NULL;
264 
265  if ((t = config_get_key(s, "name"))) {
266  nmp->sourceName = config_qstrdup(t->value);
267  } else {
268  // Must set a name, so nick the tag value
269  nmp->sourceName = strdup(lta->tag);
270  }
271  t = NULL;
272 
273  if ((t = config_get_key(s, "port"))) { nmp->portName = config_qstrdup(t->value); }
274  t = NULL;
275 
276  if ((t = config_get_key(s, "baud"))) {
277  errno = 0;
278  nmp->baudRate = strtol(t->value, NULL, 0);
279  if (errno) {
280  log_error(lta->pstate, "[NMEA:%s] Error parsing baud rate: %s", lta->tag,
281  strerror(errno));
282  free(nmp);
283  return false;
284  }
285  }
286  t = NULL;
287 
288  if ((t = config_get_key(s, "sourcenum"))) {
289  errno = 0;
290  int sn = strtol(t->value, NULL, 0);
291  if (errno) {
292  log_error(lta->pstate, "[NMEA:%s] Error parsing source number: %s",
293  lta->tag, strerror(errno));
294  free(nmp);
295  return false;
296  }
297  if (sn < 0) {
298  log_error(lta->pstate, "[NMEA:%s] Invalid source number (%s)", lta->tag,
299  t->value);
300  free(nmp);
301  return false;
302  }
303  if (sn < 10) {
304  nmp->sourceNum += sn;
305  } else {
306  nmp->sourceNum = sn;
307  if (sn < SLSOURCE_NMEA || sn > (SLSOURCE_NMEA + 0x0F)) {
308  log_warning(
309  lta->pstate,
310  "[NMEA:%s] Unexpected Source ID number (0x%02x)- this may cause analysis problems",
311  lta->tag, sn);
312  }
313  }
314  }
315  t = NULL;
316  lta->dParams = nmp;
317  return true;
318 }
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
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
int nmea_openConnection(const char *port, const int baudRate)
Set up a connection to the specified port.
Definition: NMEASerial.c:43
void nmea_closeConnection(int handle)
Close existing connection.
Definition: NMEASerial.c:52
#define NMEA_SERIAL_BUFF
Default serial buffer allocation size.
Definition: NMEASerial.h:38
#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 * nmea_shutdown(void *ptargs)
NMEA Shutdown.
Definition: LoggerNMEA.c:159
void * nmea_logging(void *ptargs)
NMEA logging (with pthread function signature)
Definition: LoggerNMEA.c:60
device_callbacks nmea_getCallbacks()
Fill out device callback functions for logging.
Definition: LoggerNMEA.c:227
void * nmea_channels(void *ptargs)
NMEA Channel map.
Definition: LoggerNMEA.c:186
nmea_params nmea_getParams()
Fill out default NMEA parameters.
Definition: LoggerNMEA.c:238
bool nmea_parseConfig(log_thread_args_t *lta, config_section *s)
Take a configuration section and parse parameters.
Definition: LoggerNMEA.c:249
void * nmea_setup(void *ptargs)
NMEA Setup.
Definition: LoggerNMEA.c:34
#define SLSOURCE_NMEA
NMEA Bus.
Definition: sources.h:65
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
struct tm * nmea_parse_zda(const nmea_msg_t *msg)
Get date/time from NMEA ZDA message.
Definition: NMEAMessages.c:240
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
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
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
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
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
char message[3]
Message ID.
Definition: NMEATypes.h:81
char talker[4]
Talker ID (2-4 characters)
Definition: NMEATypes.h:80
NMEA Device specific parameters.
Definition: LoggerNMEA.h:47
uint8_t sourceNum
Source ID for messages.
Definition: LoggerNMEA.h:50
char * sourceName
User defined name for this source.
Definition: LoggerNMEA.h:49
int handle
Handle for currently opened device.
Definition: LoggerNMEA.h:52
char * portName
Target port name.
Definition: LoggerNMEA.h:48
int baudRate
Baud rate for operations.
Definition: LoggerNMEA.h:51
Array of strings.
Definition: strarray.h:43