]> sigrok.org Git - libsigrok.git/blobdiff - src/output/csv.c
Don't reference SR_PACKAGE_VERSION_STRING directly in output modules.
[libsigrok.git] / src / output / csv.c
index 77b62285b2ff31c2d4b884ec43c4435c504c0ea8..1ff4e3a7f7fe2a4d5dbfc433e4329ba6661c35cf 100644 (file)
  * 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 <http://www.gnu.org/licenses/>.
  */
 
+/*
+ * Options and their values:
+ *
+ * gnuplot: Write out a gnuplot interpreter script (.gpi file) to plot
+ *          the datafile using the parameters given. It should be called
+ *          from a gnuplot session with the data file name as a parameter
+ *          after adjusting line styles, terminal, etc.
+ *
+ * scale:   The gnuplot graphs are scaled so they all have the same
+ *          peak-to-peak distance. Defaults to TRUE.
+ *
+ * value:   The string used to separate values in a record. Defaults to ','.
+ *
+ * record:  The string to use to separate records. Default is newline. gnuplot
+ *          files must use newline.
+ *
+ * frame:   The string to use when a frame ends. The default is a blank line.
+ *          This may confuse some CSV parsers, but it makes gnuplot happy.
+ *
+ * comment: The string that starts a comment line. Defaults to ';'.
+ *
+ * header:  Print header comment with capture metadata. 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.
+ *
+ * trigger: Whether or not to add a "trigger" column as the last column.
+ *          Defaults to FALSE.
+ *
+ * dedup:   Don't output duplicate rows. Defaults to FALSE. If time is off, then
+ *          this is forced to be off.
+ */
+
+#include <config.h>
+#include <math.h>
 #include <stdlib.h>
 #include <string.h>
 #include <glib.h>
-#include "config.h" /* Needed for PACKAGE_STRING and others. */
-#include "libsigrok.h"
+#include <libsigrok/libsigrok.h>
 #include "libsigrok-internal.h"
 
 #define LOG_PREFIX "output/csv"
 
+struct ctx_channel {
+       struct sr_channel *ch;
+       char *label;
+       float min, max;
+};
+
 struct context {
-       unsigned int num_enabled_channels;
-       uint64_t samplerate;
-       char separator;
-       gboolean header_done;
-       int *channel_index;
+       /* Options */
+       const char *gnuplot;
+       gboolean scale;
+       const char *value;
+       const char *record;
+       const char *frame;
+       const char *comment;
+       gboolean header, did_header;
+       gboolean label_do, label_did, label_names;
+       gboolean time;
+       gboolean do_trigger;
+       gboolean dedup;
+
+       /* Plot data */
+       unsigned int num_analog_channels;
+       unsigned int num_logic_channels;
+       struct ctx_channel *channels;
+
+       /* Metadata */
+       gboolean trigger;
+       uint32_t num_samples;
+       uint32_t channel_count, logic_channel_count;
+       uint32_t channels_seen;
+       uint64_t period;
+       uint64_t sample_time;
+       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. */
 };
 
 /*
  * TODO:
- *  - Option to specify delimiter character and/or string.
- *  - Option to (not) print metadata as comments.
- *  - Option to specify the comment character(s), e.g. # or ; or C/C++-style.
- *  - Option to (not) print samplenumber / time as extra column.
- *  - Option to "compress" output (only print changed samples, VCD-like).
  *  - Option to print comma-separated bits, or whole bytes/words (for 8/16
  *    channel LAs) as ASCII/hex etc. etc.
- *  - Trigger support.
  */
 
 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;
-       int i;
-
-       (void)options;
 
        if (!o || !o->sdi)
                return SR_ERR_ARG;
 
        ctx = g_malloc0(sizeof(struct context));
        o->priv = ctx;
-       ctx->separator = ',';
 
+       /* Options */
+       ctx->gnuplot = g_strdup(g_variant_get_string(
+               g_hash_table_lookup(options, "gnuplot"), NULL));
+       ctx->scale = g_variant_get_boolean(g_hash_table_lookup(options, "scale"));
+       ctx->value = g_strdup(g_variant_get_string(
+               g_hash_table_lookup(options, "value"), NULL));
+       ctx->record = g_strdup(g_variant_get_string(
+               g_hash_table_lookup(options, "record"), NULL));
+       ctx->frame = g_strdup(g_variant_get_string(
+               g_hash_table_lookup(options, "frame"), NULL));
+       ctx->comment = g_strdup(g_variant_get_string(
+               g_hash_table_lookup(options, "comment"), NULL));
+       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"));
+       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;
+
+       if (*ctx->gnuplot && g_strcmp0(ctx->record, "\n"))
+               sr_warn("gnuplot record separator must be newline.");
+
+       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, 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. */
        for (l = o->sdi->channels; l; l = l->next) {
                ch = l->data;
-               if (ch->type != SR_CHANNEL_LOGIC)
-                       continue;
-               if (!ch->enabled)
-                       continue;
-               ctx->num_enabled_channels++;
+               if (ch->type == SR_CHANNEL_LOGIC) {
+                       ctx->logic_channel_count++;
+                       if (ch->enabled)
+                               logic_channels++;
+               }
+               if (ch->type == SR_CHANNEL_ANALOG && ch->enabled)
+                       analog_channels++;
+       }
+       if (analog_channels) {
+               sr_info("Outputting %d analog values", analog_channels);
+               ctx->num_analog_channels = analog_channels;
        }
-       ctx->channel_index = g_malloc(sizeof(int) * ctx->num_enabled_channels);
+       if (logic_channels) {
+               sr_info("Outputting %d logic values", logic_channels);
+               ctx->num_logic_channels = logic_channels;
+       }
+       ctx->channels = g_malloc(sizeof(struct ctx_channel)
+               * (ctx->num_analog_channels + ctx->num_logic_channels));
 
        /* Once more to map the enabled channels. */
+       ctx->channel_count = g_slist_length(o->sdi->channels);
        for (i = 0, l = o->sdi->channels; l; l = l->next) {
                ch = l->data;
-               if (ch->type != SR_CHANNEL_LOGIC)
-                       continue;
-               if (!ch->enabled)
-                       continue;
-               ctx->channel_index[i++] = ch->index;
+               if (ch->enabled) {
+                       if (ch->type == SR_CHANNEL_ANALOG) {
+                               ctx->channels[i].min = FLT_MAX;
+                               ctx->channels[i].max = FLT_MIN;
+                       } else if (ch->type == SR_CHANNEL_LOGIC) {
+                               ctx->channels[i].min = 0;
+                               ctx->channels[i].max = 1;
+                       } 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;
+               }
        }
 
        return SR_OK;
 }
 
-static GString *gen_header(const struct sr_output *o)
+static const char *xlabels[] = {
+       "samples", "milliseconds", "microseconds", "nanoseconds", "picoseconds",
+       "femtoseconds", "attoseconds",
+};
+
+static GString *gen_header(const struct sr_output *o,
+                          const struct sr_datafeed_header *hdr)
 {
        struct context *ctx;
        struct sr_channel *ch;
        GVariant *gvar;
        GString *header;
-       GSList *l;
-       time_t t;
-       int num_channels, i;
+       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 (sr_config_get(o->sdi->driver, o->sdi, NULL,
+                                 SR_CONF_SAMPLERATE, &gvar) == SR_OK) {
+                       samplerate = g_variant_get_uint64(gvar);
+                       g_variant_unref(gvar);
+               }
+
+               i = 0;
+               sr = 1;
+               while (sr < samplerate) {
+                       i++;
+                       sr *= 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);
+       }
+       ctx->title = (o->sdi && o->sdi->driver) ? o->sdi->driver->longname : "unknown";
+
        /* Some metadata */
-       t = time(NULL);
-       g_string_append_printf(header, "; CSV, generated by %s on %s",
-                       PACKAGE_STRING, ctime(&t));
-
-       /* Columns / channels */
-       num_channels = g_slist_length(o->sdi->channels);
-       g_string_append_printf(header, "; Channels (%d/%d):",
-                       ctx->num_enabled_channels, num_channels);
-       for (i = 0, l = o->sdi->channels; l; l = l->next, i++) {
-               ch = l->data;
-               if (ch->type != SR_CHANNEL_LOGIC)
-                       continue;
-               if (!ch->enabled)
+       if (ctx->header && !ctx->did_header) {
+               /* save_gnuplot knows how many lines we print. */
+               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_get(), ctx->comment,
+                       ctx->title, ctime(&hdr->starttime.tv_sec));
+
+               /* Columns / 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 (l = channels; l; l = l->next) {
+                       ch = l->data;
+                       if (ch->enabled)
+                               g_string_append_printf(header, " %s,", ch->name);
+               }
+               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);
+                       g_string_append_printf(header, "%s Samplerate: %s\n",
+                                              ctx->comment, samplerate_s);
+                       g_free(samplerate_s);
+               }
+               ctx->did_header = TRUE;
+       }
+
+       return header;
+}
+
+/*
+ * Analog devices can have samples of different types. Since each
+ * packet has only one meaning, it is restricted to having at most one
+ * type of data. So they can send multiple packets for a single sample.
+ * To further complicate things, they can send multiple samples in a
+ * single packet.
+ *
+ * So we need to pull any channels of interest out of a packet and save
+ * them until we have complete samples to output. Some devices make this
+ * simple by sending DF_FRAME_BEGIN/DF_FRAME_END packets, the latter of which
+ * signals the end of a set of samples, so we can dump things there.
+ *
+ * At least one driver (the demo driver) sends packets that contain parts of
+ * multiple samples without wrapping them in DF_FRAME. Possibly this driver
+ * is buggy, but it's also the standard for testing, so it has to be supported
+ * as is.
+ *
+ * Many assumptions about the "shape" of the data here:
+ *
+ * All of the data for a channel is assumed to be in one frame;
+ * otherwise the data in the second packet will overwrite the data in
+ * the first packet.
+ */
+static void process_analog(struct context *ctx,
+                          const struct sr_datafeed_analog *analog)
+{
+       int ret;
+       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
+                       * sizeof(float) * ctx->num_analog_channels);
+               if (!ctx->num_samples)
+                       ctx->num_samples = analog->num_samples;
+       }
+       if (ctx->num_samples != analog->num_samples)
+               sr_warn("Expecting %u analog samples, got %u.",
+                       ctx->num_samples, analog->num_samples);
+
+       meaning = analog->meaning;
+       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.");
+
+       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;
-               g_string_append_printf(header, " %s,", ch->name);
+               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++;
        }
-       if (o->sdi->channels)
-               /* Drop last separator. */
-               g_string_truncate(header, header->len - 1);
-       g_string_append_printf(header, "\n");
+       g_free(fdata);
+}
 
-       if (ctx->samplerate == 0) {
-               if (sr_config_get(o->sdi->driver, o->sdi, NULL, SR_CONF_SAMPLERATE,
-                               &gvar) == SR_OK) {
-                       ctx->samplerate = g_variant_get_uint64(gvar);
-                       g_variant_unref(gvar);
+/*
+ * We treat logic packets the same as analog packets, though it's not
+ * strictly required. This allows us to process mixed signals properly.
+ */
+static void process_logic(struct context *ctx,
+                         const struct sr_datafeed_logic *logic)
+{
+       unsigned int i, j, ch, num_samples;
+       int idx;
+       uint8_t *sample;
+
+       num_samples = logic->length / logic->unitsize;
+       ctx->channels_seen += ctx->logic_channel_count;
+       sr_dbg("Logic packet had %d channels", logic->unitsize * 8);
+       if (!ctx->logic_samples) {
+               ctx->logic_samples = g_malloc(num_samples * ctx->num_logic_channels);
+               if (!ctx->num_samples)
+                       ctx->num_samples = num_samples;
+       }
+       if (ctx->num_samples != num_samples)
+               sr_warn("Expecting %u samples, got %u",
+                       ctx->num_samples, num_samples);
+
+       for (j = ch = 0; ch < ctx->num_logic_channels; j++) {
+               if (ctx->channels[j].ch->type == SR_CHANNEL_LOGIC) {
+                       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++;
+               }
+       }
+}
+
+static void dump_saved_values(struct context *ctx, GString **out)
+{
+       unsigned int i, j, analog_size, num_channels;
+       float *analog_sample, value;
+       uint8_t *logic_sample;
+
+       /* If we haven't seen samples we're expecting, skip them. */
+       if ((ctx->num_analog_channels && !ctx->analog_samples) ||
+           (ctx->num_logic_channels && !ctx->logic_samples)) {
+               sr_warn("Discarding partial packet");
+       } else {
+               sr_info("Dumping %u samples", ctx->num_samples);
+
+               *out = g_string_sized_new(512);
+               num_channels =
+                   ctx->num_logic_channels + ctx->num_analog_channels;
+
+               if (ctx->label_do) {
+                       if (ctx->time)
+                               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].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",
+                                                      ctx->value);
+                       /* Drop last separator. */
+                       g_string_truncate(*out, (*out)->len - 1);
+                       g_string_append(*out, ctx->record);
+
+                       ctx->label_do = FALSE;
+               }
+
+               analog_size = ctx->num_analog_channels * sizeof(float);
+               if (ctx->dedup && !ctx->previous_sample)
+                       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 =
+                           &ctx->logic_samples[i * ctx->num_logic_channels];
+
+                       if (ctx->dedup) {
+                               if (i > 0 && i < ctx->num_samples - 1 &&
+                                   !memcmp(logic_sample, ctx->previous_sample,
+                                           ctx->num_logic_channels) &&
+                                   !memcmp(analog_sample,
+                                           ctx->previous_sample +
+                                           ctx->num_logic_channels,
+                                           analog_size))
+                                       continue;
+                               memcpy(ctx->previous_sample, logic_sample,
+                                      ctx->num_logic_channels);
+                               memcpy(ctx->previous_sample
+                                      + ctx->num_logic_channels,
+                                      analog_sample, analog_size);
+                       }
+
+                       if (ctx->time)
+                               g_string_append_printf(*out, "%" PRIu64 "%s",
+                                       ctx->sample_time, ctx->value);
+
+                       for (j = 0; j < num_channels; j++) {
+                               if (ctx->channels[j].ch->type == SR_CHANNEL_ANALOG) {
+                                       value = ctx->analog_samples[i * ctx->num_analog_channels + j];
+                                       ctx->channels[j].max =
+                                           fmax(value, ctx->channels[j].max);
+                                       ctx->channels[j].min =
+                                           fmin(value, ctx->channels[j].min);
+                                       g_string_append_printf(*out, "%g%s",
+                                               value, ctx->value);
+                               } else if (ctx->channels[j].ch->type == SR_CHANNEL_LOGIC) {
+                                       g_string_append_printf(*out, "%c%s",
+                                                              ctx->logic_samples[i * ctx->num_logic_channels + j] ? '1' : '0', ctx->value);
+                               } else {
+                                       sr_warn("Unexpected channel type: %d",
+                                               ctx->channels[i].ch->type);
+                               }
+                       }
+
+                       if (ctx->do_trigger) {
+                               g_string_append_printf(*out, "%d%s",
+                                       ctx->trigger, ctx->value);
+                               ctx->trigger = FALSE;
+                       }
+                       g_string_truncate(*out, (*out)->len - 1);
+                       g_string_append(*out, ctx->record);
                }
        }
-       if (ctx->samplerate != 0) {
-               samplerate_s = sr_samplerate_string(ctx->samplerate);
-               g_string_append_printf(header, "; Samplerate: %s\n", samplerate_s);
-               g_free(samplerate_s);
+
+       /* Discard all of the working space. */
+       g_free(ctx->previous_sample);
+       g_free(ctx->analog_samples);
+       g_free(ctx->logic_samples);
+       ctx->channels_seen = 0;
+       ctx->num_samples = 0;
+       ctx->previous_sample = NULL;
+       ctx->analog_samples = NULL;
+       ctx->logic_samples = NULL;
+}
+
+static void save_gnuplot(struct context *ctx)
+{
+       float offset, max, sum;
+       unsigned int i, num_channels;
+       GString *script;
+
+       script = g_string_sized_new(512);
+       g_string_append_printf(script, "set datafile separator '%s'\n",
+                              ctx->value);
+       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",
+                                      ctx->xlabel);
+
+       g_string_append(script, "plot ");
+
+       num_channels = ctx->num_analog_channels + ctx->num_logic_channels;
+
+       /* Graph position and scaling. */
+       max = FLT_MIN;
+       sum = 0;
+       for (i = 0; i < num_channels; i++) {
+               ctx->channels[i].max =
+                   ctx->channels[i].max - ctx->channels[i].min;
+               max = fmax(max, ctx->channels[i].max);
+               sum += ctx->channels[i].max;
+       }
+       sum = (ctx->scale ? max : sum / num_channels) / 4;
+       offset = sum;
+       for (i = num_channels; i > 0;) {
+               i--;
+               ctx->channels[i].min = offset - ctx->channels[i].min;
+               offset += sum + (ctx->scale ? max : ctx->channels[i].max);
        }
 
-       return header;
+       for (i = 0; i < num_channels; i++) {
+               sr_spew("Channel %d, min %g, max %g", i, ctx->channels[i].min,
+                       ctx->channels[i].max);
+               g_string_append(script, "ARG1 ");
+               if (ctx->did_header)
+                       g_string_append(script, "skip 4 ");
+               g_string_append_printf(script, "using %u:($%u * %g + %g), ",
+                       ctx->time, i + 1 + ctx->time, ctx->scale ?
+                       max / ctx->channels[i].max : 1, ctx->channels[i].min);
+               offset += 1.1 * (ctx->channels[i].max - ctx->channels[i].min);
+       }
+       g_string_truncate(script, script->len - 2);
+       g_file_set_contents(ctx->gnuplot, script->str, script->len, NULL);
+       g_string_free(script, TRUE);
 }
 
-static int receive(const struct sr_output *o, const struct sr_datafeed_packet *packet,
-               GString **out)
+static int receive(const struct sr_output *o,
+                  const struct sr_datafeed_packet *packet, GString **out)
 {
-       const struct sr_datafeed_meta *meta;
-       const struct sr_datafeed_logic *logic;
-       const struct sr_config *src;
-       GSList *l;
        struct context *ctx;
-       int idx;
-       uint64_t i, j;
-       gchar *p, c;
 
        *out = NULL;
        if (!o || !o->sdi)
@@ -157,42 +570,36 @@ static int receive(const struct sr_output *o, const struct sr_datafeed_packet *p
        if (!(ctx = o->priv))
                return SR_ERR_ARG;
 
+       sr_dbg("Got packet of type %d", packet->type);
        switch (packet->type) {
-       case SR_DF_META:
-               meta = packet->payload;
-               for (l = meta->config; l; l = l->next) {
-                       src = l->data;
-                       if (src->key != SR_CONF_SAMPLERATE)
-                               continue;
-                       ctx->samplerate = g_variant_get_uint64(src->data);
-               }
+       case SR_DF_HEADER:
+               *out = gen_header(o, packet->payload);
+               break;
+       case SR_DF_TRIGGER:
+               ctx->trigger = TRUE;
                break;
        case SR_DF_LOGIC:
-               logic = packet->payload;
-               if (!ctx->header_done) {
-                       *out = gen_header(o);
-                       ctx->header_done = TRUE;
-               } else {
-                       *out = g_string_sized_new(512);
-               }
-
-               for (i = 0; i <= logic->length - logic->unitsize; i += logic->unitsize) {
-                       for (j = 0; j < ctx->num_enabled_channels; j++) {
-                               idx = ctx->channel_index[j];
-                               p = logic->data + i + idx / 8;
-                               c = *p & (1 << (idx % 8));
-                               g_string_append_c(*out, c ? '1' : '0');
-                               g_string_append_c(*out, ctx->separator);
-                       }
-                       if (j) {
-                               /* Drop last separator. */
-                               g_string_truncate(*out, (*out)->len - 1);
-                       }
-                       g_string_append_printf(*out, "\n");
-               }
+               process_logic(ctx, packet->payload);
+               break;
+       case SR_DF_ANALOG:
+               process_analog(ctx, packet->payload);
+               break;
+       case SR_DF_FRAME_BEGIN:
+               *out = g_string_new(ctx->frame);
+               /* Fallthrough */
+       case SR_DF_END:
+               /* Got to end of frame/session with part of the data. */
+               if (ctx->channels_seen)
+                       ctx->channels_seen = ctx->channel_count;
+               if (*ctx->gnuplot)
+                       save_gnuplot(ctx);
                break;
        }
 
+       /* If we've got them all, dump the values. */
+       if (ctx->channels_seen >= ctx->channel_count)
+               dump_saved_values(ctx, out);
+
        return SR_OK;
 }
 
@@ -205,7 +612,13 @@ static int cleanup(struct sr_output *o)
 
        if (o->priv) {
                ctx = o->priv;
-               g_free(ctx->channel_index);
+               g_free((gpointer)ctx->record);
+               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);
                o->priv = NULL;
        }
@@ -213,12 +626,53 @@ static int cleanup(struct sr_output *o)
        return SR_OK;
 }
 
+static struct sr_option options[] = {
+       {"gnuplot", "gnuplot", "gnuplot script file name", NULL, NULL},
+       {"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 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", "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},
+       ALL_ZERO
+};
+
+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));
+               options[2].def = g_variant_ref_sink(g_variant_new_string(","));
+               options[3].def = g_variant_ref_sink(g_variant_new_string("\n"));
+               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_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(TRUE));
+               options[9].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
+               options[10].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
+       }
+
+       return options;
+}
+
 SR_PRIV struct sr_output_module output_csv = {
        .id = "csv",
        .name = "CSV",
        .desc = "Comma-separated values",
-       .exts = (const char*[]){"csv", NULL},
-       .options = NULL,
+       .exts = (const char *[]){"csv", NULL},
+       .flags = 0,
+       .options = get_options,
        .init = init,
        .receive = receive,
        .cleanup = cleanup,