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