]> sigrok.org Git - libsigrok.git/blame - src/output/csv.c
csv: Fix a segfault due to a g_malloc() allocating too few bytes.
[libsigrok.git] / src / output / csv.c
CommitLineData
02604ed6 1/*
50985c20 2 * This file is part of the libsigrok project.
02604ed6
UH
3 *
4 * Copyright (C) 2011 Uwe Hermann <uwe@hermann-uwe.de>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
9e24c8bc
MM
21/*
22 * Options and their values:
23 *
24 * gnuplot: Write out a gnuplot interpreter script (.gpi file) to plot
25 * the datafile using the parameters given. It should be called
26 * from a gnuplot session with the data file name as a parameter
27 * after adjusting line styles, terminal, etc.
28 *
29 * scale: The gnuplot graphs are scaled so they all have the same
30 * peak-to-peak distance. Defaults to TRUE.
31 *
32 * value: The string used to separate values in a record. Defaults to ','.
33 *
34 * record: The string to use to separate records. Default is newline. gnuplot
35 * files must use newline.
36 *
37 * frame: The string to use when a frame ends. The default is a blank line.
38 * This may confuse some CSV parsers, but it makes gnuplot happy.
39 *
40 * comment: The string that starts a comment line. Defaults to ';'.
41 *
42 * header: Print header comment with capture metadata. Defaults to TRUE.
43 *
44 * label: Add a line of channel labels as the first line of output. Defaults
45 * to TRUE.
46 *
47 * time: Whether or not the first column should include the time the sample
48 * was taken. Defaults to TRUE.
49 *
50 * trigger: Whether or not to add a "trigger" column as the last column.
51 * Defaults to FALSE.
52 *
53 * dedup: Don't output duplicate rows. Defaults to TRUE. If time is off, then
54 * this is forced to be off.
55 */
56
57#include <math.h>
6ec6c43b 58#include <config.h>
02604ed6
UH
59#include <stdlib.h>
60#include <string.h>
61#include <glib.h>
c1aae900 62#include <libsigrok/libsigrok.h>
45c59c8b 63#include "libsigrok-internal.h"
02604ed6 64
3544f848 65#define LOG_PREFIX "output/csv"
a944a84b 66
9e24c8bc
MM
67struct ctx_channel {
68 struct sr_channel *ch;
69 float min, max;
70};
71
02604ed6 72struct context {
9e24c8bc
MM
73 /* Options */
74 const char *gnuplot;
75 gboolean scale;
76 const char *value;
77 const char *record;
78 const char *frame;
79 const char *comment;
80 gboolean header, did_header;
81 gboolean label, did_label;
82 gboolean time;
83 gboolean do_trigger;
84 gboolean dedup;
85
86 /* Plot data */
c04cf9aa 87 unsigned int num_analog_channels;
9e24c8bc
MM
88 unsigned int num_logic_channels;
89 struct ctx_channel *channels;
90
91 /* Metadata */
92 gboolean trigger;
93 uint32_t num_samples;
94 uint32_t channel_count, logic_channel_count;
95 uint32_t channels_seen;
96 uint64_t period;
97 uint64_t sample_time;
98 uint8_t *previous_sample;
99 float *analog_samples;
100 uint8_t *logic_samples;
101 const char *xlabel; /* Don't free: will point to a static string. */
102 const char *title; /* Don't free: will point into the driver struct. */
02604ed6
UH
103};
104
105/*
106 * TODO:
02604ed6 107 * - Option to print comma-separated bits, or whole bytes/words (for 8/16
ba7dd8bb 108 * channel LAs) as ASCII/hex etc. etc.
02604ed6
UH
109 */
110
a755b0e1 111static int init(struct sr_output *o, GHashTable *options)
02604ed6 112{
9e24c8bc 113 unsigned int i, analog_channels, logic_channels;
02604ed6 114 struct context *ctx;
ba7dd8bb 115 struct sr_channel *ch;
02604ed6 116 GSList *l;
a755b0e1 117
0f3d8c89 118 if (!o || !o->sdi)
02604ed6 119 return SR_ERR_ARG;
02604ed6 120
2a035e53 121 ctx = g_malloc0(sizeof(struct context));
d686c5ec 122 o->priv = ctx;
02604ed6 123
9e24c8bc
MM
124 /* Options */
125 ctx->gnuplot = g_strdup(g_variant_get_string(
126 g_hash_table_lookup(options, "gnuplot"), NULL));
127 ctx->scale = g_variant_get_boolean(g_hash_table_lookup(options, "scale"));
128 ctx->value = g_strdup(g_variant_get_string(
129 g_hash_table_lookup(options, "value"), NULL));
130 ctx->record = g_strdup(g_variant_get_string(
131 g_hash_table_lookup(options, "record"), NULL));
132 ctx->frame = g_strdup(g_variant_get_string(
133 g_hash_table_lookup(options, "frame"), NULL));
134 ctx->comment = g_strdup(g_variant_get_string(
135 g_hash_table_lookup(options, "comment"), NULL));
136 ctx->header = g_variant_get_boolean(g_hash_table_lookup(options, "header"));
137 ctx->time = g_variant_get_boolean(g_hash_table_lookup(options, "time"));
138 ctx->do_trigger = g_variant_get_boolean(g_hash_table_lookup(options, "trigger"));
139 ctx->label = g_variant_get_boolean(g_hash_table_lookup(options, "label"));
140 ctx->dedup = g_variant_get_boolean(g_hash_table_lookup(options, "dedup"));
141 ctx->dedup &= ctx->time;
142
143 if (*ctx->gnuplot && g_strcmp0(ctx->record, "\n"))
144 sr_warn("gnuplot record separator must be newline.");
145
146 if (*ctx->gnuplot && strlen(ctx->value) > 1)
147 sr_warn("gnuplot doesn't support multichar value separators.");
148
149 sr_dbg("gnuplot = '%s', scale = %d", ctx->gnuplot, ctx->scale);
150 sr_dbg("value = '%s', record = '%s', frame = '%s', comment = '%s'",
151 ctx->value, ctx->record, ctx->frame, ctx->comment);
152 sr_dbg("header = %d, label = %d, time = %d, do_trigger = %d, dedup = %d",
153 ctx->header, ctx->label, ctx->time, ctx->do_trigger, ctx->dedup);
154
155 analog_channels = logic_channels = 0;
ba7dd8bb
UH
156 /* Get the number of channels, and the unitsize. */
157 for (l = o->sdi->channels; l; l = l->next) {
158 ch = l->data;
9e24c8bc
MM
159 if (ch->type == SR_CHANNEL_LOGIC) {
160 ctx->logic_channel_count++;
161 if (ch->enabled)
162 logic_channels++;
c04cf9aa 163 }
9e24c8bc
MM
164 if (ch->type == SR_CHANNEL_ANALOG && ch->enabled)
165 analog_channels++;
166 }
167 if (analog_channels) {
168 sr_info("Outputting %d analog values", analog_channels);
169 ctx->num_analog_channels = analog_channels;
170 }
171 if (logic_channels) {
172 sr_info("Outputting %d logic values", logic_channels);
173 ctx->num_logic_channels = logic_channels;
02604ed6 174 }
9e24c8bc
MM
175 ctx->channels = g_malloc(sizeof(struct ctx_channel)
176 * (ctx->num_analog_channels + ctx->num_logic_channels));
02604ed6 177
0f3d8c89 178 /* Once more to map the enabled channels. */
9e24c8bc
MM
179 ctx->channel_count = g_slist_length(o->sdi->channels);
180 for (i = 0, l = o->sdi->channels; l; l = l->next) {
0f3d8c89 181 ch = l->data;
c04cf9aa 182 if (ch->enabled) {
9e24c8bc
MM
183 if (ch->type == SR_CHANNEL_ANALOG) {
184 ctx->channels[i].min = FLT_MAX;
185 ctx->channels[i].max = FLT_MIN;
186 } else if (ch->type == SR_CHANNEL_LOGIC) {
187 ctx->channels[i].min = 0;
188 ctx->channels[i].max = 1;
189 } else {
190 sr_warn("Unknown channel type %d.", ch->type);
191 }
192 ctx->channels[i++].ch = ch;
c04cf9aa 193 }
0f3d8c89 194 }
02604ed6 195
0f3d8c89
BV
196 return SR_OK;
197}
02604ed6 198
9e24c8bc
MM
199static const char *xlabels[] = {
200 "samples", "milliseconds", "microseconds", "nanoseconds", "picoseconds",
201 "femtoseconds", "attoseconds",
202};
203
204static GString *gen_header(const struct sr_output *o,
205 const struct sr_datafeed_header *hdr)
0f3d8c89
BV
206{
207 struct context *ctx;
208 struct sr_channel *ch;
209 GVariant *gvar;
210 GString *header;
211 GSList *l;
9e24c8bc
MM
212 unsigned int num_channels, i;
213 uint64_t samplerate = 0, sr;
0f3d8c89 214 char *samplerate_s;
02604ed6 215
d686c5ec 216 ctx = o->priv;
0f3d8c89 217 header = g_string_sized_new(512);
02604ed6 218
9e24c8bc
MM
219 if (ctx->period == 0) {
220 if (sr_config_get(o->sdi->driver, o->sdi, NULL,
221 SR_CONF_SAMPLERATE, &gvar) == SR_OK) {
222 samplerate = g_variant_get_uint64(gvar);
0f3d8c89
BV
223 g_variant_unref(gvar);
224 }
9e24c8bc
MM
225
226 i = 0;
227 sr = 1;
228 while (sr < samplerate) {
229 i++;
230 sr *= 1000;
231 }
232 if (samplerate)
233 ctx->period = sr / samplerate;
234 if (i < ARRAY_SIZE(xlabels))
235 ctx->xlabel = xlabels[i];
236 sr_info("Set sample period to %" PRIu64 " %s",
237 ctx->period, ctx->xlabel);
0f3d8c89 238 }
cf1d5f17 239 ctx->title = (o->sdi && o->sdi->driver) ? o->sdi->driver->longname : "unknown";
9e24c8bc
MM
240
241 /* Some metadata */
242 if (ctx->header && !ctx->did_header) {
243 /* save_gnuplot knows how many lines we print. */
244 g_string_append_printf(header,
245 "%s CSV generated by %s %s\n%s from %s on %s",
246 ctx->comment, PACKAGE_NAME,
247 SR_PACKAGE_VERSION_STRING, ctx->comment,
248 ctx->title, ctime(&hdr->starttime.tv_sec));
249
250 /* Columns / channels */
251 num_channels = g_slist_length(o->sdi->channels);
252 g_string_append_printf(header, "%s Channels (%d/%d):",
253 ctx->comment, ctx->num_analog_channels +
254 ctx->num_logic_channels, num_channels);
255 for (i = 0, l = o->sdi->channels; l; l = l->next, i++) {
256 ch = l->data;
257 if (ch->enabled)
258 g_string_append_printf(header, " %s,", ch->name);
259 }
260 if (o->sdi->channels)
261 /* Drop last separator. */
262 g_string_truncate(header, header->len - 1);
263 g_string_append_printf(header, "\n");
264 if (samplerate != 0) {
265 samplerate_s = sr_samplerate_string(samplerate);
266 g_string_append_printf(header, "%s Samplerate: %s\n",
267 ctx->comment, samplerate_s);
268 g_free(samplerate_s);
269 }
270 ctx->did_header = TRUE;
0f3d8c89 271 }
02604ed6 272
0f3d8c89 273 return header;
02604ed6
UH
274}
275
9e24c8bc
MM
276/*
277 * Analog devices can have samples of different types. Since each
278 * packet has only one meaning, it is restricted to having at most one
279 * type of data. So they can send multiple packets for a single sample.
280 * To further complicate things, they can send multiple samples in a
281 * single packet.
282 *
283 * So we need to pull any channels of interest out of a packet and save
284 * them until we have complete samples to output. Some devices make this
285 * simple by sending DF_FRAME_BEGIN/DF_FRAME_END packets, the latter of which
286 * signals the end of a set of samples, so we can dump things there.
287 *
288 * At least one driver (the demo driver) sends packets that contain parts of
289 * multiple samples without wrapping them in DF_FRAME. Possibly this driver
290 * is buggy, but it's also the standard for testing, so it has to be supported
291 * as is.
292 *
293 * Many assumptions about the "shape" of the data here:
294 *
295 * All of the data for a channel is assumed to be in one frame;
296 * otherwise the data in the second packet will overwrite the data in
297 * the first packet.
298 */
299static void process_analog(struct context *ctx,
300 const struct sr_datafeed_analog *analog)
301{
302 int ret;
303 unsigned int i, j, c, num_channels;
304 struct sr_analog_meaning *meaning;
305 GSList *l;
306 float *fdata = NULL;
307
308 if (!ctx->analog_samples) {
309 ctx->analog_samples = g_malloc(analog->num_samples
310 * sizeof(float) * ctx->num_analog_channels);
311 if (!ctx->num_samples)
312 ctx->num_samples = analog->num_samples;
313 }
314 if (ctx->num_samples != analog->num_samples)
315 sr_warn("Expecting %u analog samples, got %u.",
316 ctx->num_samples, analog->num_samples);
317
318 meaning = analog->meaning;
319 num_channels = g_slist_length(meaning->channels);
320 ctx->channels_seen += num_channels;
321 sr_dbg("Processing packet of %u analog channels", num_channels);
e34dbc9d 322 fdata = g_malloc(analog->num_samples * num_channels * sizeof(float));
9e24c8bc
MM
323 if ((ret = sr_analog_to_float(analog, fdata)) != SR_OK)
324 sr_warn("Problems converting data to floating point values.");
325
326 for (i = 0; i < ctx->num_analog_channels + ctx->num_logic_channels; i++) {
327 if (ctx->channels[i].ch->type == SR_CHANNEL_ANALOG) {
328 sr_dbg("Looking for channel %s",
329 ctx->channels[i].ch->name);
330 for (l = meaning->channels, c = 0; l; l = l->next, c++) {
331 struct sr_channel *ch = l->data;
332 sr_dbg("Checking %s", ch->name);
333 if (ctx->channels[i].ch == l->data) {
334 for (j = 0; j < analog->num_samples; j++)
335 ctx->analog_samples[j * ctx->num_analog_channels + i] = fdata[j * num_channels + c];
336 break;
337 }
338 }
339 }
340 }
341 g_free(fdata);
342}
343
344/*
345 * We treat logic packets the same as analog packets, though it's not
346 * strictly required. This allows us to process mixed signals properly.
347 */
348static void process_logic(struct context *ctx,
349 const struct sr_datafeed_logic *logic)
c04cf9aa 350{
9e24c8bc
MM
351 unsigned int i, j, ch, num_samples;
352 int idx;
353 uint8_t *sample;
354
355 num_samples = logic->length / logic->unitsize;
356 ctx->channels_seen += ctx->logic_channel_count;
357 sr_dbg("Logic packet had %d channels", logic->unitsize * 8);
358 if (!ctx->logic_samples) {
359 ctx->logic_samples = g_malloc(num_samples * ctx->num_logic_channels);
360 if (!ctx->num_samples)
361 ctx->num_samples = num_samples;
362 }
363 if (ctx->num_samples != num_samples)
364 sr_warn("Expecting %u samples, got %u",
365 ctx->num_samples, num_samples);
366
367 for (j = ch = 0; ch < ctx->num_logic_channels; j++) {
368 if (ctx->channels[j].ch->type == SR_CHANNEL_LOGIC) {
369 for (i = 0; i <= logic->length - logic->unitsize; i += logic->unitsize) {
370 sample = logic->data + i;
371 idx = ctx->channels[ch].ch->index;
372 ctx->logic_samples[i * ctx->num_logic_channels + ch] = sample[idx / 8] & (1 << (idx % 8));
373 }
374 ch++;
375 }
376 }
377}
378
379static void dump_saved_values(struct context *ctx, GString **out)
380{
381 unsigned int i, j, analog_size, num_channels;
382 float *analog_sample, value;
383 uint8_t *logic_sample;
384
385 /* If we haven't seen samples we're expecting, skip them. */
386 if ((ctx->num_analog_channels && !ctx->analog_samples) ||
387 (ctx->num_logic_channels && !ctx->logic_samples)) {
388 sr_warn("Discarding partial packet");
c04cf9aa 389 } else {
9e24c8bc
MM
390 sr_info("Dumping %u samples", ctx->num_samples);
391
c04cf9aa 392 *out = g_string_sized_new(512);
9e24c8bc
MM
393 num_channels =
394 ctx->num_logic_channels + ctx->num_analog_channels;
395
396 if (ctx->label && !ctx->did_label) {
397 if (ctx->time)
398 g_string_append_printf(*out, "Time%s",
399 ctx->value);
400 for (i = 0; i < num_channels; i++) {
401 g_string_append_printf(*out, "%s%s",
402 ctx->channels[i].ch->name, ctx->value);
403 }
404 if (ctx->do_trigger)
405 g_string_append_printf(*out, "Trigger%s",
406 ctx->value);
407 /* Drop last separator. */
408 g_string_truncate(*out, (*out)->len - 1);
409 g_string_append(*out, ctx->record);
410
411 ctx->did_label = TRUE;
412 }
413
414 analog_size = ctx->num_analog_channels * sizeof(float);
415 if (ctx->dedup && !ctx->previous_sample)
416 ctx->previous_sample = g_malloc0(analog_size + ctx->num_logic_channels);
417
418 for (i = 0; i < ctx->num_samples; i++) {
419 ctx->sample_time += ctx->period;
420 analog_sample =
421 &ctx->analog_samples[i * ctx->num_analog_channels];
422 logic_sample =
423 &ctx->logic_samples[i * ctx->num_logic_channels];
424
425 if (ctx->dedup) {
426 if (i > 0 && i < ctx->num_samples - 1 &&
427 !memcmp(logic_sample, ctx->previous_sample,
428 ctx->num_logic_channels) &&
429 !memcmp(analog_sample,
430 ctx->previous_sample +
431 ctx->num_logic_channels,
432 analog_size))
433 continue;
434 memcpy(ctx->previous_sample, logic_sample,
435 ctx->num_logic_channels);
436 memcpy(ctx->previous_sample
437 + ctx->num_logic_channels,
438 analog_sample, analog_size);
439 }
440
441 if (ctx->time)
442 g_string_append_printf(*out, "%lu%s",
443 ctx->sample_time, ctx->value);
444
445 for (j = 0; j < num_channels; j++) {
446 if (ctx->channels[j].ch->type == SR_CHANNEL_ANALOG) {
447 value = ctx->analog_samples[i * ctx->num_analog_channels + j];
448 ctx->channels[j].max =
449 fmax(value, ctx->channels[j].max);
450 ctx->channels[j].min =
451 fmin(value, ctx->channels[j].min);
452 g_string_append_printf(*out, "%g%s",
453 value, ctx->value);
454 } else if (ctx->channels[j].ch->type == SR_CHANNEL_LOGIC) {
455 g_string_append_printf(*out, "%c%s",
456 ctx->logic_samples[i * ctx->num_logic_channels + j] ? '1' : '0', ctx->value);
457 } else {
458 sr_warn("Unexpected channel type: %d",
459 ctx->channels[i].ch->type);
460 }
461 }
462
463 if (ctx->do_trigger) {
464 g_string_append_printf(*out, "%d%s",
465 ctx->trigger, ctx->value);
466 ctx->trigger = FALSE;
467 }
468 g_string_truncate(*out, (*out)->len - 1);
469 g_string_append(*out, ctx->record);
470 }
c04cf9aa 471 }
9e24c8bc
MM
472
473 /* Discard all of the working space. */
474 g_free(ctx->previous_sample);
475 g_free(ctx->analog_samples);
476 g_free(ctx->logic_samples);
477 ctx->channels_seen = 0;
478 ctx->num_samples = 0;
479 ctx->previous_sample = NULL;
480 ctx->analog_samples = NULL;
481 ctx->logic_samples = NULL;
c04cf9aa
BG
482}
483
9e24c8bc 484static void save_gnuplot(struct context *ctx)
c04cf9aa 485{
9e24c8bc
MM
486 float offset, max, sum;
487 unsigned int i, num_channels;
488 GString *script;
489
490 script = g_string_sized_new(512);
491 g_string_append_printf(script, "set datafile separator '%s'\n",
492 ctx->value);
493 if (ctx->did_label)
494 g_string_append(script, "set key autotitle columnhead\n");
495 if (ctx->xlabel && ctx->time)
496 g_string_append_printf(script, "set xlabel '%s'\n",
497 ctx->xlabel);
498
499 g_string_append(script, "plot ");
500
501 num_channels = ctx->num_analog_channels + ctx->num_logic_channels;
502
503 /* Graph position and scaling. */
504 max = FLT_MIN;
505 sum = 0;
506 for (i = 0; i < num_channels; i++) {
507 ctx->channels[i].max =
508 ctx->channels[i].max - ctx->channels[i].min;
509 max = fmax(max, ctx->channels[i].max);
510 sum += ctx->channels[i].max;
511 }
512 sum = (ctx->scale ? max : sum / num_channels) / 4;
513 offset = sum;
514 for (i = num_channels; i > 0;) {
515 i--;
516 ctx->channels[i].min = offset - ctx->channels[i].min;
517 offset += sum + (ctx->scale ? max : ctx->channels[i].max);
518 }
c04cf9aa 519
9e24c8bc
MM
520 for (i = 0; i < num_channels; i++) {
521 sr_spew("Channel %d, min %g, max %g", i, ctx->channels[i].min,
522 ctx->channels[i].max);
523 g_string_append(script, "ARG1 ");
524 if (ctx->did_header)
525 g_string_append(script, "skip 4 ");
526 g_string_append_printf(script, "using %u:($%u * %g + %g), ",
527 ctx->time, i + 1 + ctx->time, ctx->scale ?
528 max / ctx->channels[i].max : 1, ctx->channels[i].min);
529 offset += 1.1 * (ctx->channels[i].max - ctx->channels[i].min);
c04cf9aa 530 }
9e24c8bc
MM
531 g_string_truncate(script, script->len - 2);
532 g_file_set_contents(ctx->gnuplot, script->str, script->len, NULL);
533 g_string_free(script, TRUE);
c04cf9aa
BG
534}
535
9e24c8bc
MM
536static int receive(const struct sr_output *o,
537 const struct sr_datafeed_packet *packet, GString **out)
02604ed6
UH
538{
539 struct context *ctx;
02604ed6 540
4829d37d
BV
541 *out = NULL;
542 if (!o || !o->sdi)
02604ed6 543 return SR_ERR_ARG;
d686c5ec 544 if (!(ctx = o->priv))
02604ed6 545 return SR_ERR_ARG;
02604ed6 546
9e24c8bc 547 sr_dbg("Got packet of type %d", packet->type);
4829d37d 548 switch (packet->type) {
9e24c8bc
MM
549 case SR_DF_HEADER:
550 *out = gen_header(o, packet->payload);
0f3d8c89 551 break;
9e24c8bc
MM
552 case SR_DF_TRIGGER:
553 ctx->trigger = TRUE;
c04cf9aa 554 break;
4829d37d 555 case SR_DF_LOGIC:
9e24c8bc 556 process_logic(ctx, packet->payload);
02604ed6 557 break;
edb691fc 558 case SR_DF_ANALOG:
9e24c8bc
MM
559 process_analog(ctx, packet->payload);
560 break;
561 case SR_DF_FRAME_BEGIN:
562 *out = g_string_new(ctx->frame);
563 /* And then fall through to... */
564 case SR_DF_END:
565 /* Got to end of frame/session with part of the data. */
566 if (ctx->channels_seen)
567 ctx->channels_seen = ctx->channel_count;
568 if (*ctx->gnuplot)
569 save_gnuplot(ctx);
c04cf9aa 570 break;
02604ed6
UH
571 }
572
9e24c8bc
MM
573 /* If we've got them all, dump the values. */
574 if (ctx->channels_seen >= ctx->channel_count)
575 dump_saved_values(ctx, out);
576
577 return SR_OK;
02604ed6
UH
578}
579
4829d37d 580static int cleanup(struct sr_output *o)
02604ed6
UH
581{
582 struct context *ctx;
02604ed6 583
4829d37d 584 if (!o || !o->sdi)
02604ed6 585 return SR_ERR_ARG;
02604ed6 586
d686c5ec
BV
587 if (o->priv) {
588 ctx = o->priv;
9e24c8bc
MM
589 g_free((gpointer)ctx->record);
590 g_free((gpointer)ctx->frame);
591 g_free((gpointer)ctx->comment);
592 g_free((gpointer)ctx->gnuplot);
593 g_free(ctx->previous_sample);
c04cf9aa 594 g_free(ctx->channels);
d686c5ec
BV
595 g_free(o->priv);
596 o->priv = NULL;
02604ed6
UH
597 }
598
02604ed6
UH
599 return SR_OK;
600}
601
9e24c8bc
MM
602static struct sr_option options[] = {
603 {"gnuplot", "gnuplot", "gnuplot script file name", NULL, NULL},
604 {"scale", "scale", "Scale gnuplot graphs", NULL, NULL},
605 {"value", "Value separator", "Character to print between values", NULL, NULL},
606 {"record", "Record separator", "String to print between records", NULL, NULL},
607 {"frame", "Frame seperator", "String to print between frames", NULL, NULL},
608 {"comment", "Comment start string", "String used at start of comment lines", NULL, NULL},
609 {"header", "Output header", "Output header comment with capture metdata", NULL, NULL},
610 {"label", "Label values", "Output labels for each value", NULL, NULL},
611 {"time", "Time column", "Output sample time as column 1", NULL, NULL},
612 {"trigger", "Trigger column", "Output trigger indicator as last column ", NULL, NULL},
613 {"dedup", "Dedup rows", "Set to false to output duplicate rows", NULL, NULL},
614 ALL_ZERO
615};
616
617static const struct sr_option *get_options(void)
618{
619 if (!options[0].def) {
620 options[0].def = g_variant_ref_sink(g_variant_new_string(""));
621 options[1].def = g_variant_ref_sink(g_variant_new_boolean(TRUE));
622 options[2].def = g_variant_ref_sink(g_variant_new_string(","));
623 options[3].def = g_variant_ref_sink(g_variant_new_string("\n"));
624 options[4].def = g_variant_ref_sink(g_variant_new_string("\n"));
625 options[5].def = g_variant_ref_sink(g_variant_new_string(";"));
626 options[6].def = g_variant_ref_sink(g_variant_new_boolean(TRUE));
627 options[7].def = g_variant_ref_sink(g_variant_new_boolean(TRUE));
628 options[8].def = g_variant_ref_sink(g_variant_new_boolean(TRUE));
629 options[9].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
630 options[10].def = g_variant_ref_sink(g_variant_new_boolean(TRUE));
631 }
632
633 return options;
634}
635
a755b0e1 636SR_PRIV struct sr_output_module output_csv = {
02604ed6 637 .id = "csv",
a755b0e1
BV
638 .name = "CSV",
639 .desc = "Comma-separated values",
9e24c8bc 640 .exts = (const char *[]){"csv", NULL},
3cd4b381 641 .flags = 0,
9e24c8bc 642 .options = get_options,
02604ed6 643 .init = init,
4829d37d
BV
644 .receive = receive,
645 .cleanup = cleanup,
02604ed6 646};