]> sigrok.org Git - libsigrok.git/blame - src/output/csv.c
Don't reference SR_PACKAGE_VERSION_STRING directly in output modules.
[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
2ea1fdf1 17 * along with this program; if not, see <http://www.gnu.org/licenses/>.
02604ed6
UH
18 */
19
9e24c8bc
MM
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 *
cad447d2
MM
43 * label: What to use for channel labels as the first line of output.
44 * Values are "channel", "units", "off". Defaults to "units".
9e24c8bc
MM
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 *
04a0e0dc 52 * dedup: Don't output duplicate rows. Defaults to FALSE. If time is off, then
9e24c8bc
MM
53 * this is forced to be off.
54 */
55
6ec6c43b 56#include <config.h>
65788048 57#include <math.h>
02604ed6
UH
58#include <stdlib.h>
59#include <string.h>
60#include <glib.h>
c1aae900 61#include <libsigrok/libsigrok.h>
45c59c8b 62#include "libsigrok-internal.h"
02604ed6 63
3544f848 64#define LOG_PREFIX "output/csv"
a944a84b 65
9e24c8bc
MM
66struct ctx_channel {
67 struct sr_channel *ch;
cad447d2 68 char *label;
9e24c8bc
MM
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;
cad447d2 81 gboolean label_do, label_did, label_names;
9e24c8bc
MM
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;
cad447d2 116 const char *label_string;
02604ed6 117 GSList *l;
a755b0e1 118
0f3d8c89 119 if (!o || !o->sdi)
02604ed6 120 return SR_ERR_ARG;
02604ed6 121
2a035e53 122 ctx = g_malloc0(sizeof(struct context));
d686c5ec 123 o->priv = ctx;
02604ed6 124
9e24c8bc
MM
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"));
cad447d2
MM
140 label_string = g_variant_get_string(
141 g_hash_table_lookup(options, "label"), NULL);
9e24c8bc
MM
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
cad447d2
MM
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
9e24c8bc
MM
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);
cad447d2
MM
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);
9e24c8bc
MM
160
161 analog_channels = logic_channels = 0;
ba7dd8bb
UH
162 /* Get the number of channels, and the unitsize. */
163 for (l = o->sdi->channels; l; l = l->next) {
164 ch = l->data;
9e24c8bc
MM
165 if (ch->type == SR_CHANNEL_LOGIC) {
166 ctx->logic_channel_count++;
167 if (ch->enabled)
168 logic_channels++;
c04cf9aa 169 }
9e24c8bc
MM
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;
02604ed6 180 }
9e24c8bc
MM
181 ctx->channels = g_malloc(sizeof(struct ctx_channel)
182 * (ctx->num_analog_channels + ctx->num_logic_channels));
02604ed6 183
0f3d8c89 184 /* Once more to map the enabled channels. */
9e24c8bc
MM
185 ctx->channel_count = g_slist_length(o->sdi->channels);
186 for (i = 0, l = o->sdi->channels; l; l = l->next) {
0f3d8c89 187 ch = l->data;
c04cf9aa 188 if (ch->enabled) {
9e24c8bc
MM
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 }
cad447d2
MM
198 if (ctx->label_do && ctx->label_names)
199 ctx->channels[i].label = ch->name;
9e24c8bc 200 ctx->channels[i++].ch = ch;
c04cf9aa 201 }
0f3d8c89 202 }
02604ed6 203
0f3d8c89
BV
204 return SR_OK;
205}
02604ed6 206
9e24c8bc
MM
207static const char *xlabels[] = {
208 "samples", "milliseconds", "microseconds", "nanoseconds", "picoseconds",
209 "femtoseconds", "attoseconds",
210};
211
212static GString *gen_header(const struct sr_output *o,
213 const struct sr_datafeed_header *hdr)
0f3d8c89
BV
214{
215 struct context *ctx;
216 struct sr_channel *ch;
217 GVariant *gvar;
218 GString *header;
a21fef07 219 GSList *channels, *l;
9e24c8bc
MM
220 unsigned int num_channels, i;
221 uint64_t samplerate = 0, sr;
0f3d8c89 222 char *samplerate_s;
02604ed6 223
d686c5ec 224 ctx = o->priv;
0f3d8c89 225 header = g_string_sized_new(512);
02604ed6 226
9e24c8bc
MM
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);
0f3d8c89
BV
231 g_variant_unref(gvar);
232 }
9e24c8bc
MM
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);
0f3d8c89 246 }
cf1d5f17 247 ctx->title = (o->sdi && o->sdi->driver) ? o->sdi->driver->longname : "unknown";
9e24c8bc
MM
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,
2868bca3 255 sr_package_version_string_get(), ctx->comment,
9e24c8bc
MM
256 ctx->title, ctime(&hdr->starttime.tv_sec));
257
258 /* Columns / channels */
a21fef07
GS
259 channels = o->sdi ? o->sdi->channels : NULL;
260 num_channels = g_slist_length(channels);
9e24c8bc
MM
261 g_string_append_printf(header, "%s Channels (%d/%d):",
262 ctx->comment, ctx->num_analog_channels +
263 ctx->num_logic_channels, num_channels);
a21fef07 264 for (l = channels; l; l = l->next) {
9e24c8bc
MM
265 ch = l->data;
266 if (ch->enabled)
267 g_string_append_printf(header, " %s,", ch->name);
268 }
a21fef07 269 if (channels) {
9e24c8bc
MM
270 /* Drop last separator. */
271 g_string_truncate(header, header->len - 1);
a21fef07 272 }
9e24c8bc
MM
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;
0f3d8c89 281 }
02604ed6 282
0f3d8c89 283 return header;
02604ed6
UH
284}
285
9e24c8bc
MM
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 */
309static void process_analog(struct context *ctx,
310 const struct sr_datafeed_analog *analog)
311{
312 int ret;
a551cb09
GS
313 size_t num_rcvd_ch, num_have_ch;
314 size_t idx_have, idx_smpl, idx_rcvd;
823b0e29 315 size_t idx_send;
9e24c8bc
MM
316 struct sr_analog_meaning *meaning;
317 GSList *l;
318 float *fdata = NULL;
b078dddb 319 struct sr_channel *ch;
9e24c8bc
MM
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;
a551cb09
GS
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));
9e24c8bc
MM
336 if ((ret = sr_analog_to_float(analog, fdata)) != SR_OK)
337 sr_warn("Problems converting data to floating point values.");
338
a551cb09 339 num_have_ch = ctx->num_analog_channels + ctx->num_logic_channels;
823b0e29 340 idx_send = 0;
a551cb09
GS
341 for (idx_have = 0; idx_have < num_have_ch; idx_have++) {
342 if (ctx->channels[idx_have].ch->type != SR_CHANNEL_ANALOG)
b078dddb
GS
343 continue;
344 sr_dbg("Looking for channel %s",
a551cb09
GS
345 ctx->channels[idx_have].ch->name);
346 for (l = meaning->channels, idx_rcvd = 0; l; l = l->next, idx_rcvd++) {
b078dddb
GS
347 ch = l->data;
348 sr_dbg("Checking %s", ch->name);
a551cb09 349 if (ctx->channels[idx_have].ch != ch)
b078dddb
GS
350 continue;
351 if (ctx->label_do && !ctx->label_names) {
352 sr_analog_unit_to_string(analog,
a551cb09 353 &ctx->channels[idx_have].label);
9e24c8bc 354 }
a551cb09 355 for (idx_smpl = 0; idx_smpl < analog->num_samples; idx_smpl++)
823b0e29 356 ctx->analog_samples[idx_smpl * ctx->num_analog_channels + idx_send] = fdata[idx_smpl * num_rcvd_ch + idx_rcvd];
b078dddb 357 break;
9e24c8bc 358 }
823b0e29 359 idx_send++;
9e24c8bc
MM
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 */
368static void process_logic(struct context *ctx,
369 const struct sr_datafeed_logic *logic)
c04cf9aa 370{
9e24c8bc
MM
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) {
d3cc09a6
WS
389 for (i = 0; i < num_samples; i++) {
390 sample = logic->data + i * logic->unitsize;
10892c5b 391 idx = ctx->channels[j].ch->index;
cad447d2 392 if (ctx->label_do && !ctx->label_names)
6a235225 393 ctx->channels[j].label = "logic";
9e24c8bc
MM
394 ctx->logic_samples[i * ctx->num_logic_channels + ch] = sample[idx / 8] & (1 << (idx % 8));
395 }
396 ch++;
397 }
398 }
399}
400
401static 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");
c04cf9aa 411 } else {
9e24c8bc
MM
412 sr_info("Dumping %u samples", ctx->num_samples);
413
c04cf9aa 414 *out = g_string_sized_new(512);
9e24c8bc
MM
415 num_channels =
416 ctx->num_logic_channels + ctx->num_analog_channels;
417
cad447d2 418 if (ctx->label_do) {
9e24c8bc 419 if (ctx->time)
cad447d2
MM
420 g_string_append_printf(*out, "%s%s",
421 ctx->label_names ? "Time" :
422 ctx->xlabel, ctx->value);
9e24c8bc
MM
423 for (i = 0; i < num_channels; i++) {
424 g_string_append_printf(*out, "%s%s",
cad447d2
MM
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);
9e24c8bc
MM
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
cad447d2 437 ctx->label_do = FALSE;
9e24c8bc
MM
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)
8005151b 468 g_string_append_printf(*out, "%" PRIu64 "%s",
9e24c8bc
MM
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 }
c04cf9aa 497 }
9e24c8bc
MM
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;
c04cf9aa
BG
508}
509
9e24c8bc 510static void save_gnuplot(struct context *ctx)
c04cf9aa 511{
9e24c8bc
MM
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);
cad447d2 519 if (ctx->label_did)
9e24c8bc
MM
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 }
c04cf9aa 545
9e24c8bc
MM
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);
c04cf9aa 556 }
9e24c8bc
MM
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);
c04cf9aa
BG
560}
561
9e24c8bc
MM
562static int receive(const struct sr_output *o,
563 const struct sr_datafeed_packet *packet, GString **out)
02604ed6
UH
564{
565 struct context *ctx;
02604ed6 566
4829d37d
BV
567 *out = NULL;
568 if (!o || !o->sdi)
02604ed6 569 return SR_ERR_ARG;
d686c5ec 570 if (!(ctx = o->priv))
02604ed6 571 return SR_ERR_ARG;
02604ed6 572
9e24c8bc 573 sr_dbg("Got packet of type %d", packet->type);
4829d37d 574 switch (packet->type) {
9e24c8bc
MM
575 case SR_DF_HEADER:
576 *out = gen_header(o, packet->payload);
0f3d8c89 577 break;
9e24c8bc
MM
578 case SR_DF_TRIGGER:
579 ctx->trigger = TRUE;
c04cf9aa 580 break;
4829d37d 581 case SR_DF_LOGIC:
9e24c8bc 582 process_logic(ctx, packet->payload);
02604ed6 583 break;
edb691fc 584 case SR_DF_ANALOG:
9e24c8bc
MM
585 process_analog(ctx, packet->payload);
586 break;
587 case SR_DF_FRAME_BEGIN:
588 *out = g_string_new(ctx->frame);
317c97be 589 /* Fallthrough */
9e24c8bc
MM
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);
c04cf9aa 596 break;
02604ed6
UH
597 }
598
9e24c8bc
MM
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;
02604ed6
UH
604}
605
4829d37d 606static int cleanup(struct sr_output *o)
02604ed6
UH
607{
608 struct context *ctx;
02604ed6 609
4829d37d 610 if (!o || !o->sdi)
02604ed6 611 return SR_ERR_ARG;
02604ed6 612
d686c5ec
BV
613 if (o->priv) {
614 ctx = o->priv;
9e24c8bc
MM
615 g_free((gpointer)ctx->record);
616 g_free((gpointer)ctx->frame);
617 g_free((gpointer)ctx->comment);
618 g_free((gpointer)ctx->gnuplot);
7e7d7bc0 619 g_free((gpointer)ctx->value);
9e24c8bc 620 g_free(ctx->previous_sample);
c04cf9aa 621 g_free(ctx->channels);
d686c5ec
BV
622 g_free(o->priv);
623 o->priv = NULL;
02604ed6
UH
624 }
625
02604ed6
UH
626 return SR_OK;
627}
628
9e24c8bc
MM
629static 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},
51d64bf5 634 {"frame", "Frame separator", "String to print between frames", NULL, NULL},
9e24c8bc
MM
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},
cad447d2 637 {"label", "Label values", "Type of column labels", NULL, NULL},
9e24c8bc
MM
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
644static const struct sr_option *get_options(void)
645{
7c59d8f3
UH
646 GSList *l = NULL;
647
9e24c8bc
MM
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));
cad447d2 656 options[7].def = g_variant_ref_sink(g_variant_new_string("units"));
7c59d8f3
UH
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;
9e24c8bc
MM
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));
04a0e0dc 663 options[10].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
9e24c8bc
MM
664 }
665
666 return options;
667}
668
a755b0e1 669SR_PRIV struct sr_output_module output_csv = {
02604ed6 670 .id = "csv",
a755b0e1
BV
671 .name = "CSV",
672 .desc = "Comma-separated values",
9e24c8bc 673 .exts = (const char *[]){"csv", NULL},
3cd4b381 674 .flags = 0,
9e24c8bc 675 .options = get_options,
02604ed6 676 .init = init,
4829d37d
BV
677 .receive = receive,
678 .cleanup = cleanup,
02604ed6 679};