X-Git-Url: https://sigrok.org/gitweb/?a=blobdiff_plain;f=src%2Foutput%2Fcsv.c;h=c4801ef7f984bca96b432fda2eb37c55fd9da730;hb=HEAD;hp=e14984ef785379591b32b16f150e1e9dc3533d55;hpb=e34dbc9d0834d9dac310bc2dce844567adfffd08;p=libsigrok.git diff --git a/src/output/csv.c b/src/output/csv.c index e14984ef..c4801ef7 100644 --- a/src/output/csv.c +++ b/src/output/csv.c @@ -14,8 +14,7 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * along with this program; if not, see . */ /* @@ -41,21 +40,21 @@ * * header: Print header comment with capture metadata. Defaults to TRUE. * - * label: Add a line of channel labels as the first line of output. Defaults - * to TRUE. + * label: What to use for channel labels as the first line of output. + * Values are "channel", "units", "off". Defaults to "units". * * time: Whether or not the first column should include the time the sample - * was taken. Defaults to TRUE. + * was taken. Defaults to FALSE. * * trigger: Whether or not to add a "trigger" column as the last column. * Defaults to FALSE. * - * dedup: Don't output duplicate rows. Defaults to TRUE. If time is off, then + * dedup: Don't output duplicate rows. Defaults to FALSE. If time is off, then * this is forced to be off. */ -#include #include +#include #include #include #include @@ -66,6 +65,7 @@ struct ctx_channel { struct sr_channel *ch; + char *label; float min, max; }; @@ -78,7 +78,7 @@ struct context { const char *frame; const char *comment; gboolean header, did_header; - gboolean label, did_label; + gboolean label_do, label_did, label_names; gboolean time; gboolean do_trigger; gboolean dedup; @@ -93,13 +93,19 @@ struct context { uint32_t num_samples; uint32_t channel_count, logic_channel_count; uint32_t channels_seen; - uint64_t period; - uint64_t sample_time; + uint64_t sample_rate; + uint64_t sample_scale; + uint64_t out_sample_count; uint8_t *previous_sample; float *analog_samples; uint8_t *logic_samples; const char *xlabel; /* Don't free: will point to a static string. */ const char *title; /* Don't free: will point into the driver struct. */ + + /* Input data constraints check. */ + gboolean have_checked; + gboolean have_frames; + uint64_t pkt_snums; }; /* @@ -113,6 +119,7 @@ static int init(struct sr_output *o, GHashTable *options) unsigned int i, analog_channels, logic_channels; struct context *ctx; struct sr_channel *ch; + const char *label_string; GSList *l; if (!o || !o->sdi) @@ -136,7 +143,8 @@ static int init(struct sr_output *o, GHashTable *options) ctx->header = g_variant_get_boolean(g_hash_table_lookup(options, "header")); ctx->time = g_variant_get_boolean(g_hash_table_lookup(options, "time")); ctx->do_trigger = g_variant_get_boolean(g_hash_table_lookup(options, "trigger")); - ctx->label = g_variant_get_boolean(g_hash_table_lookup(options, "label")); + label_string = g_variant_get_string( + g_hash_table_lookup(options, "label"), NULL); ctx->dedup = g_variant_get_boolean(g_hash_table_lookup(options, "dedup")); ctx->dedup &= ctx->time; @@ -146,11 +154,15 @@ static int init(struct sr_output *o, GHashTable *options) if (*ctx->gnuplot && strlen(ctx->value) > 1) sr_warn("gnuplot doesn't support multichar value separators."); + if ((ctx->label_did = ctx->label_do = g_strcmp0(label_string, "off") != 0)) + ctx->label_names = g_strcmp0(label_string, "units") != 0; + sr_dbg("gnuplot = '%s', scale = %d", ctx->gnuplot, ctx->scale); sr_dbg("value = '%s', record = '%s', frame = '%s', comment = '%s'", ctx->value, ctx->record, ctx->frame, ctx->comment); - sr_dbg("header = %d, label = %d, time = %d, do_trigger = %d, dedup = %d", - ctx->header, ctx->label, ctx->time, ctx->do_trigger, ctx->dedup); + sr_dbg("header = %d, time = %d, do_trigger = %d, dedup = %d", + ctx->header, ctx->time, ctx->do_trigger, ctx->dedup); + sr_dbg("label_do = %d, label_names = %d", ctx->label_do, ctx->label_names); analog_channels = logic_channels = 0; /* Get the number of channels, and the unitsize. */ @@ -189,6 +201,8 @@ static int init(struct sr_output *o, GHashTable *options) } else { sr_warn("Unknown channel type %d.", ch->type); } + if (ctx->label_do && ctx->label_names) + ctx->channels[i].label = ch->name; ctx->channels[i++].ch = ch; } } @@ -208,61 +222,62 @@ static GString *gen_header(const struct sr_output *o, struct sr_channel *ch; GVariant *gvar; GString *header; - GSList *l; + GSList *channels, *l; unsigned int num_channels, i; - uint64_t samplerate = 0, sr; char *samplerate_s; ctx = o->priv; header = g_string_sized_new(512); - if (ctx->period == 0) { + if (ctx->sample_rate == 0) { if (sr_config_get(o->sdi->driver, o->sdi, NULL, SR_CONF_SAMPLERATE, &gvar) == SR_OK) { - samplerate = g_variant_get_uint64(gvar); + ctx->sample_rate = g_variant_get_uint64(gvar); g_variant_unref(gvar); } i = 0; - sr = 1; - while (sr < samplerate) { + ctx->sample_scale = 1; + while (ctx->sample_scale < ctx->sample_rate) { i++; - sr *= 1000; + ctx->sample_scale *= 1000; } - if (samplerate) - ctx->period = sr / samplerate; if (i < ARRAY_SIZE(xlabels)) ctx->xlabel = xlabels[i]; - sr_info("Set sample period to %" PRIu64 " %s", - ctx->period, ctx->xlabel); + sr_info("Set sample rate, scale to %" PRIu64 ", %" PRIu64 " %s", + ctx->sample_rate, ctx->sample_scale, ctx->xlabel); } ctx->title = (o->sdi && o->sdi->driver) ? o->sdi->driver->longname : "unknown"; /* Some metadata */ if (ctx->header && !ctx->did_header) { /* save_gnuplot knows how many lines we print. */ + time_t secs; + secs = hdr->starttime.tv_sec; g_string_append_printf(header, "%s CSV generated by %s %s\n%s from %s on %s", ctx->comment, PACKAGE_NAME, - SR_PACKAGE_VERSION_STRING, ctx->comment, - ctx->title, ctime(&hdr->starttime.tv_sec)); + sr_package_version_string_get(), ctx->comment, + ctx->title, ctime(&secs)); /* Columns / channels */ - num_channels = g_slist_length(o->sdi->channels); + channels = o->sdi ? o->sdi->channels : NULL; + num_channels = g_slist_length(channels); g_string_append_printf(header, "%s Channels (%d/%d):", ctx->comment, ctx->num_analog_channels + ctx->num_logic_channels, num_channels); - for (i = 0, l = o->sdi->channels; l; l = l->next, i++) { + for (l = channels; l; l = l->next) { ch = l->data; if (ch->enabled) g_string_append_printf(header, " %s,", ch->name); } - if (o->sdi->channels) + if (channels) { /* Drop last separator. */ g_string_truncate(header, header->len - 1); + } g_string_append_printf(header, "\n"); - if (samplerate != 0) { - samplerate_s = sr_samplerate_string(samplerate); + if (ctx->sample_rate != 0) { + samplerate_s = sr_samplerate_string(ctx->sample_rate); g_string_append_printf(header, "%s Samplerate: %s\n", ctx->comment, samplerate_s); g_free(samplerate_s); @@ -270,6 +285,10 @@ static GString *gen_header(const struct sr_output *o, ctx->did_header = TRUE; } + /* Time column requested but samplerate unknown. Emit a warning. */ + if (ctx->time && !ctx->sample_rate) + sr_warn("Samplerate unknown, cannot provide timestamps."); + return header; } @@ -300,10 +319,13 @@ static void process_analog(struct context *ctx, const struct sr_datafeed_analog *analog) { int ret; - unsigned int i, j, c, num_channels; + size_t num_rcvd_ch, num_have_ch; + size_t idx_have, idx_smpl, idx_rcvd; + size_t idx_send; struct sr_analog_meaning *meaning; GSList *l; float *fdata = NULL; + struct sr_channel *ch; if (!ctx->analog_samples) { ctx->analog_samples = g_malloc(analog->num_samples @@ -316,27 +338,34 @@ static void process_analog(struct context *ctx, ctx->num_samples, analog->num_samples); meaning = analog->meaning; - num_channels = g_slist_length(meaning->channels); - ctx->channels_seen += num_channels; - sr_dbg("Processing packet of %u analog channels", num_channels); - fdata = g_malloc(analog->num_samples * num_channels * sizeof(float)); + num_rcvd_ch = g_slist_length(meaning->channels); + ctx->channels_seen += num_rcvd_ch; + sr_dbg("Processing packet of %zu analog channels", num_rcvd_ch); + fdata = g_malloc(analog->num_samples * num_rcvd_ch * sizeof(float)); if ((ret = sr_analog_to_float(analog, fdata)) != SR_OK) sr_warn("Problems converting data to floating point values."); - for (i = 0; i < ctx->num_analog_channels + ctx->num_logic_channels; i++) { - if (ctx->channels[i].ch->type == SR_CHANNEL_ANALOG) { - sr_dbg("Looking for channel %s", - ctx->channels[i].ch->name); - for (l = meaning->channels, c = 0; l; l = l->next, c++) { - struct sr_channel *ch = l->data; - sr_dbg("Checking %s", ch->name); - if (ctx->channels[i].ch == l->data) { - for (j = 0; j < analog->num_samples; j++) - ctx->analog_samples[j * ctx->num_analog_channels + i] = fdata[j * num_channels + c]; - break; - } + num_have_ch = ctx->num_analog_channels + ctx->num_logic_channels; + idx_send = 0; + for (idx_have = 0; idx_have < num_have_ch; idx_have++) { + if (ctx->channels[idx_have].ch->type != SR_CHANNEL_ANALOG) + continue; + sr_dbg("Looking for channel %s", + ctx->channels[idx_have].ch->name); + for (l = meaning->channels, idx_rcvd = 0; l; l = l->next, idx_rcvd++) { + ch = l->data; + sr_dbg("Checking %s", ch->name); + if (ctx->channels[idx_have].ch != ch) + continue; + if (ctx->label_do && !ctx->label_names) { + sr_analog_unit_to_string(analog, + &ctx->channels[idx_have].label); } + for (idx_smpl = 0; idx_smpl < analog->num_samples; idx_smpl++) + ctx->analog_samples[idx_smpl * ctx->num_analog_channels + idx_send] = fdata[idx_smpl * num_rcvd_ch + idx_rcvd]; + break; } + idx_send++; } g_free(fdata); } @@ -366,9 +395,11 @@ static void process_logic(struct context *ctx, for (j = ch = 0; ch < ctx->num_logic_channels; j++) { if (ctx->channels[j].ch->type == SR_CHANNEL_LOGIC) { - for (i = 0; i <= logic->length - logic->unitsize; i += logic->unitsize) { - sample = logic->data + i; - idx = ctx->channels[ch].ch->index; + for (i = 0; i < num_samples; i++) { + sample = logic->data + i * logic->unitsize; + idx = ctx->channels[j].ch->index; + if (ctx->label_do && !ctx->label_names) + ctx->channels[j].label = "logic"; ctx->logic_samples[i * ctx->num_logic_channels + ch] = sample[idx / 8] & (1 << (idx % 8)); } ch++; @@ -379,6 +410,8 @@ static void process_logic(struct context *ctx, static void dump_saved_values(struct context *ctx, GString **out) { unsigned int i, j, analog_size, num_channels; + double sample_time_dbl; + uint64_t sample_time_u64; float *analog_sample, value; uint8_t *logic_sample; @@ -389,17 +422,22 @@ static void dump_saved_values(struct context *ctx, GString **out) } else { sr_info("Dumping %u samples", ctx->num_samples); - *out = g_string_sized_new(512); + if (!*out) + *out = g_string_sized_new(512); num_channels = ctx->num_logic_channels + ctx->num_analog_channels; - if (ctx->label && !ctx->did_label) { + if (ctx->label_do) { if (ctx->time) - g_string_append_printf(*out, "Time%s", - ctx->value); + g_string_append_printf(*out, "%s%s", + ctx->label_names ? "Time" : ctx->xlabel, + ctx->value); for (i = 0; i < num_channels; i++) { g_string_append_printf(*out, "%s%s", - ctx->channels[i].ch->name, ctx->value); + ctx->channels[i].label, ctx->value); + if (ctx->channels[i].ch->type == SR_CHANNEL_ANALOG + && ctx->label_names) + g_free(ctx->channels[i].label); } if (ctx->do_trigger) g_string_append_printf(*out, "Trigger%s", @@ -408,7 +446,7 @@ static void dump_saved_values(struct context *ctx, GString **out) g_string_truncate(*out, (*out)->len - 1); g_string_append(*out, ctx->record); - ctx->did_label = TRUE; + ctx->label_do = FALSE; } analog_size = ctx->num_analog_channels * sizeof(float); @@ -416,7 +454,6 @@ static void dump_saved_values(struct context *ctx, GString **out) ctx->previous_sample = g_malloc0(analog_size + ctx->num_logic_channels); for (i = 0; i < ctx->num_samples; i++) { - ctx->sample_time += ctx->period; analog_sample = &ctx->analog_samples[i * ctx->num_analog_channels]; logic_sample = @@ -438,9 +475,16 @@ static void dump_saved_values(struct context *ctx, GString **out) analog_sample, analog_size); } - if (ctx->time) - g_string_append_printf(*out, "%lu%s", - ctx->sample_time, ctx->value); + if (ctx->time && !ctx->sample_rate) { + g_string_append_printf(*out, "0%s", ctx->value); + } else if (ctx->time) { + sample_time_dbl = ctx->out_sample_count++; + sample_time_dbl /= ctx->sample_rate; + sample_time_dbl *= ctx->sample_scale; + sample_time_u64 = sample_time_dbl; + g_string_append_printf(*out, "%" PRIu64 "%s", + sample_time_u64, ctx->value); + } for (j = 0; j < num_channels; j++) { if (ctx->channels[j].ch->type == SR_CHANNEL_ANALOG) { @@ -490,7 +534,7 @@ static void save_gnuplot(struct context *ctx) script = g_string_sized_new(512); g_string_append_printf(script, "set datafile separator '%s'\n", ctx->value); - if (ctx->did_label) + if (ctx->label_did) g_string_append(script, "set key autotitle columnhead\n"); if (ctx->xlabel && ctx->time) g_string_append_printf(script, "set xlabel '%s'\n", @@ -533,10 +577,91 @@ static void save_gnuplot(struct context *ctx) g_string_free(script, TRUE); } +static void check_input_constraints(struct context *ctx) +{ + size_t snum_count, snum_warn_limit; + size_t logic, analog; + gboolean has_frames, is_short, is_mixed, is_multi_analog; + gboolean do_warn; + + /* + * Check and conditionally warn exactly once during execution + * of the output module on a set of input data. + */ + if (ctx->have_checked) + return; + ctx->have_checked = TRUE; + + /* + * This implementation of the CSV output module assumes some + * constraints which need not be met in reality. Emit warnings + * until a better version becomes available. Letting users know + * that their request may not get processed correctly is the + * only thing we can do for now except for complete refusal to + * process the input data. + * + * What the implementation appears to assume (unverified, this + * interpretation may be incorrect and/or incomplete): + * - Multi-channel analog data, or mixed signal input, always + * is enclosed in frame markers. + * - Data which gets received across several packets spans a + * consistent sample number range. All samples of one frame + * and channel number or data type fit into a single packet. + * Arbitrary chunking seems to not be supported. + * - A specific order of analog data packets is assumed. + * + * With these assumptions encoded in the implementation, and + * not being met at runtime, incorrect and unexpected results + * were seen for these configurations: + * - More than one analog channel. + * - The combination of logic and analog channel types. + * + * The condition of frames with large sample counts is a wild + * guess, the limit is a totally arbitrary choice. It assumes + * typical scope frames with at most a few thousand samples per + * frame, and assumes that a channel's data gets sent in large + * enough packets. The absence of a warning message does not + * necessarily translate to correct output, it's more of a rate + * limiting approach to not scare users too much. + */ + snum_count = ctx->pkt_snums; + snum_warn_limit = 1 * 1000 * 1000; + logic = ctx->num_logic_channels; + analog = ctx->num_analog_channels; + has_frames = ctx->have_frames; + is_short = snum_count < snum_warn_limit; + is_mixed = logic && analog; + is_multi_analog = analog > 1; + + if (has_frames && is_short) { + sr_info("Assuming consistent framed input data."); + return; + } + + do_warn = FALSE; + if (has_frames) { + sr_warn("Untested configuration: large frame content."); + do_warn = TRUE; + } + if (is_mixed) { + sr_warn("Untested configuration: mixed signal input data."); + do_warn = TRUE; + } + if (is_multi_analog) { + sr_warn("Untested configuration: multi-channel analog data."); + do_warn = TRUE; + } + if (!do_warn) + return; + sr_warn("Resulting CSV output data may be incomplete or incorrect."); +} + static int receive(const struct sr_output *o, const struct sr_datafeed_packet *packet, GString **out) { struct context *ctx; + const struct sr_datafeed_logic *logic; + const struct sr_datafeed_analog *analog; *out = NULL; if (!o || !o->sdi) @@ -547,20 +672,34 @@ static int receive(const struct sr_output *o, sr_dbg("Got packet of type %d", packet->type); switch (packet->type) { case SR_DF_HEADER: + ctx->have_checked = FALSE; + ctx->have_frames = FALSE; + ctx->pkt_snums = FALSE; *out = gen_header(o, packet->payload); break; case SR_DF_TRIGGER: ctx->trigger = TRUE; break; case SR_DF_LOGIC: - process_logic(ctx, packet->payload); + *out = g_string_sized_new(512); + logic = packet->payload; + ctx->pkt_snums = logic->length; + ctx->pkt_snums /= logic->length; + check_input_constraints(ctx); + process_logic(ctx, logic); break; case SR_DF_ANALOG: - process_analog(ctx, packet->payload); + *out = g_string_sized_new(512); + analog = packet->payload; + ctx->pkt_snums = analog->num_samples; + ctx->pkt_snums /= g_slist_length(analog->meaning->channels); + check_input_constraints(ctx); + process_analog(ctx, analog); break; case SR_DF_FRAME_BEGIN: + ctx->have_frames = TRUE; *out = g_string_new(ctx->frame); - /* And then fall through to... */ + /* Fallthrough */ case SR_DF_END: /* Got to end of frame/session with part of the data. */ if (ctx->channels_seen) @@ -590,6 +729,7 @@ static int cleanup(struct sr_output *o) g_free((gpointer)ctx->frame); g_free((gpointer)ctx->comment); g_free((gpointer)ctx->gnuplot); + g_free((gpointer)ctx->value); g_free(ctx->previous_sample); g_free(ctx->channels); g_free(o->priv); @@ -604,10 +744,10 @@ static struct sr_option options[] = { {"scale", "scale", "Scale gnuplot graphs", NULL, NULL}, {"value", "Value separator", "Character to print between values", NULL, NULL}, {"record", "Record separator", "String to print between records", NULL, NULL}, - {"frame", "Frame seperator", "String to print between frames", NULL, NULL}, + {"frame", "Frame separator", "String to print between frames", NULL, NULL}, {"comment", "Comment start string", "String used at start of comment lines", NULL, NULL}, {"header", "Output header", "Output header comment with capture metdata", NULL, NULL}, - {"label", "Label values", "Output labels for each value", NULL, NULL}, + {"label", "Label values", "Type of column labels", NULL, NULL}, {"time", "Time column", "Output sample time as column 1", NULL, NULL}, {"trigger", "Trigger column", "Output trigger indicator as last column ", NULL, NULL}, {"dedup", "Dedup rows", "Set to false to output duplicate rows", NULL, NULL}, @@ -616,6 +756,8 @@ static struct sr_option options[] = { static const struct sr_option *get_options(void) { + GSList *l = NULL; + if (!options[0].def) { options[0].def = g_variant_ref_sink(g_variant_new_string("")); options[1].def = g_variant_ref_sink(g_variant_new_boolean(TRUE)); @@ -624,10 +766,14 @@ static const struct sr_option *get_options(void) options[4].def = g_variant_ref_sink(g_variant_new_string("\n")); options[5].def = g_variant_ref_sink(g_variant_new_string(";")); options[6].def = g_variant_ref_sink(g_variant_new_boolean(TRUE)); - options[7].def = g_variant_ref_sink(g_variant_new_boolean(TRUE)); - options[8].def = g_variant_ref_sink(g_variant_new_boolean(TRUE)); + options[7].def = g_variant_ref_sink(g_variant_new_string("units")); + l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string("units"))); + l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string("channel"))); + l = g_slist_append(l, g_variant_ref_sink(g_variant_new_string("off"))); + options[7].values = l; + options[8].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); options[9].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); - options[10].def = g_variant_ref_sink(g_variant_new_boolean(TRUE)); + options[10].def = g_variant_ref_sink(g_variant_new_boolean(FALSE)); } return options;