+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);
+ }
+
+ 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 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)