48 int sort_uint(
const void *a,
const void *b);
63 typedef char *(*csv_header_fn)(
const uint8_t,
const uint8_t,
const char *,
const char *);
79 typedef char *(*csv_data_fn)(
const msg_t *);
83 const char *channelName);
90 const char *channelName);
97 const char *channelName);
104 const char *channelName);
111 const char *channelName);
130 void free_sn_cn(
char *sn[128],
char *cn[128][128]);
141 int main(
int argc,
char *argv[]) {
145 char *varFileName = NULL;
146 char *outFileName = NULL;
148 bool clobberOutput =
false;
149 uint8_t primaryClock = 0x02;
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"
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";
169 bool doUsage =
false;
170 while ((go = getopt(argc, argv,
"vqfzZc:o:T:")) != -1) {
179 clobberOutput =
true;
191 "Only a single source mapping (.var) file may be specified");
194 varFileName = strdup(optarg);
201 "Only a single output file name may be specified");
204 outFileName = strdup(optarg);
209 primaryClock = strtol(optarg, NULL, 0);
210 if (errno || primaryClock == 0 || primaryClock >= 128) {
211 log_error(&state,
"Bad clock source value ('%s')", optarg);
217 log_error(&state,
"Unknown option `-%c'", optopt);
223 if (argc - optind != 1) {
229 fprintf(stderr, usage, argv[0]);
230 if (varFileName) { free(varFileName); }
231 if (outFileName) { free(outFileName); }
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); }
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;
253 sourceNames[0] = strdup(
"Logger");
254 sourceNames[1] = strdup(
"dat2csv");
257 uint8_t usedSources[128] = {0};
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",
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");
278 bool exitLoop =
false;
279 while (!(feof(varFile) || exitLoop)) {
286 log_info(&state, 2,
"End of variable file reached");
292 "Error reading messages from variable file (Code: 0x%52x)\n",
294 log_error(&state,
"%s", strerror(errno));
300 if (sourceNames[tmp.
source]) {
302 free(sourceNames[tmp.
source]);
303 sourceNames[tmp.
source] = NULL;
308 usedSources[nSources++] = tmp.
source;
312 if (channelNames[tmp.
source][i]) {
315 free(channelNames[tmp.
source][i]);
316 channelNames[tmp.
source][i] = NULL;
318 channelNames[tmp.
source][i] =
327 for (
int i = 0; i < 128; i++) {
328 if (sourceNames[i]) {
329 log_info(&state, 2,
"[0x%02x]\t%s", i, sourceNames[i]);
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]);
340 qsort(&usedSources, nSources,
sizeof(uint8_t), &
sort_uint);
344 int maxHandlers = 100;
346 if (handlers == NULL) {
log_error(&state,
"Unable to allocate handler map"); }
349 for (
int i = 0; i < nSources; i++) {
350 handlers[nHandlers++] =
354 if (nHandlers >= maxHandlers) {
357 if (handlers == NULL) {
358 log_error(&state,
"Unable to expand handler map: %s",
366 for (
int i = 0; i < nSources; i++) {
369 if (nHandlers >= maxHandlers - 4) {
370 handlers = reallocarray(handlers, 50 + maxHandlers,
372 if (handlers == NULL) {
373 log_error(&state,
"Unable to expand handler map: %s",
391 for (
int c = 3; c < 128; c++) {
393 if (channelNames[usedSources[i]][c] == NULL) {
continue; }
398 if (nHandlers >= maxHandlers) {
399 handlers = reallocarray(handlers, 50 + maxHandlers,
401 if (handlers == NULL) {
403 "Unable to expand handler map: %s",
413 if (outFileName == NULL) {
417 char *inF1 = strdup(inFileName);
418 char *inF2 = strdup(inFileName);
419 char *dn = dirname(inF1);
420 char *bn = basename(inF2);
423 char *ep = strrchr(bn,
'.');
433 char *nbn = calloc(bnl + 1,
sizeof(
char));
434 strncpy(nbn, bn, bnl);
436 if (asprintf(&outFileName,
"%s/%s.csv.gz", dn, nbn) <= 0) {
return -1; }
438 if (asprintf(&outFileName,
"%s/%s.csv", dn, nbn) <= 0) {
return -1; }
446 char fmode[5] = {
'w',
'b', 0, 0};
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));
482 log_info(&state, 1,
"Writing %s output to %s", doGZ ?
"compressed" :
"uncompressed",
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));
501 long inSize = inStat.st_size;
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);
511 log_info(&state, 1,
"Reading %ld bytes of data from %s", inSize, inFileName);
516 uint32_t timestep = 0;
517 uint32_t nextstep = 0;
521 char *header = calloc(hsize, 1);
524 hlen += snprintf(header + hlen, hsize - hlen,
"Timestamp");
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",
542 if (hlen > (hsize - strlen(fieldTitle))) {
543 header = realloc(header, hsize + 512);
546 hlen += snprintf(header + hlen, hsize - hlen,
",%s", fieldTitle);
550 const int ctsLimit = 1000;
551 msg_t currentTimestep[ctsLimit];
552 for (
int i = 0; i < ctsLimit; i++) {
553 currentTimestep[i] = (
msg_t){0};
557 gzprintf(outFile,
"%s\n", header);
561 while (!(feof(inFile))) {
563 msg_t *tmp = &(currentTimestep[currMsg++]);
567 "Error reading messages from file (Code: 0x%52x)\n",
572 log_info(&state, 1,
"End of file reached");
586 if (currMsg >= (ctsLimit - 1) && (timestep == nextstep)) {
588 "Too many messages processed during %d. Forcing output.",
594 if (nextstep != timestep) {
596 gzprintf(outFile,
"%d", timestep);
598 for (
int i = 0; i < nHandlers; i++) {
599 bool handled =
false;
601 for (
int m = 0; m < currMsg; m++) {
602 msg_t *msg = &(currentTimestep[m]);
603 if (msg->
type == handlers[i].
type &&
605 char *out = handlers[i].
data(msg);
609 "Error converting message to output format: %s",
615 gzprintf(outFile,
",%s", out);
621 if (!error && !handled) {
623 handlers[i].
data(NULL);
626 "Error generating empty field: %s",
630 gzprintf(outFile,
",%s", out);
635 for (
int m = 0; m < currMsg; m++) {
646 gzprintf(outFile,
"\n");
648 for (
int m = 0; m < currMsg; m++) {
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);
678 for (
int i = 0; i < 128; i++) {
679 if (sn[i] != NULL) { free(sn[i]); }
681 for (
int i = 0; i < 128; i++) {
682 for (
int j = 0; j < 128; j++) {
683 if (cn[i][j] != NULL) {
697 const uint8_t *ai = a;
698 const uint8_t *bi = b;
701 }
else if (*ai < *bi) {
717 const char *channelName) {
724 if (asprintf(&fields,
"Timestamp:%02X", source) <= 0) {
return NULL; }
736 if (msg == NULL) {
return strdup(
""); }
738 if (asprintf(&out,
"%d", msg->
data.
timestamp) <= 0) {
return NULL; }
752 const char *channelName) {
758 if (asprintf(&fields,
759 "Longitude:%1$02X,Latitude:%1$02X,Height:%1$02X,HAcc:%1$02X,VAcc:%1$02X",
777 if (msg == NULL) {
return strdup(
",,,,"); }
779 if (asprintf(&out,
"%.5f,%.5f,%.3f,%.3f,%.3f", d[0], d[1], d[2], d[4], d[5]) <= 0) {
795 const char *channelName) {
803 "Velocity_N:%1$02X,Velocity_E:%1$02X,Velocity_D:%1$02X,SpeedAcc:%1$02X,Heading:%1$02X,HeadAcc:%1$02X",
821 if (msg == NULL) {
return strdup(
",,,,,"); }
824 if (asprintf(&out,
"%.3f,%.3f,%.3f,%.3f,%.3f,%.3f", d[0], d[1], d[2], d[5], d[4], d[6]) <= 0) {
841 const char *channelName) {
847 if (asprintf(&fields,
"Date:%1$02X,Time:%1$02X,DTAcc:%1$02X", source) <= 0) {
864 if (msg == NULL) {
return strdup(
",,"); }
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) {
885 const char *channelName) {
890 if (asprintf(&fields,
"%s:%02X", channelName, source) <= 0) {
return NULL; }
904 if (msg == NULL) {
return strdup(
""); }
905 if (asprintf(&out,
"%.6f", msg->
data.
value) <= 0) {
return NULL; }
int main(int argc, char *argv[])
void free_sn_cn(char *sn[128], char *cn[128][128])
Tidy up source and channel name arrays.
void msg_destroy(msg_t *msg)
Destroy a message.
bool mp_readMessage(int handle, msg_t *out)
Static wrapper around mp_readMessage_buf.
char *(* csv_header_fn)(const uint8_t, const uint8_t, const char *, const char *)
int sort_uint(const void *a, const void *b)
qsort() comparison function
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.
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)
char * csv_all_timestamp_data(const msg_t *msg)
Convert timestamp (SLCHAN_TSTAMP) to string.
char *(* csv_data_fn)(const msg_t *)
char * csv_gps_datetime_data(const msg_t *msg)
Convert GPS date and time information to appropriate CSV string.
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.
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.
char * csv_gps_position_data(const msg_t *msg)
Convert GPS position information to CSV string.
char * csv_all_float_data(const msg_t *msg)
Convert single value floating point data channel to CSV string.
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.
char * csv_gps_velocity_data(const msg_t *msg)
Convert GPS velocity information to CSV string.
#define SLCHAN_TSTAMP
Source timestamp (milliseconds, arbitrary epoch)
#define SLCHAN_MAP
Channel name map (excludes log channels)
#define SLCHAN_NAME
Name of source device.
#define SLSOURCE_I2C
I2C Bus.
#define SLSOURCE_ADC
Generic analogue inputs.
#define SLSOURCE_GPS
GPS (or other satellite navigation) sources.
#define SLSOURCE_MP
Devices with suitable MessagePack output returning single value channels.
void destroy_program_state(program_state *s)
Cleanly destroy program state.
void log_info(const program_state *s, const int level, const char *format,...)
Output formatted information message at a given level.
void log_warning(const program_state *s, const char *format,...)
Output formatted warning message.
void log_error(const program_state *s, const char *format,...)
Output formatted error message.
csv_data_fn data
CSV field generator.
uint8_t source
Message source.
uint8_t type
Message type.
csv_header_fn header
CSV Header generator.
msg_data_t data
Embedded data.
uint8_t type
Message type. Common types to be documented.
uint8_t source
Maps to a specific sensor unit or data source.
Program state and logging information.
int verbose
Current log verbosity (console output)
bool started
Indicates startup completed.
int entries
Maximum number of strings in array, set when calling sa_new()
string * strings
Simple array of string structures.
char * data
Character array, should be null terminated.
size_t length
This should include a terminating null byte where possible.
float * farray
Array of floats.
string string
Single character array with length.
uint32_t timestamp
Intended to represent millisecond level clock.
float value
Generic numerical data.
strarray names
Array of strings, intended for use to provide channel names.
#define GIT_VERSION_STRING
Git version description.