2 * This file is part of the libsigrok project.
4 * Copyright (C) 2011 Uwe Hermann <uwe@hermann-uwe.de>
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.
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.
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/>.
21 * Options and their values:
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.
28 * scale: The gnuplot graphs are scaled so they all have the same
29 * peak-to-peak distance. Defaults to TRUE.
31 * value: The string used to separate values in a record. Defaults to ','.
33 * record: The string to use to separate records. Default is newline. gnuplot
34 * files must use newline.
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.
39 * comment: The string that starts a comment line. Defaults to ';'.
41 * header: Print header comment with capture metadata. Defaults to TRUE.
43 * label: What to use for channel labels as the first line of output.
44 * Values are "channel", "units", "off". Defaults to "units".
46 * time: Whether or not the first column should include the time the sample
47 * was taken. Defaults to FALSE.
49 * trigger: Whether or not to add a "trigger" column as the last column.
52 * dedup: Don't output duplicate rows. Defaults to FALSE. If time is off, then
53 * this is forced to be off.
61 #include <libsigrok/libsigrok.h>
62 #include "libsigrok-internal.h"
64 #define LOG_PREFIX "output/csv"
67 struct sr_channel *ch;
80 gboolean header, did_header;
81 gboolean label_do, label_did, label_names;
87 unsigned int num_analog_channels;
88 unsigned int num_logic_channels;
89 struct ctx_channel *channels;
94 uint32_t channel_count, logic_channel_count;
95 uint32_t channels_seen;
97 uint64_t sample_scale;
98 uint64_t out_sample_count;
99 uint8_t *previous_sample;
100 float *analog_samples;
101 uint8_t *logic_samples;
102 const char *xlabel; /* Don't free: will point to a static string. */
103 const char *title; /* Don't free: will point into the driver struct. */
108 * - Option to print comma-separated bits, or whole bytes/words (for 8/16
109 * channel LAs) as ASCII/hex etc. etc.
112 static int init(struct sr_output *o, GHashTable *options)
114 unsigned int i, analog_channels, logic_channels;
116 struct sr_channel *ch;
117 const char *label_string;
123 ctx = g_malloc0(sizeof(struct context));
127 ctx->gnuplot = g_strdup(g_variant_get_string(
128 g_hash_table_lookup(options, "gnuplot"), NULL));
129 ctx->scale = g_variant_get_boolean(g_hash_table_lookup(options, "scale"));
130 ctx->value = g_strdup(g_variant_get_string(
131 g_hash_table_lookup(options, "value"), NULL));
132 ctx->record = g_strdup(g_variant_get_string(
133 g_hash_table_lookup(options, "record"), NULL));
134 ctx->frame = g_strdup(g_variant_get_string(
135 g_hash_table_lookup(options, "frame"), NULL));
136 ctx->comment = g_strdup(g_variant_get_string(
137 g_hash_table_lookup(options, "comment"), NULL));
138 ctx->header = g_variant_get_boolean(g_hash_table_lookup(options, "header"));
139 ctx->time = g_variant_get_boolean(g_hash_table_lookup(options, "time"));
140 ctx->do_trigger = g_variant_get_boolean(g_hash_table_lookup(options, "trigger"));
141 label_string = g_variant_get_string(
142 g_hash_table_lookup(options, "label"), NULL);
143 ctx->dedup = g_variant_get_boolean(g_hash_table_lookup(options, "dedup"));
144 ctx->dedup &= ctx->time;
146 if (*ctx->gnuplot && g_strcmp0(ctx->record, "\n"))
147 sr_warn("gnuplot record separator must be newline.");
149 if (*ctx->gnuplot && strlen(ctx->value) > 1)
150 sr_warn("gnuplot doesn't support multichar value separators.");
152 if ((ctx->label_did = ctx->label_do = g_strcmp0(label_string, "off") != 0))
153 ctx->label_names = g_strcmp0(label_string, "units") != 0;
155 sr_dbg("gnuplot = '%s', scale = %d", ctx->gnuplot, ctx->scale);
156 sr_dbg("value = '%s', record = '%s', frame = '%s', comment = '%s'",
157 ctx->value, ctx->record, ctx->frame, ctx->comment);
158 sr_dbg("header = %d, time = %d, do_trigger = %d, dedup = %d",
159 ctx->header, ctx->time, ctx->do_trigger, ctx->dedup);
160 sr_dbg("label_do = %d, label_names = %d", ctx->label_do, ctx->label_names);
162 analog_channels = logic_channels = 0;
163 /* Get the number of channels, and the unitsize. */
164 for (l = o->sdi->channels; l; l = l->next) {
166 if (ch->type == SR_CHANNEL_LOGIC) {
167 ctx->logic_channel_count++;
171 if (ch->type == SR_CHANNEL_ANALOG && ch->enabled)
174 if (analog_channels) {
175 sr_info("Outputting %d analog values", analog_channels);
176 ctx->num_analog_channels = analog_channels;
178 if (logic_channels) {
179 sr_info("Outputting %d logic values", logic_channels);
180 ctx->num_logic_channels = logic_channels;
182 ctx->channels = g_malloc(sizeof(struct ctx_channel)
183 * (ctx->num_analog_channels + ctx->num_logic_channels));
185 /* Once more to map the enabled channels. */
186 ctx->channel_count = g_slist_length(o->sdi->channels);
187 for (i = 0, l = o->sdi->channels; l; l = l->next) {
190 if (ch->type == SR_CHANNEL_ANALOG) {
191 ctx->channels[i].min = FLT_MAX;
192 ctx->channels[i].max = FLT_MIN;
193 } else if (ch->type == SR_CHANNEL_LOGIC) {
194 ctx->channels[i].min = 0;
195 ctx->channels[i].max = 1;
197 sr_warn("Unknown channel type %d.", ch->type);
199 if (ctx->label_do && ctx->label_names)
200 ctx->channels[i].label = ch->name;
201 ctx->channels[i++].ch = ch;
208 static const char *xlabels[] = {
209 "samples", "milliseconds", "microseconds", "nanoseconds", "picoseconds",
210 "femtoseconds", "attoseconds",
213 static GString *gen_header(const struct sr_output *o,
214 const struct sr_datafeed_header *hdr)
217 struct sr_channel *ch;
220 GSList *channels, *l;
221 unsigned int num_channels, i;
225 header = g_string_sized_new(512);
227 if (ctx->sample_rate == 0) {
228 if (sr_config_get(o->sdi->driver, o->sdi, NULL,
229 SR_CONF_SAMPLERATE, &gvar) == SR_OK) {
230 ctx->sample_rate = g_variant_get_uint64(gvar);
231 g_variant_unref(gvar);
235 ctx->sample_scale = 1;
236 while (ctx->sample_scale < ctx->sample_rate) {
238 ctx->sample_scale *= 1000;
240 if (i < ARRAY_SIZE(xlabels))
241 ctx->xlabel = xlabels[i];
242 sr_info("Set sample rate, scale to %" PRIu64 ", %" PRIu64 " %s",
243 ctx->sample_rate, ctx->sample_scale, ctx->xlabel);
245 ctx->title = (o->sdi && o->sdi->driver) ? o->sdi->driver->longname : "unknown";
248 if (ctx->header && !ctx->did_header) {
249 /* save_gnuplot knows how many lines we print. */
250 g_string_append_printf(header,
251 "%s CSV generated by %s %s\n%s from %s on %s",
252 ctx->comment, PACKAGE_NAME,
253 sr_package_version_string_get(), ctx->comment,
254 ctx->title, ctime(&hdr->starttime.tv_sec));
256 /* Columns / channels */
257 channels = o->sdi ? o->sdi->channels : NULL;
258 num_channels = g_slist_length(channels);
259 g_string_append_printf(header, "%s Channels (%d/%d):",
260 ctx->comment, ctx->num_analog_channels +
261 ctx->num_logic_channels, num_channels);
262 for (l = channels; l; l = l->next) {
265 g_string_append_printf(header, " %s,", ch->name);
268 /* Drop last separator. */
269 g_string_truncate(header, header->len - 1);
271 g_string_append_printf(header, "\n");
272 if (ctx->sample_rate != 0) {
273 samplerate_s = sr_samplerate_string(ctx->sample_rate);
274 g_string_append_printf(header, "%s Samplerate: %s\n",
275 ctx->comment, samplerate_s);
276 g_free(samplerate_s);
278 ctx->did_header = TRUE;
281 /* Time column requested but samplerate unknown. Emit a warning. */
282 if (ctx->time && !ctx->sample_rate)
283 sr_warn("Samplerate unknown, cannot provide timestamps.");
289 * Analog devices can have samples of different types. Since each
290 * packet has only one meaning, it is restricted to having at most one
291 * type of data. So they can send multiple packets for a single sample.
292 * To further complicate things, they can send multiple samples in a
295 * So we need to pull any channels of interest out of a packet and save
296 * them until we have complete samples to output. Some devices make this
297 * simple by sending DF_FRAME_BEGIN/DF_FRAME_END packets, the latter of which
298 * signals the end of a set of samples, so we can dump things there.
300 * At least one driver (the demo driver) sends packets that contain parts of
301 * multiple samples without wrapping them in DF_FRAME. Possibly this driver
302 * is buggy, but it's also the standard for testing, so it has to be supported
305 * Many assumptions about the "shape" of the data here:
307 * All of the data for a channel is assumed to be in one frame;
308 * otherwise the data in the second packet will overwrite the data in
311 static void process_analog(struct context *ctx,
312 const struct sr_datafeed_analog *analog)
315 size_t num_rcvd_ch, num_have_ch;
316 size_t idx_have, idx_smpl, idx_rcvd;
318 struct sr_analog_meaning *meaning;
321 struct sr_channel *ch;
323 if (!ctx->analog_samples) {
324 ctx->analog_samples = g_malloc(analog->num_samples
325 * sizeof(float) * ctx->num_analog_channels);
326 if (!ctx->num_samples)
327 ctx->num_samples = analog->num_samples;
329 if (ctx->num_samples != analog->num_samples)
330 sr_warn("Expecting %u analog samples, got %u.",
331 ctx->num_samples, analog->num_samples);
333 meaning = analog->meaning;
334 num_rcvd_ch = g_slist_length(meaning->channels);
335 ctx->channels_seen += num_rcvd_ch;
336 sr_dbg("Processing packet of %zu analog channels", num_rcvd_ch);
337 fdata = g_malloc(analog->num_samples * num_rcvd_ch * sizeof(float));
338 if ((ret = sr_analog_to_float(analog, fdata)) != SR_OK)
339 sr_warn("Problems converting data to floating point values.");
341 num_have_ch = ctx->num_analog_channels + ctx->num_logic_channels;
343 for (idx_have = 0; idx_have < num_have_ch; idx_have++) {
344 if (ctx->channels[idx_have].ch->type != SR_CHANNEL_ANALOG)
346 sr_dbg("Looking for channel %s",
347 ctx->channels[idx_have].ch->name);
348 for (l = meaning->channels, idx_rcvd = 0; l; l = l->next, idx_rcvd++) {
350 sr_dbg("Checking %s", ch->name);
351 if (ctx->channels[idx_have].ch != ch)
353 if (ctx->label_do && !ctx->label_names) {
354 sr_analog_unit_to_string(analog,
355 &ctx->channels[idx_have].label);
357 for (idx_smpl = 0; idx_smpl < analog->num_samples; idx_smpl++)
358 ctx->analog_samples[idx_smpl * ctx->num_analog_channels + idx_send] = fdata[idx_smpl * num_rcvd_ch + idx_rcvd];
367 * We treat logic packets the same as analog packets, though it's not
368 * strictly required. This allows us to process mixed signals properly.
370 static void process_logic(struct context *ctx,
371 const struct sr_datafeed_logic *logic)
373 unsigned int i, j, ch, num_samples;
377 num_samples = logic->length / logic->unitsize;
378 ctx->channels_seen += ctx->logic_channel_count;
379 sr_dbg("Logic packet had %d channels", logic->unitsize * 8);
380 if (!ctx->logic_samples) {
381 ctx->logic_samples = g_malloc(num_samples * ctx->num_logic_channels);
382 if (!ctx->num_samples)
383 ctx->num_samples = num_samples;
385 if (ctx->num_samples != num_samples)
386 sr_warn("Expecting %u samples, got %u",
387 ctx->num_samples, num_samples);
389 for (j = ch = 0; ch < ctx->num_logic_channels; j++) {
390 if (ctx->channels[j].ch->type == SR_CHANNEL_LOGIC) {
391 for (i = 0; i < num_samples; i++) {
392 sample = logic->data + i * logic->unitsize;
393 idx = ctx->channels[j].ch->index;
394 if (ctx->label_do && !ctx->label_names)
395 ctx->channels[j].label = "logic";
396 ctx->logic_samples[i * ctx->num_logic_channels + ch] = sample[idx / 8] & (1 << (idx % 8));
403 static void dump_saved_values(struct context *ctx, GString **out)
405 unsigned int i, j, analog_size, num_channels;
406 double sample_time_dbl;
407 uint64_t sample_time_u64;
408 float *analog_sample, value;
409 uint8_t *logic_sample;
411 /* If we haven't seen samples we're expecting, skip them. */
412 if ((ctx->num_analog_channels && !ctx->analog_samples) ||
413 (ctx->num_logic_channels && !ctx->logic_samples)) {
414 sr_warn("Discarding partial packet");
416 sr_info("Dumping %u samples", ctx->num_samples);
418 *out = g_string_sized_new(512);
420 ctx->num_logic_channels + ctx->num_analog_channels;
424 g_string_append_printf(*out, "%s%s",
425 ctx->label_names ? "Time" : ctx->xlabel,
427 for (i = 0; i < num_channels; i++) {
428 g_string_append_printf(*out, "%s%s",
429 ctx->channels[i].label, ctx->value);
430 if (ctx->channels[i].ch->type == SR_CHANNEL_ANALOG
432 g_free(ctx->channels[i].label);
435 g_string_append_printf(*out, "Trigger%s",
437 /* Drop last separator. */
438 g_string_truncate(*out, (*out)->len - 1);
439 g_string_append(*out, ctx->record);
441 ctx->label_do = FALSE;
444 analog_size = ctx->num_analog_channels * sizeof(float);
445 if (ctx->dedup && !ctx->previous_sample)
446 ctx->previous_sample = g_malloc0(analog_size + ctx->num_logic_channels);
448 for (i = 0; i < ctx->num_samples; i++) {
450 &ctx->analog_samples[i * ctx->num_analog_channels];
452 &ctx->logic_samples[i * ctx->num_logic_channels];
455 if (i > 0 && i < ctx->num_samples - 1 &&
456 !memcmp(logic_sample, ctx->previous_sample,
457 ctx->num_logic_channels) &&
458 !memcmp(analog_sample,
459 ctx->previous_sample +
460 ctx->num_logic_channels,
463 memcpy(ctx->previous_sample, logic_sample,
464 ctx->num_logic_channels);
465 memcpy(ctx->previous_sample
466 + ctx->num_logic_channels,
467 analog_sample, analog_size);
470 if (ctx->time && !ctx->sample_rate) {
471 g_string_append_printf(*out, "0%s", ctx->value);
472 } else if (ctx->time) {
473 sample_time_dbl = ctx->out_sample_count++;
474 sample_time_dbl /= ctx->sample_rate;
475 sample_time_dbl *= ctx->sample_scale;
476 sample_time_u64 = sample_time_dbl;
477 g_string_append_printf(*out, "%" PRIu64 "%s",
478 sample_time_u64, ctx->value);
481 for (j = 0; j < num_channels; j++) {
482 if (ctx->channels[j].ch->type == SR_CHANNEL_ANALOG) {
483 value = ctx->analog_samples[i * ctx->num_analog_channels + j];
484 ctx->channels[j].max =
485 fmax(value, ctx->channels[j].max);
486 ctx->channels[j].min =
487 fmin(value, ctx->channels[j].min);
488 g_string_append_printf(*out, "%g%s",
490 } else if (ctx->channels[j].ch->type == SR_CHANNEL_LOGIC) {
491 g_string_append_printf(*out, "%c%s",
492 ctx->logic_samples[i * ctx->num_logic_channels + j] ? '1' : '0', ctx->value);
494 sr_warn("Unexpected channel type: %d",
495 ctx->channels[i].ch->type);
499 if (ctx->do_trigger) {
500 g_string_append_printf(*out, "%d%s",
501 ctx->trigger, ctx->value);
502 ctx->trigger = FALSE;
504 g_string_truncate(*out, (*out)->len - 1);
505 g_string_append(*out, ctx->record);
509 /* Discard all of the working space. */
510 g_free(ctx->previous_sample);
511 g_free(ctx->analog_samples);
512 g_free(ctx->logic_samples);
513 ctx->channels_seen = 0;
514 ctx->num_samples = 0;
515 ctx->previous_sample = NULL;
516 ctx->analog_samples = NULL;
517 ctx->logic_samples = NULL;
520 static void save_gnuplot(struct context *ctx)
522 float offset, max, sum;
523 unsigned int i, num_channels;
526 script = g_string_sized_new(512);
527 g_string_append_printf(script, "set datafile separator '%s'\n",
530 g_string_append(script, "set key autotitle columnhead\n");
531 if (ctx->xlabel && ctx->time)
532 g_string_append_printf(script, "set xlabel '%s'\n",
535 g_string_append(script, "plot ");
537 num_channels = ctx->num_analog_channels + ctx->num_logic_channels;
539 /* Graph position and scaling. */
542 for (i = 0; i < num_channels; i++) {
543 ctx->channels[i].max =
544 ctx->channels[i].max - ctx->channels[i].min;
545 max = fmax(max, ctx->channels[i].max);
546 sum += ctx->channels[i].max;
548 sum = (ctx->scale ? max : sum / num_channels) / 4;
550 for (i = num_channels; i > 0;) {
552 ctx->channels[i].min = offset - ctx->channels[i].min;
553 offset += sum + (ctx->scale ? max : ctx->channels[i].max);
556 for (i = 0; i < num_channels; i++) {
557 sr_spew("Channel %d, min %g, max %g", i, ctx->channels[i].min,
558 ctx->channels[i].max);
559 g_string_append(script, "ARG1 ");
561 g_string_append(script, "skip 4 ");
562 g_string_append_printf(script, "using %u:($%u * %g + %g), ",
563 ctx->time, i + 1 + ctx->time, ctx->scale ?
564 max / ctx->channels[i].max : 1, ctx->channels[i].min);
565 offset += 1.1 * (ctx->channels[i].max - ctx->channels[i].min);
567 g_string_truncate(script, script->len - 2);
568 g_file_set_contents(ctx->gnuplot, script->str, script->len, NULL);
569 g_string_free(script, TRUE);
572 static int receive(const struct sr_output *o,
573 const struct sr_datafeed_packet *packet, GString **out)
580 if (!(ctx = o->priv))
583 sr_dbg("Got packet of type %d", packet->type);
584 switch (packet->type) {
586 *out = gen_header(o, packet->payload);
592 process_logic(ctx, packet->payload);
595 process_analog(ctx, packet->payload);
597 case SR_DF_FRAME_BEGIN:
598 *out = g_string_new(ctx->frame);
601 /* Got to end of frame/session with part of the data. */
602 if (ctx->channels_seen)
603 ctx->channels_seen = ctx->channel_count;
609 /* If we've got them all, dump the values. */
610 if (ctx->channels_seen >= ctx->channel_count)
611 dump_saved_values(ctx, out);
616 static int cleanup(struct sr_output *o)
625 g_free((gpointer)ctx->record);
626 g_free((gpointer)ctx->frame);
627 g_free((gpointer)ctx->comment);
628 g_free((gpointer)ctx->gnuplot);
629 g_free((gpointer)ctx->value);
630 g_free(ctx->previous_sample);
631 g_free(ctx->channels);
639 static struct sr_option options[] = {
640 {"gnuplot", "gnuplot", "gnuplot script file name", NULL, NULL},
641 {"scale", "scale", "Scale gnuplot graphs", NULL, NULL},
642 {"value", "Value separator", "Character to print between values", NULL, NULL},
643 {"record", "Record separator", "String to print between records", NULL, NULL},
644 {"frame", "Frame separator", "String to print between frames", NULL, NULL},
645 {"comment", "Comment start string", "String used at start of comment lines", NULL, NULL},
646 {"header", "Output header", "Output header comment with capture metdata", NULL, NULL},
647 {"label", "Label values", "Type of column labels", NULL, NULL},
648 {"time", "Time column", "Output sample time as column 1", NULL, NULL},
649 {"trigger", "Trigger column", "Output trigger indicator as last column ", NULL, NULL},
650 {"dedup", "Dedup rows", "Set to false to output duplicate rows", NULL, NULL},
654 static const struct sr_option *get_options(void)
658 if (!options[0].def) {
659 options[0].def = g_variant_ref_sink(g_variant_new_string(""));
660 options[1].def = g_variant_ref_sink(g_variant_new_boolean(TRUE));
661 options[2].def = g_variant_ref_sink(g_variant_new_string(","));
662 options[3].def = g_variant_ref_sink(g_variant_new_string("\n"));
663 options[4].def = g_variant_ref_sink(g_variant_new_string("\n"));
664 options[5].def = g_variant_ref_sink(g_variant_new_string(";"));
665 options[6].def = g_variant_ref_sink(g_variant_new_boolean(TRUE));
666 options[7].def = g_variant_ref_sink(g_variant_new_string("units"));
667 l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string("units")));
668 l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string("channel")));
669 l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string("off")));
670 options[7].values = l;
671 options[8].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
672 options[9].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
673 options[10].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
679 SR_PRIV struct sr_output_module output_csv = {
682 .desc = "Comma-separated values",
683 .exts = (const char *[]){"csv", NULL},
685 .options = get_options,