SELKIELogger  1.0.0
dat2csv.c
Go to the documentation of this file.
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 <libgen.h>
23 #include <stdbool.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <sys/stat.h>
27 #include <unistd.h>
28 #include <zlib.h>
29 
30 #include "SELKIELoggerBase.h"
31 #include "SELKIELoggerMP.h"
32 
33 #include "version.h"
34 
48 int sort_uint(const void *a, const void *b);
49 
63 typedef char *(*csv_header_fn)(const uint8_t, const uint8_t, const char *, const char *);
64 
79 typedef char *(*csv_data_fn)(const msg_t *);
80 
82 char *csv_all_timestamp_headers(const uint8_t source, const uint8_t type, const char *sourceName,
83  const char *channelName);
84 
86 char *csv_all_timestamp_data(const msg_t *msg);
87 
89 char *csv_gps_position_headers(const uint8_t source, const uint8_t type, const char *sourceName,
90  const char *channelName);
91 
93 char *csv_gps_position_data(const msg_t *msg);
94 
96 char *csv_gps_velocity_headers(const uint8_t source, const uint8_t type, const char *sourceName,
97  const char *channelName);
98 
100 char *csv_gps_velocity_data(const msg_t *msg);
101 
103 char *csv_gps_datetime_headers(const uint8_t source, const uint8_t type, const char *sourceName,
104  const char *channelName);
105 
107 char *csv_gps_datetime_data(const msg_t *msg);
108 
110 char *csv_all_float_headers(const uint8_t source, const uint8_t type, const char *sourceName,
111  const char *channelName);
112 
114 char *csv_all_float_data(const msg_t *msg);
115 
121 typedef struct {
122  uint8_t source;
123  uint8_t type;
128 
130 void free_sn_cn(char *sn[128], char *cn[128][128]);
131 
141 int main(int argc, char *argv[]) {
142  program_state state = {0};
143  state.verbose = 1;
144 
145  char *varFileName = NULL;
146  char *outFileName = NULL;
147  bool doGZ = true;
148  bool clobberOutput = false;
149  uint8_t primaryClock = 0x02;
150 
151  char *usage =
152  "Usage: %1$s [-v] [-q] [-f] [-c varfile] [-z|-Z] [-T source] [-o outfile] datfile\n"
153  "\t-v\tIncrease verbosity\n"
154  "\t-q\tDecrease verbosity\n"
155  "\t-f\tOverwrite existing output files\n"
156  "\t-c\tRead source and channel names from specified file\n"
157  "\t-z\tEnable gzipped output\n"
158  "\t-Z\tDisable gzipped output\n"
159  "\t-T\tUse specified source as primary clock\n"
160  "\t-o\tWrite output to named file\n"
161  "\nVersion: " GIT_VERSION_STRING "\n"
162  "Default options equivalent to:\n"
163  "\t%1$s -z -T 0x02 datafile\n"
164  "Output file name will be generated based on input file name and compression flags, unless set by -o option\n"
165  "If no var file is provided using the -c option, the data file will be parsed twice in order to discover active channels\n";
166 
167  opterr = 0; // Handle errors ourselves
168  int go = 0;
169  bool doUsage = false;
170  while ((go = getopt(argc, argv, "vqfzZc:o:T:")) != -1) {
171  switch (go) {
172  case 'v':
173  state.verbose++;
174  break;
175  case 'q':
176  state.verbose--;
177  break;
178  case 'f':
179  clobberOutput = true;
180  break;
181  case 'z':
182  doGZ = true;
183  break;
184  case 'Z':
185  doGZ = false;
186  break;
187  case 'c':
188  if (varFileName) {
189  log_error(
190  &state,
191  "Only a single source mapping (.var) file may be specified");
192  doUsage = true;
193  } else {
194  varFileName = strdup(optarg);
195  }
196  break;
197  case 'o':
198  if (outFileName) {
199  log_error(
200  &state,
201  "Only a single output file name may be specified");
202  doUsage = true;
203  } else {
204  outFileName = strdup(optarg);
205  }
206  break;
207  case 'T':
208  errno = 0;
209  primaryClock = strtol(optarg, NULL, 0);
210  if (errno || primaryClock == 0 || primaryClock >= 128) {
211  log_error(&state, "Bad clock source value ('%s')", optarg);
212  doUsage = true;
213  }
214  break;
215 
216  case '?':
217  log_error(&state, "Unknown option `-%c'", optopt);
218  doUsage = true;
219  }
220  }
221 
222  // Should be 1 spare arguments: The file to convert
223  if (argc - optind != 1) {
224  log_error(&state, "Invalid arguments");
225  doUsage = true;
226  }
227 
228  if (doUsage) {
229  fprintf(stderr, usage, argv[0]);
230  if (varFileName) { free(varFileName); }
231  if (outFileName) { free(outFileName); }
232  return -1;
233  }
234 
235  char *inFileName = strdup(argv[optind]);
236  FILE *inFile = fopen(inFileName, "rb");
237  if (inFile == NULL) {
238  log_error(&state, "Unable to open input file");
239  if (inFileName) { free(inFileName); }
240  if (varFileName) { free(varFileName); }
241  if (outFileName) { free(outFileName); }
242  return -1;
243  }
244 
245  char *sourceNames[128] = {0};
246  char *channelNames[128][128] = {0};
247  for (int i = 0; i < 128; i++) {
248  sourceNames[i] = NULL;
249  for (int j = 0; j < 128; j++) {
250  channelNames[i][j] = NULL;
251  }
252  }
253  sourceNames[0] = strdup("Logger");
254  sourceNames[1] = strdup("dat2csv");
255 
256  int nSources = 0;
257  uint8_t usedSources[128] = {0};
258 
259  if (varFileName == NULL) {
260  log_warning(&state, "Reading entire data file to generate channel list.");
261  log_warning(&state, "Provide .var file using '-c' option to avoid this");
262  varFileName = strdup(inFileName);
263  if (varFileName == NULL) {
264  log_error(&state, "Error processing variable file name: %s",
265  strerror(errno));
266  return -1;
267  }
268  }
269 
270  // No longer run conditionally, but keeping variables in own scope
271  {
272  log_info(&state, 1, "Reading channel and source names from %s", varFileName);
273  FILE *varFile = fopen(varFileName, "rb");
274  if (varFile == NULL) {
275  log_error(&state, "Unable to open variable file");
276  return -1;
277  }
278  bool exitLoop = false;
279  while (!(feof(varFile) || exitLoop)) {
280  msg_t tmp = {0};
281  if (!mp_readMessage(fileno(varFile), &tmp)) {
282  if (tmp.data.value == 0xFF) {
283  continue;
284  } else if (tmp.data.value == 0xFD) {
285  // No more data, exit cleanly
286  log_info(&state, 2, "End of variable file reached");
287  exitLoop = true;
288  continue;
289  } else {
290  log_error(
291  &state,
292  "Error reading messages from variable file (Code: 0x%52x)\n",
293  (uint8_t)tmp.data.value);
294  log_error(&state, "%s", strerror(errno));
295  return -1;
296  }
297  }
298  if (tmp.type == SLCHAN_NAME) {
299  if (tmp.data.string.length == 0) { continue; }
300  if (sourceNames[tmp.source]) {
301  // Most recent name wins, so discard any previous entries
302  free(sourceNames[tmp.source]);
303  sourceNames[tmp.source] = NULL;
304  nSources--;
305  }
306  sourceNames[tmp.source] =
307  strndup(tmp.data.string.data, tmp.data.string.length);
308  usedSources[nSources++] = tmp.source;
309  } else if (tmp.type == SLCHAN_MAP) {
310  for (int i = 0; i < tmp.data.names.entries && i < 128; i++) {
311  if (tmp.data.names.strings[i].length == 0) { continue; }
312  if (channelNames[tmp.source][i]) {
313  // Most recent name wins, so discard any previous
314  // entries
315  free(channelNames[tmp.source][i]);
316  channelNames[tmp.source][i] = NULL;
317  }
318  channelNames[tmp.source][i] =
319  strndup(tmp.data.names.strings[i].data,
320  tmp.data.names.strings[i].length);
321  }
322  } // And ignore any other message types
323  msg_destroy(&tmp);
324  }
325  fclose(varFile);
326  // clang-format off
327  for (int i = 0; i < 128; i++) {
328  if (sourceNames[i]) {
329  log_info(&state, 2, "[0x%02x]\t%s", i, sourceNames[i]);
330  }
331  for (int j = 0; j < 128; j++) {
332  if (channelNames[i][j]) {
333  log_info(&state, 2, " \t[0x%02x]\t%s", j, channelNames[i][j]);
334  }
335  }
336  }
337  // clang-format on
338  free(varFileName);
339  varFileName = NULL;
340  qsort(&usedSources, nSources, sizeof(uint8_t), &sort_uint);
341  }
342 
343  int nHandlers = 0;
344  int maxHandlers = 100;
345  csv_msg_handler *handlers = calloc(maxHandlers, sizeof(csv_msg_handler));
346  if (handlers == NULL) { log_error(&state, "Unable to allocate handler map"); }
347 
348  // Set up per source timestamps (master clock handle separately);
349  for (int i = 0; i < nSources; i++) {
350  handlers[nHandlers++] =
351  (csv_msg_handler){usedSources[i], SLCHAN_TSTAMP,
353 
354  if (nHandlers >= maxHandlers) {
355  handlers =
356  reallocarray(handlers, 50 + maxHandlers, sizeof(csv_msg_handler));
357  if (handlers == NULL) {
358  log_error(&state, "Unable to expand handler map: %s",
359  strerror(errno));
360  return -1;
361  }
362  maxHandlers += 50;
363  }
364  }
365 
366  for (int i = 0; i < nSources; i++) {
367  if (usedSources[i] >= SLSOURCE_GPS && usedSources[i] < (SLSOURCE_GPS + 0x10)) {
368  // Source ID in correct range, assume this is GPS device
369  if (nHandlers >= maxHandlers - 4) {
370  handlers = reallocarray(handlers, 50 + maxHandlers,
371  sizeof(csv_msg_handler));
372  if (handlers == NULL) {
373  log_error(&state, "Unable to expand handler map: %s",
374  strerror(errno));
375  return -1;
376  }
377  maxHandlers += 50;
378  }
379  // clang-format off
380  handlers[nHandlers++] = (csv_msg_handler){usedSources[i], 4, &csv_gps_position_headers, &csv_gps_position_data};
381  handlers[nHandlers++] = (csv_msg_handler){usedSources[i], 5, &csv_gps_velocity_headers, &csv_gps_velocity_data};
382  handlers[nHandlers++] = (csv_msg_handler){usedSources[i], 6, &csv_gps_datetime_headers, &csv_gps_datetime_data};
383  // clang-format on
384  }
385  // Although these sources have to be communicated with differently, both
386  // output named channels with single floating point values
387  if ((usedSources[i] >= SLSOURCE_I2C && usedSources[i] < (SLSOURCE_I2C + 0x10)) ||
388  (usedSources[i] >= SLSOURCE_MP && usedSources[i] < (SLSOURCE_MP + 0x10)) ||
389  (usedSources[i] >= SLSOURCE_ADC && usedSources[i] < (SLSOURCE_ADC + 0x10))) {
390  // Start at first valid data channel (3)
391  for (int c = 3; c < 128; c++) {
392  // If the channel name is empty, assume we're not using this one
393  if (channelNames[usedSources[i]][c] == NULL) { continue; }
394  // Generic handler for any single floating point channels
395  handlers[nHandlers++] = (csv_msg_handler){usedSources[i], c,
398  if (nHandlers >= maxHandlers) {
399  handlers = reallocarray(handlers, 50 + maxHandlers,
400  sizeof(csv_msg_handler));
401  if (handlers == NULL) {
402  log_error(&state,
403  "Unable to expand handler map: %s",
404  strerror(errno));
405  return -1;
406  }
407  maxHandlers += 50;
408  }
409  }
410  }
411  }
412 
413  if (outFileName == NULL) {
414  // Work out output file name
415  // Split into base and dirnames so that we're don't accidentally split the
416  // path on a .
417  char *inF1 = strdup(inFileName);
418  char *inF2 = strdup(inFileName);
419  char *dn = dirname(inF1);
420  char *bn = basename(inF2);
421 
422  // Find last . in file name, if any
423  char *ep = strrchr(bn, '.');
424 
425  // If no ., use full basename length, else use length to .
426  int bnl = 0;
427  if (ep == NULL) {
428  bnl = strlen(bn);
429  } else {
430  bnl = ep - bn;
431  }
432  // New basename is old basename up to . (or end, if absent)
433  char *nbn = calloc(bnl + 1, sizeof(char));
434  strncpy(nbn, bn, bnl);
435  if (doGZ) {
436  if (asprintf(&outFileName, "%s/%s.csv.gz", dn, nbn) <= 0) { return -1; }
437  } else {
438  if (asprintf(&outFileName, "%s/%s.csv", dn, nbn) <= 0) { return -1; }
439  }
440  free(nbn);
441  free(inF1);
442  free(inF2);
443  }
444 
445  errno = 0;
446  char fmode[5] = {'w', 'b', 0, 0};
447 
448  if (doGZ) {
449  if (clobberOutput) {
450  // wb7: Write, binary, comp. level 7
451  fmode[2] = 7;
452  } else {
453  // wbx7: Create, binary, comp. level 7
454  fmode[2] = 'x';
455  fmode[3] = 7;
456  }
457  } else {
458  if (clobberOutput) {
459  // wbT: Write, binary, do not gzip
460  fmode[2] = 'T';
461  } else {
462  // wbxT: Create, binary, do not gzip
463  fmode[2] = 'x';
464  fmode[3] = 'T';
465  }
466  }
467 
468  // Use zib functions, but in transparent mode if not compressing
469  // Avoids wrapping every write function with doGZ checks
470  gzFile outFile = gzopen(outFileName, fmode);
471  if (outFile == NULL) {
472  log_error(&state, "Unable to open output file");
473  log_error(&state, "%s", strerror(errno));
474  fclose(inFile);
475  free(inFileName);
476  free(outFileName);
477  free(handlers);
478  free_sn_cn(sourceNames, channelNames);
479  destroy_program_state(&state);
480  return -1;
481  }
482  log_info(&state, 1, "Writing %s output to %s", doGZ ? "compressed" : "uncompressed",
483  outFileName);
484  free(outFileName);
485  outFileName = NULL;
486 
487  state.started = 1;
488  int msgCount = 0;
489  struct stat inStat = {0};
490  if (fstat(fileno(inFile), &inStat) != 0) {
491  log_error(&state, "Unable to get input file status: %s", strerror(errno));
492  fclose(inFile);
493  gzclose(outFile);
494  free(inFileName);
495  free(outFileName);
496  free(handlers);
497  free_sn_cn(sourceNames, channelNames);
498  destroy_program_state(&state);
499  return -1;
500  }
501  long inSize = inStat.st_size;
502  long inPos = 0;
503  int progress = 0;
504  if (inSize >= 1E9) {
505  log_info(&state, 1, "Reading %.2fGB of data from %s", inSize / 1.0E9, inFileName);
506  } else if (inSize >= 1E6) {
507  log_info(&state, 1, "Reading %.2fMB of data from %s", inSize / 1.0E6, inFileName);
508  } else if (inSize >= 1E3) {
509  log_info(&state, 1, "Reading %.2fkB of data from %s", inSize / 1.0E3, inFileName);
510  } else {
511  log_info(&state, 1, "Reading %ld bytes of data from %s", inSize, inFileName);
512  }
513  free(inFileName);
514  inFileName = NULL;
515 
516  uint32_t timestep = 0;
517  uint32_t nextstep = 0;
518 
519  size_t hlen = 0;
520  size_t hsize = 512;
521  char *header = calloc(hsize, 1);
522 
523  // The first handler is the master clock timestamp, so special case that header
524  hlen += snprintf(header + hlen, hsize - hlen, "Timestamp");
525  // For all remaining headers:
526  for (int i = 0; i < nHandlers; i++) {
527  char *fieldTitle = handlers[i].header(
528  handlers[i].source, handlers[i].type, sourceNames[handlers[i].source],
529  channelNames[handlers[i].source][handlers[i].type]);
530  if (fieldTitle == NULL) {
531  log_error(&state, "Unable to generate field name string: %s",
532  strerror(errno));
533  gzclose(outFile);
534  fclose(inFile);
535  free(header);
536  free(fieldTitle);
537  free(handlers);
538  free_sn_cn(sourceNames, channelNames);
539  destroy_program_state(&state);
540  return -1;
541  }
542  if (hlen > (hsize - strlen(fieldTitle))) {
543  header = realloc(header, hsize + 512);
544  hsize += 512;
545  }
546  hlen += snprintf(header + hlen, hsize - hlen, ",%s", fieldTitle);
547  free(fieldTitle);
548  }
549 
550  const int ctsLimit = 1000;
551  msg_t currentTimestep[ctsLimit];
552  for (int i = 0; i < ctsLimit; i++) {
553  currentTimestep[i] = (msg_t){0};
554  }
555  int currMsg = 0;
556  log_info(&state, 2, "%s", header);
557  gzprintf(outFile, "%s\n", header);
558  free(header);
559  header = NULL;
560 
561  while (!(feof(inFile))) {
562  // Read message from data file
563  msg_t *tmp = &(currentTimestep[currMsg++]);
564  if (!mp_readMessage(fileno(inFile), tmp)) {
565  if (tmp->data.value == 0xAA || tmp->data.value == 0xEE) {
566  log_error(&state,
567  "Error reading messages from file (Code: 0x%52x)\n",
568  (uint8_t)tmp->data.value);
569  }
570  if (tmp->data.value == 0xFD) {
571  // No more data, exit cleanly
572  log_info(&state, 1, "End of file reached");
573  }
574  break;
575  }
576  // Increment total message count
577  msgCount++;
578 
579  // Check whether we're updating the current timestep
580  if (tmp->source == primaryClock && tmp->type == SLCHAN_TSTAMP) {
581  nextstep = tmp->data.timestamp;
582  }
583 
584  // If we accumulate too many messages (bad clock choice?), force a record
585  // to be output
586  if (currMsg >= (ctsLimit - 1) && (timestep == nextstep)) {
587  log_warning(&state,
588  "Too many messages processed during %d. Forcing output.",
589  timestep);
590  nextstep++;
591  }
592 
593  // Time for a new record? Write it out
594  if (nextstep != timestep) {
595  // Special case: Timestep from master clock
596  gzprintf(outFile, "%d", timestep);
597  // Call all message handlers, in order
598  for (int i = 0; i < nHandlers; i++) {
599  bool handled = false;
600  bool error = false;
601  for (int m = 0; m < currMsg; m++) {
602  msg_t *msg = &(currentTimestep[m]);
603  if (msg->type == handlers[i].type &&
604  msg->source == handlers[i].source) {
605  char *out = handlers[i].data(msg);
606  if (out == NULL) {
607  log_error(
608  &state,
609  "Error converting message to output format: %s",
610  strerror(errno));
611  error = true;
612  free(out);
613  break;
614  }
615  gzprintf(outFile, ",%s", out);
616  free(out);
617  handled = true;
618  break; // First instance of each message wins
619  }
620  }
621  if (!error && !handled) {
622  char *out =
623  handlers[i].data(NULL); // Generate empty fields
624  if (out == NULL) {
625  log_error(&state,
626  "Error generating empty field: %s",
627  strerror(errno));
628  error = true;
629  } else {
630  gzprintf(outFile, ",%s", out);
631  }
632  free(out);
633  }
634  if (error) {
635  for (int m = 0; m < currMsg; m++) {
636  msg_destroy(&(currentTimestep[m]));
637  }
638  gzclose(outFile);
639  fclose(inFile);
640  free(handlers);
641  free_sn_cn(sourceNames, channelNames);
642  destroy_program_state(&state);
643  return -1;
644  }
645  }
646  gzprintf(outFile, "\n");
647  // Empty current message list
648  for (int m = 0; m < currMsg; m++) {
649  msg_destroy(&(currentTimestep[m]));
650  }
651  currMsg = 0;
652  timestep = nextstep;
653  }
654 
655  inPos = ftell(inFile);
656  if (((((1.0 * inPos) / inSize) * 100) - progress) >= 5) {
657  progress = (((1.0 * inPos) / inSize) * 100);
658  log_info(&state, 2, "Progress: %d%% (%ld / %ld)", progress, inPos, inSize);
659  }
660  }
661  fclose(inFile);
662  gzclose(outFile);
663 
664  log_info(&state, 1, "%d messages processed", msgCount);
665  free(handlers);
666  free_sn_cn(sourceNames, channelNames);
667  destroy_program_state(&state);
668  return 0;
669 }
670 
677 void free_sn_cn(char *sn[128], char *cn[128][128]) {
678  for (int i = 0; i < 128; i++) {
679  if (sn[i] != NULL) { free(sn[i]); }
680  }
681  for (int i = 0; i < 128; i++) {
682  for (int j = 0; j < 128; j++) {
683  if (cn[i][j] != NULL) {
684  free(cn[i][j]);
685  cn[i][j] = NULL;
686  }
687  }
688  }
689 }
690 
696 int sort_uint(const void *a, const void *b) {
697  const uint8_t *ai = a;
698  const uint8_t *bi = b;
699  if (*ai > *bi) {
700  return 1;
701  } else if (*ai < *bi) {
702  return -1;
703  }
704  return 0;
705 }
706 
716 char *csv_all_timestamp_headers(const uint8_t source, const uint8_t type, const char *sourceName,
717  const char *channelName) {
718  // Not used here
719  (void) type;
720  (void) sourceName;
721  (void) channelName;
722 
723  char *fields = NULL;
724  if (asprintf(&fields, "Timestamp:%02X", source) <= 0) { return NULL; }
725  return fields;
726 }
727 
734 char *csv_all_timestamp_data(const msg_t *msg) {
735  char *out = NULL;
736  if (msg == NULL) { return strdup(""); }
737 
738  if (asprintf(&out, "%d", msg->data.timestamp) <= 0) { return NULL; }
739  return out;
740 }
741 
751 char *csv_gps_position_headers(const uint8_t source, const uint8_t type, const char *sourceName,
752  const char *channelName) {
753  (void) type;
754  (void) sourceName;
755  (void) channelName;
756 
757  char *fields = NULL;
758  if (asprintf(&fields,
759  "Longitude:%1$02X,Latitude:%1$02X,Height:%1$02X,HAcc:%1$02X,VAcc:%1$02X",
760  source) <= 0) {
761  return NULL;
762  }
763  return fields;
764 }
765 
775 char *csv_gps_position_data(const msg_t *msg) {
776  char *out = NULL;
777  if (msg == NULL) { return strdup(",,,,"); }
778  const float *d = msg->data.farray;
779  if (asprintf(&out, "%.5f,%.5f,%.3f,%.3f,%.3f", d[0], d[1], d[2], d[4], d[5]) <= 0) {
780  return NULL;
781  }
782  return out;
783 }
784 
794 char *csv_gps_velocity_headers(const uint8_t source, const uint8_t type, const char *sourceName,
795  const char *channelName) {
796  (void) type;
797  (void) sourceName;
798  (void) channelName;
799 
800  char *fields = NULL;
801  if (asprintf(
802  &fields,
803  "Velocity_N:%1$02X,Velocity_E:%1$02X,Velocity_D:%1$02X,SpeedAcc:%1$02X,Heading:%1$02X,HeadAcc:%1$02X",
804  source) <= 0) {
805  return NULL;
806  }
807  return fields;
808 }
809 
819 char *csv_gps_velocity_data(const msg_t *msg) {
820  char *out = NULL;
821  if (msg == NULL) { return strdup(",,,,,"); }
822  const float *d = msg->data.farray;
823  // clang-format off
824  if (asprintf(&out, "%.3f,%.3f,%.3f,%.3f,%.3f,%.3f", d[0], d[1], d[2], d[5], d[4], d[6]) <= 0) {
825  return NULL;
826  }
827  // clang-format on
828  return out;
829 }
830 
840 char *csv_gps_datetime_headers(const uint8_t source, const uint8_t type, const char *sourceName,
841  const char *channelName) {
842  (void) type;
843  (void) sourceName;
844  (void) channelName;
845 
846  char *fields = NULL;
847  if (asprintf(&fields, "Date:%1$02X,Time:%1$02X,DTAcc:%1$02X", source) <= 0) {
848  return NULL;
849  }
850  return fields;
851 }
852 
862 char *csv_gps_datetime_data(const msg_t *msg) {
863  char *out = NULL;
864  if (msg == NULL) { return strdup(",,"); }
865  const float *d = msg->data.farray;
866  if (asprintf(&out, "%04.0f-%02.0f-%02.0f,%02.0f:%02.0f:%02.0f.%06.0f,%09.0f", d[0], d[1],
867  d[2], d[3], d[4], d[5], d[6], d[7]) <= 0) {
868  return NULL;
869  }
870  return out;
871 }
872 
884 char *csv_all_float_headers(const uint8_t source, const uint8_t type, const char *sourceName,
885  const char *channelName) {
886  (void) type;
887  (void) sourceName;
888 
889  char *fields = NULL;
890  if (asprintf(&fields, "%s:%02X", channelName, source) <= 0) { return NULL; }
891  return fields;
892 }
893 
902 char *csv_all_float_data(const msg_t *msg) {
903  char *out = NULL;
904  if (msg == NULL) { return strdup(""); }
905  if (asprintf(&out, "%.6f", msg->data.value) <= 0) { return NULL; }
906  return out;
907 }
int msgCount[PGN_MAX]
Definition: N2KClassify.c:51
int main(int argc, char *argv[])
Definition: dat2csv.c:141
void free_sn_cn(char *sn[128], char *cn[128][128])
Tidy up source and channel name arrays.
Definition: dat2csv.c:677
void msg_destroy(msg_t *msg)
Destroy a message.
Definition: messages.c:349
bool mp_readMessage(int handle, msg_t *out)
Static wrapper around mp_readMessage_buf.
Definition: MPSerial.c:66
char *(* csv_header_fn)(const uint8_t, const uint8_t, const char *, const char *)
Definition: dat2csv.c:63
int sort_uint(const void *a, const void *b)
qsort() comparison function
Definition: dat2csv.c:696
char * csv_gps_datetime_headers(const uint8_t source, const uint8_t type, const char *sourceName, const char *channelName)
Generate CSV header for GPS date and time information.
Definition: dat2csv.c:840
char * csv_all_timestamp_headers(const uint8_t source, const uint8_t type, const char *sourceName, const char *channelName)
Generate CSV header for timestamp messages (SLCHAN_TSTAMP)
Definition: dat2csv.c:716
char * csv_all_timestamp_data(const msg_t *msg)
Convert timestamp (SLCHAN_TSTAMP) to string.
Definition: dat2csv.c:734
char *(* csv_data_fn)(const msg_t *)
Definition: dat2csv.c:79
char * csv_gps_datetime_data(const msg_t *msg)
Convert GPS date and time information to appropriate CSV string.
Definition: dat2csv.c:862
char * csv_gps_position_headers(const uint8_t source, const uint8_t type, const char *sourceName, const char *channelName)
Generate CSV header for GPS position fields.
Definition: dat2csv.c:751
char * csv_all_float_headers(const uint8_t source, const uint8_t type, const char *sourceName, const char *channelName)
Generate CSV header for any single value floating point channel.
Definition: dat2csv.c:884
char * csv_gps_position_data(const msg_t *msg)
Convert GPS position information to CSV string.
Definition: dat2csv.c:775
char * csv_all_float_data(const msg_t *msg)
Convert single value floating point data channel to CSV string.
Definition: dat2csv.c:902
char * csv_gps_velocity_headers(const uint8_t source, const uint8_t type, const char *sourceName, const char *channelName)
Generate CSV header for GPS velocity information.
Definition: dat2csv.c:794
char * csv_gps_velocity_data(const msg_t *msg)
Convert GPS velocity information to CSV string.
Definition: dat2csv.c:819
#define SLCHAN_TSTAMP
Source timestamp (milliseconds, arbitrary epoch)
Definition: sources.h:99
#define SLCHAN_MAP
Channel name map (excludes log channels)
Definition: sources.h:98
#define SLCHAN_NAME
Name of source device.
Definition: sources.h:97
#define SLSOURCE_I2C
I2C Bus.
Definition: sources.h:68
#define SLSOURCE_ADC
Generic analogue inputs.
Definition: sources.h:63
#define SLSOURCE_GPS
GPS (or other satellite navigation) sources.
Definition: sources.h:62
#define SLSOURCE_MP
Devices with suitable MessagePack output returning single value channels.
Definition: sources.h:75
void destroy_program_state(program_state *s)
Cleanly destroy program state.
Definition: logging.c:207
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
csv_data_fn data
CSV field generator.
Definition: dat2csv.c:125
uint8_t source
Message source.
Definition: dat2csv.c:122
uint8_t type
Message type.
Definition: dat2csv.c:123
csv_header_fn header
CSV Header generator.
Definition: dat2csv.c:124
Queuable message.
Definition: messages.h:71
msg_data_t data
Embedded data.
Definition: messages.h:76
uint8_t type
Message type. Common types to be documented.
Definition: messages.h:73
uint8_t source
Maps to a specific sensor unit or data source.
Definition: messages.h:72
Program state and logging information.
Definition: logging.h:40
int verbose
Current log verbosity (console output)
Definition: logging.h:43
bool started
Indicates startup completed.
Definition: logging.h:41
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
float * farray
Array of floats.
Definition: messages.h:51
string string
Single character array with length.
Definition: messages.h:49
uint32_t timestamp
Intended to represent millisecond level clock.
Definition: messages.h:47
float value
Generic numerical data.
Definition: messages.h:46
strarray names
Array of strings, intended for use to provide channel names.
Definition: messages.h:50
#define GIT_VERSION_STRING
Git version description.
Definition: version.h.in:13