]> sigrok.org Git - libsigrok.git/blame - src/output/csv.c
output/csv: use intermediate time_t var, silence compiler warning
[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
5a273567 47 * was taken. Defaults to FALSE.
9e24c8bc
MM
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;
01ac3eed
EPI
96 uint64_t sample_rate;
97 uint64_t sample_scale;
4feb6ec9 98 uint64_t out_sample_count;
9e24c8bc
MM
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. */
ce384e07
GS
104
105 /* Input data constraints check. */
106 gboolean have_checked;
107 gboolean have_frames;
108 uint64_t pkt_snums;
02604ed6
UH
109};
110
111/*
112 * TODO:
02604ed6 113 * - Option to print comma-separated bits, or whole bytes/words (for 8/16
ba7dd8bb 114 * channel LAs) as ASCII/hex etc. etc.
02604ed6
UH
115 */
116
a755b0e1 117static int init(struct sr_output *o, GHashTable *options)
02604ed6 118{
9e24c8bc 119 unsigned int i, analog_channels, logic_channels;
02604ed6 120 struct context *ctx;
ba7dd8bb 121 struct sr_channel *ch;
cad447d2 122 const char *label_string;
02604ed6 123 GSList *l;
a755b0e1 124
0f3d8c89 125 if (!o || !o->sdi)
02604ed6 126 return SR_ERR_ARG;
02604ed6 127
2a035e53 128 ctx = g_malloc0(sizeof(struct context));
d686c5ec 129 o->priv = ctx;
02604ed6 130
9e24c8bc
MM
131 /* Options */
132 ctx->gnuplot = g_strdup(g_variant_get_string(
133 g_hash_table_lookup(options, "gnuplot"), NULL));
134 ctx->scale = g_variant_get_boolean(g_hash_table_lookup(options, "scale"));
135 ctx->value = g_strdup(g_variant_get_string(
136 g_hash_table_lookup(options, "value"), NULL));
137 ctx->record = g_strdup(g_variant_get_string(
138 g_hash_table_lookup(options, "record"), NULL));
139 ctx->frame = g_strdup(g_variant_get_string(
140 g_hash_table_lookup(options, "frame"), NULL));
141 ctx->comment = g_strdup(g_variant_get_string(
142 g_hash_table_lookup(options, "comment"), NULL));
143 ctx->header = g_variant_get_boolean(g_hash_table_lookup(options, "header"));
144 ctx->time = g_variant_get_boolean(g_hash_table_lookup(options, "time"));
145 ctx->do_trigger = g_variant_get_boolean(g_hash_table_lookup(options, "trigger"));
cad447d2
MM
146 label_string = g_variant_get_string(
147 g_hash_table_lookup(options, "label"), NULL);
9e24c8bc
MM
148 ctx->dedup = g_variant_get_boolean(g_hash_table_lookup(options, "dedup"));
149 ctx->dedup &= ctx->time;
150
151 if (*ctx->gnuplot && g_strcmp0(ctx->record, "\n"))
152 sr_warn("gnuplot record separator must be newline.");
153
154 if (*ctx->gnuplot && strlen(ctx->value) > 1)
155 sr_warn("gnuplot doesn't support multichar value separators.");
156
cad447d2
MM
157 if ((ctx->label_did = ctx->label_do = g_strcmp0(label_string, "off") != 0))
158 ctx->label_names = g_strcmp0(label_string, "units") != 0;
159
9e24c8bc
MM
160 sr_dbg("gnuplot = '%s', scale = %d", ctx->gnuplot, ctx->scale);
161 sr_dbg("value = '%s', record = '%s', frame = '%s', comment = '%s'",
162 ctx->value, ctx->record, ctx->frame, ctx->comment);
cad447d2
MM
163 sr_dbg("header = %d, time = %d, do_trigger = %d, dedup = %d",
164 ctx->header, ctx->time, ctx->do_trigger, ctx->dedup);
165 sr_dbg("label_do = %d, label_names = %d", ctx->label_do, ctx->label_names);
9e24c8bc
MM
166
167 analog_channels = logic_channels = 0;
ba7dd8bb
UH
168 /* Get the number of channels, and the unitsize. */
169 for (l = o->sdi->channels; l; l = l->next) {
170 ch = l->data;
9e24c8bc
MM
171 if (ch->type == SR_CHANNEL_LOGIC) {
172 ctx->logic_channel_count++;
173 if (ch->enabled)
174 logic_channels++;
c04cf9aa 175 }
9e24c8bc
MM
176 if (ch->type == SR_CHANNEL_ANALOG && ch->enabled)
177 analog_channels++;
178 }
179 if (analog_channels) {
180 sr_info("Outputting %d analog values", analog_channels);
181 ctx->num_analog_channels = analog_channels;
182 }
183 if (logic_channels) {
184 sr_info("Outputting %d logic values", logic_channels);
185 ctx->num_logic_channels = logic_channels;
02604ed6 186 }
9e24c8bc
MM
187 ctx->channels = g_malloc(sizeof(struct ctx_channel)
188 * (ctx->num_analog_channels + ctx->num_logic_channels));
02604ed6 189
0f3d8c89 190 /* Once more to map the enabled channels. */
9e24c8bc
MM
191 ctx->channel_count = g_slist_length(o->sdi->channels);
192 for (i = 0, l = o->sdi->channels; l; l = l->next) {
0f3d8c89 193 ch = l->data;
c04cf9aa 194 if (ch->enabled) {
9e24c8bc
MM
195 if (ch->type == SR_CHANNEL_ANALOG) {
196 ctx->channels[i].min = FLT_MAX;
197 ctx->channels[i].max = FLT_MIN;
198 } else if (ch->type == SR_CHANNEL_LOGIC) {
199 ctx->channels[i].min = 0;
200 ctx->channels[i].max = 1;
201 } else {
202 sr_warn("Unknown channel type %d.", ch->type);
203 }
cad447d2
MM
204 if (ctx->label_do && ctx->label_names)
205 ctx->channels[i].label = ch->name;
9e24c8bc 206 ctx->channels[i++].ch = ch;
c04cf9aa 207 }
0f3d8c89 208 }
02604ed6 209
0f3d8c89
BV
210 return SR_OK;
211}
02604ed6 212
9e24c8bc
MM
213static const char *xlabels[] = {
214 "samples", "milliseconds", "microseconds", "nanoseconds", "picoseconds",
215 "femtoseconds", "attoseconds",
216};
217
218static GString *gen_header(const struct sr_output *o,
219 const struct sr_datafeed_header *hdr)
0f3d8c89
BV
220{
221 struct context *ctx;
222 struct sr_channel *ch;
223 GVariant *gvar;
224 GString *header;
a21fef07 225 GSList *channels, *l;
9e24c8bc 226 unsigned int num_channels, i;
0f3d8c89 227 char *samplerate_s;
02604ed6 228
d686c5ec 229 ctx = o->priv;
0f3d8c89 230 header = g_string_sized_new(512);
02604ed6 231
01ac3eed 232 if (ctx->sample_rate == 0) {
9e24c8bc
MM
233 if (sr_config_get(o->sdi->driver, o->sdi, NULL,
234 SR_CONF_SAMPLERATE, &gvar) == SR_OK) {
01ac3eed 235 ctx->sample_rate = g_variant_get_uint64(gvar);
0f3d8c89
BV
236 g_variant_unref(gvar);
237 }
9e24c8bc
MM
238
239 i = 0;
01ac3eed
EPI
240 ctx->sample_scale = 1;
241 while (ctx->sample_scale < ctx->sample_rate) {
9e24c8bc 242 i++;
01ac3eed 243 ctx->sample_scale *= 1000;
9e24c8bc 244 }
9e24c8bc
MM
245 if (i < ARRAY_SIZE(xlabels))
246 ctx->xlabel = xlabels[i];
01ac3eed
EPI
247 sr_info("Set sample rate, scale to %" PRIu64 ", %" PRIu64 " %s",
248 ctx->sample_rate, ctx->sample_scale, ctx->xlabel);
0f3d8c89 249 }
cf1d5f17 250 ctx->title = (o->sdi && o->sdi->driver) ? o->sdi->driver->longname : "unknown";
9e24c8bc
MM
251
252 /* Some metadata */
253 if (ctx->header && !ctx->did_header) {
254 /* save_gnuplot knows how many lines we print. */
b503d24c
GS
255 time_t secs;
256 secs = hdr->starttime.tv_sec;
9e24c8bc
MM
257 g_string_append_printf(header,
258 "%s CSV generated by %s %s\n%s from %s on %s",
259 ctx->comment, PACKAGE_NAME,
2868bca3 260 sr_package_version_string_get(), ctx->comment,
b503d24c 261 ctx->title, ctime(&secs));
9e24c8bc
MM
262
263 /* Columns / channels */
a21fef07
GS
264 channels = o->sdi ? o->sdi->channels : NULL;
265 num_channels = g_slist_length(channels);
9e24c8bc
MM
266 g_string_append_printf(header, "%s Channels (%d/%d):",
267 ctx->comment, ctx->num_analog_channels +
268 ctx->num_logic_channels, num_channels);
a21fef07 269 for (l = channels; l; l = l->next) {
9e24c8bc
MM
270 ch = l->data;
271 if (ch->enabled)
272 g_string_append_printf(header, " %s,", ch->name);
273 }
a21fef07 274 if (channels) {
9e24c8bc
MM
275 /* Drop last separator. */
276 g_string_truncate(header, header->len - 1);
a21fef07 277 }
9e24c8bc 278 g_string_append_printf(header, "\n");
01ac3eed
EPI
279 if (ctx->sample_rate != 0) {
280 samplerate_s = sr_samplerate_string(ctx->sample_rate);
9e24c8bc
MM
281 g_string_append_printf(header, "%s Samplerate: %s\n",
282 ctx->comment, samplerate_s);
283 g_free(samplerate_s);
284 }
285 ctx->did_header = TRUE;
0f3d8c89 286 }
02604ed6 287
4feb6ec9
GS
288 /* Time column requested but samplerate unknown. Emit a warning. */
289 if (ctx->time && !ctx->sample_rate)
290 sr_warn("Samplerate unknown, cannot provide timestamps.");
291
0f3d8c89 292 return header;
02604ed6
UH
293}
294
9e24c8bc
MM
295/*
296 * Analog devices can have samples of different types. Since each
297 * packet has only one meaning, it is restricted to having at most one
298 * type of data. So they can send multiple packets for a single sample.
299 * To further complicate things, they can send multiple samples in a
300 * single packet.
301 *
302 * So we need to pull any channels of interest out of a packet and save
303 * them until we have complete samples to output. Some devices make this
304 * simple by sending DF_FRAME_BEGIN/DF_FRAME_END packets, the latter of which
305 * signals the end of a set of samples, so we can dump things there.
306 *
307 * At least one driver (the demo driver) sends packets that contain parts of
308 * multiple samples without wrapping them in DF_FRAME. Possibly this driver
309 * is buggy, but it's also the standard for testing, so it has to be supported
310 * as is.
311 *
312 * Many assumptions about the "shape" of the data here:
313 *
314 * All of the data for a channel is assumed to be in one frame;
315 * otherwise the data in the second packet will overwrite the data in
316 * the first packet.
317 */
318static void process_analog(struct context *ctx,
319 const struct sr_datafeed_analog *analog)
320{
321 int ret;
a551cb09
GS
322 size_t num_rcvd_ch, num_have_ch;
323 size_t idx_have, idx_smpl, idx_rcvd;
823b0e29 324 size_t idx_send;
9e24c8bc
MM
325 struct sr_analog_meaning *meaning;
326 GSList *l;
327 float *fdata = NULL;
b078dddb 328 struct sr_channel *ch;
9e24c8bc
MM
329
330 if (!ctx->analog_samples) {
331 ctx->analog_samples = g_malloc(analog->num_samples
332 * sizeof(float) * ctx->num_analog_channels);
333 if (!ctx->num_samples)
334 ctx->num_samples = analog->num_samples;
335 }
336 if (ctx->num_samples != analog->num_samples)
337 sr_warn("Expecting %u analog samples, got %u.",
338 ctx->num_samples, analog->num_samples);
339
340 meaning = analog->meaning;
a551cb09
GS
341 num_rcvd_ch = g_slist_length(meaning->channels);
342 ctx->channels_seen += num_rcvd_ch;
343 sr_dbg("Processing packet of %zu analog channels", num_rcvd_ch);
344 fdata = g_malloc(analog->num_samples * num_rcvd_ch * sizeof(float));
9e24c8bc
MM
345 if ((ret = sr_analog_to_float(analog, fdata)) != SR_OK)
346 sr_warn("Problems converting data to floating point values.");
347
a551cb09 348 num_have_ch = ctx->num_analog_channels + ctx->num_logic_channels;
823b0e29 349 idx_send = 0;
a551cb09
GS
350 for (idx_have = 0; idx_have < num_have_ch; idx_have++) {
351 if (ctx->channels[idx_have].ch->type != SR_CHANNEL_ANALOG)
b078dddb
GS
352 continue;
353 sr_dbg("Looking for channel %s",
a551cb09
GS
354 ctx->channels[idx_have].ch->name);
355 for (l = meaning->channels, idx_rcvd = 0; l; l = l->next, idx_rcvd++) {
b078dddb
GS
356 ch = l->data;
357 sr_dbg("Checking %s", ch->name);
a551cb09 358 if (ctx->channels[idx_have].ch != ch)
b078dddb
GS
359 continue;
360 if (ctx->label_do && !ctx->label_names) {
361 sr_analog_unit_to_string(analog,
a551cb09 362 &ctx->channels[idx_have].label);
9e24c8bc 363 }
a551cb09 364 for (idx_smpl = 0; idx_smpl < analog->num_samples; idx_smpl++)
823b0e29 365 ctx->analog_samples[idx_smpl * ctx->num_analog_channels + idx_send] = fdata[idx_smpl * num_rcvd_ch + idx_rcvd];
b078dddb 366 break;
9e24c8bc 367 }
823b0e29 368 idx_send++;
9e24c8bc
MM
369 }
370 g_free(fdata);
371}
372
373/*
374 * We treat logic packets the same as analog packets, though it's not
375 * strictly required. This allows us to process mixed signals properly.
376 */
377static void process_logic(struct context *ctx,
378 const struct sr_datafeed_logic *logic)
c04cf9aa 379{
9e24c8bc
MM
380 unsigned int i, j, ch, num_samples;
381 int idx;
382 uint8_t *sample;
383
384 num_samples = logic->length / logic->unitsize;
385 ctx->channels_seen += ctx->logic_channel_count;
386 sr_dbg("Logic packet had %d channels", logic->unitsize * 8);
387 if (!ctx->logic_samples) {
388 ctx->logic_samples = g_malloc(num_samples * ctx->num_logic_channels);
389 if (!ctx->num_samples)
390 ctx->num_samples = num_samples;
391 }
392 if (ctx->num_samples != num_samples)
393 sr_warn("Expecting %u samples, got %u",
394 ctx->num_samples, num_samples);
395
396 for (j = ch = 0; ch < ctx->num_logic_channels; j++) {
397 if (ctx->channels[j].ch->type == SR_CHANNEL_LOGIC) {
d3cc09a6
WS
398 for (i = 0; i < num_samples; i++) {
399 sample = logic->data + i * logic->unitsize;
10892c5b 400 idx = ctx->channels[j].ch->index;
cad447d2 401 if (ctx->label_do && !ctx->label_names)
6a235225 402 ctx->channels[j].label = "logic";
9e24c8bc
MM
403 ctx->logic_samples[i * ctx->num_logic_channels + ch] = sample[idx / 8] & (1 << (idx % 8));
404 }
405 ch++;
406 }
407 }
408}
409
410static void dump_saved_values(struct context *ctx, GString **out)
411{
412 unsigned int i, j, analog_size, num_channels;
4feb6ec9
GS
413 double sample_time_dbl;
414 uint64_t sample_time_u64;
9e24c8bc
MM
415 float *analog_sample, value;
416 uint8_t *logic_sample;
417
418 /* If we haven't seen samples we're expecting, skip them. */
419 if ((ctx->num_analog_channels && !ctx->analog_samples) ||
420 (ctx->num_logic_channels && !ctx->logic_samples)) {
421 sr_warn("Discarding partial packet");
c04cf9aa 422 } else {
9e24c8bc
MM
423 sr_info("Dumping %u samples", ctx->num_samples);
424
98b7b089
GS
425 if (!*out)
426 *out = g_string_sized_new(512);
9e24c8bc
MM
427 num_channels =
428 ctx->num_logic_channels + ctx->num_analog_channels;
429
cad447d2 430 if (ctx->label_do) {
9e24c8bc 431 if (ctx->time)
cad447d2 432 g_string_append_printf(*out, "%s%s",
4feb6ec9
GS
433 ctx->label_names ? "Time" : ctx->xlabel,
434 ctx->value);
9e24c8bc
MM
435 for (i = 0; i < num_channels; i++) {
436 g_string_append_printf(*out, "%s%s",
cad447d2
MM
437 ctx->channels[i].label, ctx->value);
438 if (ctx->channels[i].ch->type == SR_CHANNEL_ANALOG
439 && ctx->label_names)
440 g_free(ctx->channels[i].label);
9e24c8bc
MM
441 }
442 if (ctx->do_trigger)
443 g_string_append_printf(*out, "Trigger%s",
444 ctx->value);
445 /* Drop last separator. */
446 g_string_truncate(*out, (*out)->len - 1);
447 g_string_append(*out, ctx->record);
448
cad447d2 449 ctx->label_do = FALSE;
9e24c8bc
MM
450 }
451
452 analog_size = ctx->num_analog_channels * sizeof(float);
453 if (ctx->dedup && !ctx->previous_sample)
454 ctx->previous_sample = g_malloc0(analog_size + ctx->num_logic_channels);
455
456 for (i = 0; i < ctx->num_samples; i++) {
9e24c8bc
MM
457 analog_sample =
458 &ctx->analog_samples[i * ctx->num_analog_channels];
459 logic_sample =
460 &ctx->logic_samples[i * ctx->num_logic_channels];
461
462 if (ctx->dedup) {
463 if (i > 0 && i < ctx->num_samples - 1 &&
464 !memcmp(logic_sample, ctx->previous_sample,
465 ctx->num_logic_channels) &&
466 !memcmp(analog_sample,
467 ctx->previous_sample +
468 ctx->num_logic_channels,
469 analog_size))
470 continue;
471 memcpy(ctx->previous_sample, logic_sample,
472 ctx->num_logic_channels);
473 memcpy(ctx->previous_sample
474 + ctx->num_logic_channels,
475 analog_sample, analog_size);
476 }
477
4feb6ec9
GS
478 if (ctx->time && !ctx->sample_rate) {
479 g_string_append_printf(*out, "0%s", ctx->value);
480 } else if (ctx->time) {
481 sample_time_dbl = ctx->out_sample_count++;
482 sample_time_dbl /= ctx->sample_rate;
483 sample_time_dbl *= ctx->sample_scale;
484 sample_time_u64 = sample_time_dbl;
8005151b 485 g_string_append_printf(*out, "%" PRIu64 "%s",
4feb6ec9
GS
486 sample_time_u64, ctx->value);
487 }
9e24c8bc
MM
488
489 for (j = 0; j < num_channels; j++) {
490 if (ctx->channels[j].ch->type == SR_CHANNEL_ANALOG) {
491 value = ctx->analog_samples[i * ctx->num_analog_channels + j];
492 ctx->channels[j].max =
493 fmax(value, ctx->channels[j].max);
494 ctx->channels[j].min =
495 fmin(value, ctx->channels[j].min);
496 g_string_append_printf(*out, "%g%s",
497 value, ctx->value);
498 } else if (ctx->channels[j].ch->type == SR_CHANNEL_LOGIC) {
499 g_string_append_printf(*out, "%c%s",
500 ctx->logic_samples[i * ctx->num_logic_channels + j] ? '1' : '0', ctx->value);
501 } else {
502 sr_warn("Unexpected channel type: %d",
503 ctx->channels[i].ch->type);
504 }
505 }
506
507 if (ctx->do_trigger) {
508 g_string_append_printf(*out, "%d%s",
509 ctx->trigger, ctx->value);
510 ctx->trigger = FALSE;
511 }
512 g_string_truncate(*out, (*out)->len - 1);
513 g_string_append(*out, ctx->record);
514 }
c04cf9aa 515 }
9e24c8bc
MM
516
517 /* Discard all of the working space. */
518 g_free(ctx->previous_sample);
519 g_free(ctx->analog_samples);
520 g_free(ctx->logic_samples);
521 ctx->channels_seen = 0;
522 ctx->num_samples = 0;
523 ctx->previous_sample = NULL;
524 ctx->analog_samples = NULL;
525 ctx->logic_samples = NULL;
c04cf9aa
BG
526}
527
9e24c8bc 528static void save_gnuplot(struct context *ctx)
c04cf9aa 529{
9e24c8bc
MM
530 float offset, max, sum;
531 unsigned int i, num_channels;
532 GString *script;
533
534 script = g_string_sized_new(512);
535 g_string_append_printf(script, "set datafile separator '%s'\n",
536 ctx->value);
cad447d2 537 if (ctx->label_did)
9e24c8bc
MM
538 g_string_append(script, "set key autotitle columnhead\n");
539 if (ctx->xlabel && ctx->time)
540 g_string_append_printf(script, "set xlabel '%s'\n",
541 ctx->xlabel);
542
543 g_string_append(script, "plot ");
544
545 num_channels = ctx->num_analog_channels + ctx->num_logic_channels;
546
547 /* Graph position and scaling. */
548 max = FLT_MIN;
549 sum = 0;
550 for (i = 0; i < num_channels; i++) {
551 ctx->channels[i].max =
552 ctx->channels[i].max - ctx->channels[i].min;
553 max = fmax(max, ctx->channels[i].max);
554 sum += ctx->channels[i].max;
555 }
556 sum = (ctx->scale ? max : sum / num_channels) / 4;
557 offset = sum;
558 for (i = num_channels; i > 0;) {
559 i--;
560 ctx->channels[i].min = offset - ctx->channels[i].min;
561 offset += sum + (ctx->scale ? max : ctx->channels[i].max);
562 }
c04cf9aa 563
9e24c8bc
MM
564 for (i = 0; i < num_channels; i++) {
565 sr_spew("Channel %d, min %g, max %g", i, ctx->channels[i].min,
566 ctx->channels[i].max);
567 g_string_append(script, "ARG1 ");
568 if (ctx->did_header)
569 g_string_append(script, "skip 4 ");
570 g_string_append_printf(script, "using %u:($%u * %g + %g), ",
571 ctx->time, i + 1 + ctx->time, ctx->scale ?
572 max / ctx->channels[i].max : 1, ctx->channels[i].min);
573 offset += 1.1 * (ctx->channels[i].max - ctx->channels[i].min);
c04cf9aa 574 }
9e24c8bc
MM
575 g_string_truncate(script, script->len - 2);
576 g_file_set_contents(ctx->gnuplot, script->str, script->len, NULL);
577 g_string_free(script, TRUE);
c04cf9aa
BG
578}
579
ce384e07
GS
580static void check_input_constraints(struct context *ctx)
581{
582 size_t snum_count, snum_warn_limit;
583 size_t logic, analog;
584 gboolean has_frames, is_short, is_mixed, is_multi_analog;
585 gboolean do_warn;
586
587 /*
588 * Check and conditionally warn exactly once during execution
589 * of the output module on a set of input data.
590 */
591 if (ctx->have_checked)
592 return;
593 ctx->have_checked = TRUE;
594
595 /*
596 * This implementation of the CSV output module assumes some
597 * constraints which need not be met in reality. Emit warnings
598 * until a better version becomes available. Letting users know
599 * that their request may not get processed correctly is the
600 * only thing we can do for now except for complete refusal to
601 * process the input data.
602 *
603 * What the implementation appears to assume (unverified, this
604 * interpretation may be incorrect and/or incomplete):
605 * - Multi-channel analog data, or mixed signal input, always
606 * is enclosed in frame markers.
607 * - Data which gets received across several packets spans a
608 * consistent sample number range. All samples of one frame
609 * and channel number or data type fit into a single packet.
610 * Arbitrary chunking seems to not be supported.
611 * - A specific order of analog data packets is assumed.
612 *
613 * With these assumptions encoded in the implementation, and
614 * not being met at runtime, incorrect and unexpected results
615 * were seen for these configurations:
616 * - More than one analog channel.
617 * - The combination of logic and analog channel types.
618 *
619 * The condition of frames with large sample counts is a wild
620 * guess, the limit is a totally arbitrary choice. It assumes
621 * typical scope frames with at most a few thousand samples per
622 * frame, and assumes that a channel's data gets sent in large
623 * enough packets. The absence of a warning message does not
624 * necessarily translate to correct output, it's more of a rate
625 * limiting approach to not scare users too much.
626 */
627 snum_count = ctx->pkt_snums;
628 snum_warn_limit = 1 * 1000 * 1000;
629 logic = ctx->num_logic_channels;
630 analog = ctx->num_analog_channels;
631 has_frames = ctx->have_frames;
632 is_short = snum_count < snum_warn_limit;
633 is_mixed = logic && analog;
634 is_multi_analog = analog > 1;
635
636 if (has_frames && is_short) {
637 sr_info("Assuming consistent framed input data.");
638 return;
639 }
640
641 do_warn = FALSE;
642 if (has_frames) {
643 sr_warn("Untested configuration: large frame content.");
644 do_warn = TRUE;
645 }
646 if (is_mixed) {
647 sr_warn("Untested configuration: mixed signal input data.");
648 do_warn = TRUE;
649 }
650 if (is_multi_analog) {
651 sr_warn("Untested configuration: multi-channel analog data.");
652 do_warn = TRUE;
653 }
654 if (!do_warn)
655 return;
656 sr_warn("Resulting CSV output data may be incomplete or incorrect.");
657}
658
9e24c8bc
MM
659static int receive(const struct sr_output *o,
660 const struct sr_datafeed_packet *packet, GString **out)
02604ed6
UH
661{
662 struct context *ctx;
ce384e07
GS
663 const struct sr_datafeed_logic *logic;
664 const struct sr_datafeed_analog *analog;
02604ed6 665
4829d37d
BV
666 *out = NULL;
667 if (!o || !o->sdi)
02604ed6 668 return SR_ERR_ARG;
d686c5ec 669 if (!(ctx = o->priv))
02604ed6 670 return SR_ERR_ARG;
02604ed6 671
9e24c8bc 672 sr_dbg("Got packet of type %d", packet->type);
4829d37d 673 switch (packet->type) {
9e24c8bc 674 case SR_DF_HEADER:
ce384e07
GS
675 ctx->have_checked = FALSE;
676 ctx->have_frames = FALSE;
677 ctx->pkt_snums = FALSE;
9e24c8bc 678 *out = gen_header(o, packet->payload);
0f3d8c89 679 break;
9e24c8bc
MM
680 case SR_DF_TRIGGER:
681 ctx->trigger = TRUE;
c04cf9aa 682 break;
4829d37d 683 case SR_DF_LOGIC:
98b7b089 684 *out = g_string_sized_new(512);
ce384e07
GS
685 logic = packet->payload;
686 ctx->pkt_snums = logic->length;
687 ctx->pkt_snums /= logic->length;
688 check_input_constraints(ctx);
689 process_logic(ctx, logic);
02604ed6 690 break;
edb691fc 691 case SR_DF_ANALOG:
98b7b089 692 *out = g_string_sized_new(512);
ce384e07
GS
693 analog = packet->payload;
694 ctx->pkt_snums = analog->num_samples;
695 ctx->pkt_snums /= g_slist_length(analog->meaning->channels);
696 check_input_constraints(ctx);
697 process_analog(ctx, analog);
9e24c8bc
MM
698 break;
699 case SR_DF_FRAME_BEGIN:
ce384e07 700 ctx->have_frames = TRUE;
9e24c8bc 701 *out = g_string_new(ctx->frame);
317c97be 702 /* Fallthrough */
9e24c8bc
MM
703 case SR_DF_END:
704 /* Got to end of frame/session with part of the data. */
705 if (ctx->channels_seen)
706 ctx->channels_seen = ctx->channel_count;
707 if (*ctx->gnuplot)
708 save_gnuplot(ctx);
c04cf9aa 709 break;
02604ed6
UH
710 }
711
9e24c8bc
MM
712 /* If we've got them all, dump the values. */
713 if (ctx->channels_seen >= ctx->channel_count)
714 dump_saved_values(ctx, out);
715
716 return SR_OK;
02604ed6
UH
717}
718
4829d37d 719static int cleanup(struct sr_output *o)
02604ed6
UH
720{
721 struct context *ctx;
02604ed6 722
4829d37d 723 if (!o || !o->sdi)
02604ed6 724 return SR_ERR_ARG;
02604ed6 725
d686c5ec
BV
726 if (o->priv) {
727 ctx = o->priv;
9e24c8bc
MM
728 g_free((gpointer)ctx->record);
729 g_free((gpointer)ctx->frame);
730 g_free((gpointer)ctx->comment);
731 g_free((gpointer)ctx->gnuplot);
7e7d7bc0 732 g_free((gpointer)ctx->value);
9e24c8bc 733 g_free(ctx->previous_sample);
c04cf9aa 734 g_free(ctx->channels);
d686c5ec
BV
735 g_free(o->priv);
736 o->priv = NULL;
02604ed6
UH
737 }
738
02604ed6
UH
739 return SR_OK;
740}
741
9e24c8bc
MM
742static struct sr_option options[] = {
743 {"gnuplot", "gnuplot", "gnuplot script file name", NULL, NULL},
744 {"scale", "scale", "Scale gnuplot graphs", NULL, NULL},
745 {"value", "Value separator", "Character to print between values", NULL, NULL},
746 {"record", "Record separator", "String to print between records", NULL, NULL},
51d64bf5 747 {"frame", "Frame separator", "String to print between frames", NULL, NULL},
9e24c8bc
MM
748 {"comment", "Comment start string", "String used at start of comment lines", NULL, NULL},
749 {"header", "Output header", "Output header comment with capture metdata", NULL, NULL},
cad447d2 750 {"label", "Label values", "Type of column labels", NULL, NULL},
9e24c8bc
MM
751 {"time", "Time column", "Output sample time as column 1", NULL, NULL},
752 {"trigger", "Trigger column", "Output trigger indicator as last column ", NULL, NULL},
753 {"dedup", "Dedup rows", "Set to false to output duplicate rows", NULL, NULL},
754 ALL_ZERO
755};
756
757static const struct sr_option *get_options(void)
758{
7c59d8f3
UH
759 GSList *l = NULL;
760
9e24c8bc
MM
761 if (!options[0].def) {
762 options[0].def = g_variant_ref_sink(g_variant_new_string(""));
763 options[1].def = g_variant_ref_sink(g_variant_new_boolean(TRUE));
764 options[2].def = g_variant_ref_sink(g_variant_new_string(","));
765 options[3].def = g_variant_ref_sink(g_variant_new_string("\n"));
766 options[4].def = g_variant_ref_sink(g_variant_new_string("\n"));
767 options[5].def = g_variant_ref_sink(g_variant_new_string(";"));
768 options[6].def = g_variant_ref_sink(g_variant_new_boolean(TRUE));
cad447d2 769 options[7].def = g_variant_ref_sink(g_variant_new_string("units"));
7c59d8f3
UH
770 l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string("units")));
771 l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string("channel")));
772 l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string("off")));
773 options[7].values = l;
5a273567 774 options[8].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
9e24c8bc 775 options[9].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
04a0e0dc 776 options[10].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
9e24c8bc
MM
777 }
778
779 return options;
780}
781
a755b0e1 782SR_PRIV struct sr_output_module output_csv = {
02604ed6 783 .id = "csv",
a755b0e1
BV
784 .name = "CSV",
785 .desc = "Comma-separated values",
9e24c8bc 786 .exts = (const char *[]){"csv", NULL},
3cd4b381 787 .flags = 0,
9e24c8bc 788 .options = get_options,
02604ed6 789 .init = init,
4829d37d
BV
790 .receive = receive,
791 .cleanup = cleanup,
02604ed6 792};