From 44d2af5e2793dd9eec45a40e90bdbdd461d73026 Mon Sep 17 00:00:00 2001
From: "Christian W. Zuckschwerdt" <zany@triq.net>
Date: Fri, 7 Feb 2020 12:54:13 +0100
Subject: [PATCH] Add native Sigrok writer (#1297)

---
 examples/sigrok-conv.sh |  60 +------------
 examples/sigrok-open.sh |  66 +-------------
 include/r_api.h         |   4 +
 include/rtl_433.h       |   2 +
 include/write_sigrok.h  |  31 +++++++
 src/CMakeLists.txt      |   1 +
 src/r_api.c             |  38 +++++++++
 src/rtl_433.c           |   7 ++
 src/write_sigrok.c      | 184 ++++++++++++++++++++++++++++++++++++++++
 9 files changed, 269 insertions(+), 124 deletions(-)
 create mode 100644 include/write_sigrok.h
 create mode 100644 src/write_sigrok.c

diff --git a/examples/sigrok-conv.sh b/examples/sigrok-conv.sh
index 070edac9..c7560989 100755
--- a/examples/sigrok-conv.sh
+++ b/examples/sigrok-conv.sh
@@ -1,61 +1,3 @@
 #!/bin/bash
 
-if [ -z "$1" ] ; then
-  echo input file missing
-  echo "Usage: $0 input.cu8 output.sr [sample rate in kHz]"
-  exit 1
-fi
-if [ ! -r "$1" ] ; then
-  echo input not found
-  echo "Usage: $0 input.cu8 output.sr [sample rate in kHz]"
-  exit 1
-fi
-file=$1
-
-if [ -z "$2" ] ; then
-  echo output file missing
-  echo "Usage: $0 input.cu8 output.sr [sample rate in kHz]"
-  exit 1
-fi
-if [ -e "$2" ] ; then
-  echo output already exists
-  echo "Usage: $0 input.cu8 output.sr [sample rate in kHz]"
-  exit 1
-fi
-out=$2
-
-if [ -z "$3" ] ; then
-  rate=250
-else
-  rate=$3
-fi
-
-if [ ! -z "$4" ] ; then
-  echo too many arguments
-  echo "Usage: $0 input.cu8 output.sr [sample rate in kHz]"
-  exit 1
-fi
-
-# create channels
-rtl_433 -s ${rate}k -r "$file" -w F32:I:analog-1-4-1 -w F32:Q:analog-1-5-1 -w F32:AM:analog-1-6-1 -w F32:FM:analog-1-7-1 -w U8:LOGIC:logic-1-1 >/dev/null 2>&1
-# create version tag
-echo -n "2" >version
-# create meta data
-cat >metadata <<EOF
-[device 1]
-capturefile=logic-1
-total probes=3
-samplerate=$rate kHz
-total analog=4
-probe1=FRAME
-probe2=ASK
-probe3=FSK
-analog4=I
-analog5=Q
-analog6=AM
-analog7=FM
-unitsize=1
-EOF
-
-zip "$out" version metadata analog-1-4-1 analog-1-5-1 analog-1-6-1 analog-1-7-1 logic-1-1
-rm version metadata analog-1-4-1 analog-1-5-1 analog-1-6-1 analog-1-7-1 logic-1-1
+echo 'Please use "rtl_433 [-s <samplerate>] -w <output>.sr -r <input>.cu8"'
diff --git a/examples/sigrok-open.sh b/examples/sigrok-open.sh
index 6a10dfed..39ad80bc 100755
--- a/examples/sigrok-open.sh
+++ b/examples/sigrok-open.sh
@@ -1,67 +1,3 @@
 #!/bin/bash
 
-if [ -z "$1" ] ; then
-  echo input file missing
-  echo "Usage: $0 input.cu8 [sample rate in kHz]"
-  exit 1
-fi
-if [ ! -r "$1" ] ; then
-  echo input not found
-  echo "Usage: $0 input.cu8 [sample rate in kHz]"
-  exit 1
-fi
-file=$1
-
-filename=$(basename "$file")
-tempdir=$(mktemp -d)
-out="$tempdir/$filename.sr"
-trap "rm -f -- '$out'; rmdir -- '$tempdir'" EXIT
-
-if [ -z "$2" ] ; then
-  rate=250
-else
-  rate=$2
-fi
-
-if [ ! -z "$3" ] ; then
-  echo too many arguments
-  echo "Usage: $0 input.cu8 [sample rate in kHz]"
-  exit 1
-fi
-
-# create channels
-rtl_433 -s ${rate}k -r "$file" -w F32:I:analog-1-4-1 -w F32:Q:analog-1-5-1 -w F32:AM:analog-1-6-1 -w F32:FM:analog-1-7-1 -w U8:LOGIC:logic-1-1 >/dev/null 2>&1
-# create version tag
-echo -n "2" >version
-# create meta data
-cat >metadata <<EOF
-[device 1]
-capturefile=logic-1
-total probes=3
-samplerate=$rate kHz
-total analog=4
-probe1=FRAME
-probe2=ASK
-probe3=FSK
-analog4=I
-analog5=Q
-analog6=AM
-analog7=FM
-unitsize=1
-EOF
-
-zip "$out" version metadata analog-1-4-1 analog-1-5-1 analog-1-6-1 analog-1-7-1 logic-1-1
-rm version metadata analog-1-4-1 analog-1-5-1 analog-1-6-1 analog-1-7-1 logic-1-1
-
-case "$OSTYPE" in
-  darwin*)
-    open -b org.sigrok.PulseView --fresh --new --wait-apps --args -i "$out"
-    ;;
-  *)
-    pulseview -i "$out"
-    ;;
-esac
-
-rm -f -- "$out"
-rmdir -- "$tempdir"
-trap - EXIT
+echo 'Please use "rtl_433 [-s <samplerate>] -W <output>.sr -r <input>.cu8"'
diff --git a/include/r_api.h b/include/r_api.h
index e8711576..790282f5 100644
--- a/include/r_api.h
+++ b/include/r_api.h
@@ -86,6 +86,10 @@ void add_null_output(struct r_cfg *cfg, char *param);
 
 void start_outputs(struct r_cfg *cfg, char const **well_known);
 
+void add_sr_dumper(struct r_cfg *cfg, char const *spec, int overwrite);
+
+void close_dumpers(struct r_cfg *cfg);
+
 void add_dumper(struct r_cfg *cfg, char const *spec, int overwrite);
 
 void add_infile(struct r_cfg *cfg, char *in_file);
diff --git a/include/rtl_433.h b/include/rtl_433.h
index c42fb6bc..c41331e1 100644
--- a/include/rtl_433.h
+++ b/include/rtl_433.h
@@ -94,6 +94,8 @@ typedef struct r_cfg {
     char *output_tag;
     list_t output_handler;
     struct dm_state *demod;
+    char const *sr_filename;
+    int sr_execopen;
     int old_model_keys;
     /* stats*/
     unsigned frames_count; ///< stats counter for interval
diff --git a/include/write_sigrok.h b/include/write_sigrok.h
new file mode 100644
index 00000000..521631ae
--- /dev/null
+++ b/include/write_sigrok.h
@@ -0,0 +1,31 @@
+/** @file
+    Sigrok Pulseview format writer.
+
+    Copyright (C) 2020 by Christian Zuckschwerdt <zany@triq.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+*/
+
+#ifndef INCLUDE_WRITE_SIGROK_
+#define INCLUDE_WRITE_SIGROK_
+
+/** Write a Sigrok file from data dump files.
+
+    @param filename file to write
+    @param samplerate sample rate for the channels
+    @param probes number of binary channels, needs "logic-1-1" file
+    @param analogs number of analog channels, needs "analog-1-N-1" with N starting at probes+1
+    @param labels channel labels, probes+analog strings or NULL for generic labels
+*/
+void write_sigrok(char const *filename, unsigned samplerate, unsigned probes, unsigned analogs, char const *labels[]);
+
+/** Open a file in a forked Pulseview.
+
+    @param filename file to open in Pulseview
+*/
+void open_pulseview(char const *filename);
+
+#endif /* INCLUDE_WRITE_SIGROK_ */
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 400931c1..e6470058 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -29,6 +29,7 @@ add_library(r_433 STATIC
     sdr.c
     term_ctl.c
     util.c
+    write_sigrok.c
     devices/acurite.c
     devices/akhan_100F14.c
     devices/alecto.c
diff --git a/src/r_api.c b/src/r_api.c
index f181212a..fad2293c 100644
--- a/src/r_api.c
+++ b/src/r_api.c
@@ -28,6 +28,7 @@
 #include "optparse.h"
 #include "output_mqtt.h"
 #include "output_influx.h"
+#include "write_sigrok.h"
 #include "compat_time.h"
 #include "fatal.h"
 
@@ -878,8 +879,45 @@ void add_null_output(r_cfg_t *cfg, char *param)
     list_push(&cfg->output_handler, NULL);
 }
 
+void add_sr_dumper(r_cfg_t *cfg, char const *spec, int overwrite)
+{
+    // create channels
+    add_dumper(cfg, "U8:LOGIC:logic-1-1", overwrite);
+    add_dumper(cfg, "F32:I:analog-1-4-1", overwrite);
+    add_dumper(cfg, "F32:Q:analog-1-5-1", overwrite);
+    add_dumper(cfg, "F32:AM:analog-1-6-1", overwrite);
+    add_dumper(cfg, "F32:FM:analog-1-7-1", overwrite);
+    cfg->sr_filename = spec;
+    cfg->sr_execopen = overwrite;
+}
+
+void close_dumpers(struct r_cfg *cfg)
+{
+    char const *labels[] = {
+            "FRAME", // probe1
+            "ASK", // probe2
+            "FSK", // probe3
+            "I", // analog4
+            "Q", // analog5
+            "AM", // analog6
+            "FM", // analog7
+    };
+    if (cfg->sr_filename) {
+        write_sigrok(cfg->sr_filename, cfg->samp_rate, 3, 4, labels);
+    }
+    if (cfg->sr_execopen) {
+        open_pulseview(cfg->sr_filename);
+    }
+}
+
 void add_dumper(r_cfg_t *cfg, char const *spec, int overwrite)
 {
+    size_t spec_len = strlen(spec);
+    if (spec_len >= 3 && !strcmp(&spec[spec_len - 3], ".sr")) {
+        add_sr_dumper(cfg, spec, overwrite);
+        return;
+    }
+
     file_info_t *dumper = calloc(1, sizeof(*dumper));
     if (!dumper)
         FATAL_CALLOC("add_dumper()");
diff --git a/src/rtl_433.c b/src/rtl_433.c
index 2c625825..e98c7c2f 100644
--- a/src/rtl_433.c
+++ b/src/rtl_433.c
@@ -48,6 +48,7 @@
 #include "term_ctl.h"
 #include "compat_paths.h"
 #include "fatal.h"
+#include "write_sigrok.h"
 
 #ifdef _WIN32
 #include <io.h>
@@ -1465,12 +1466,18 @@ int main(int argc, char **argv) {
                 fclose(in_file = stdin);
         }
 
+        close_dumpers(cfg);
         free(test_mode_buf);
         free(test_mode_float_buf);
         r_free_cfg(cfg);
         exit(0);
     }
 
+    if (cfg->sr_filename) {
+        fprintf(stderr, "SR writing not recommended for live input\n");
+        exit(1);
+    }
+
     // Normal case, no test data, no in files
     r = sdr_open(&cfg->dev, &demod->sample_size, cfg->dev_query, cfg->verbosity);
     if (r < 0) {
diff --git a/src/write_sigrok.c b/src/write_sigrok.c
new file mode 100644
index 00000000..5135c5a9
--- /dev/null
+++ b/src/write_sigrok.c
@@ -0,0 +1,184 @@
+/** @file
+    Sigrok Pulseview format writer.
+
+    Copyright (C) 2020 by Christian Zuckschwerdt <zany@triq.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#ifndef _MSC_VER
+#include <unistd.h>
+#endif
+
+#include "write_sigrok.h"
+
+void write_sigrok(char const *filename, unsigned samplerate, unsigned probes, unsigned analogs, char const *labels[])
+{
+#ifdef _WIN32
+    fprintf(stderr, "Writing Sigrok not implemented for win32\n");
+#else
+    // e.g. uses channels
+    // U8:LOGIC:logic-1-1
+    // F32:I:analog-1-4-1
+    // F32:Q:analog-1-5-1
+    // F32:AM:analog-1-6-1
+    // F32:FM:analog-1-7-1
+
+    // probe1=FRAME
+    // probe2=ASK
+    // probe3=FSK
+    // analog4=I
+    // analog5=Q
+    // analog6=AM
+    // analog7=FM
+
+    // create version tag
+    FILE *fp = fopen("version", "w");
+    if (!fp) {
+        perror("creating Sigrok \"version\" file");
+        return;
+    }
+    fprintf(fp, "2");
+    fclose(fp);
+
+    // create meta data
+    fp = fopen("metadata", "w");
+    if (!fp) {
+        perror("creating Sigrok \"metadata\" file");
+        return;
+    }
+    fprintf(fp,
+            "[device 1]\n"
+            "samplerate=%u kHz\n"
+            "capturefile=logic-1\n"
+            "unitsize=1\n"
+            "total probes=%u\n"
+            "total analog=%u\n",
+            samplerate / 1000, probes, analogs);
+    if (labels) {
+        char const **label = labels;
+        for (unsigned i = 1; i <= probes; ++i)
+            fprintf(fp, "probe%u=%s\n", i, *label++);
+        for (unsigned i = probes + 1; i <= probes + analogs; ++i)
+            fprintf(fp, "analog%u=%s\n", i, *label++);
+    }
+    else {
+        for (unsigned i = 1; i <= probes; ++i)
+            fprintf(fp, "probe%u=L%u\n", i, i);
+        for (unsigned i = probes + 1; i <= probes + analogs; ++i)
+            fprintf(fp, "analog%u=A%u\n", i, i);
+    }
+
+    // EOF
+    fclose(fp);
+
+    char *argv[30] = {0};
+    int arg        = 0;
+    argv[arg++]    = "zip";
+    argv[arg++]    = (char *)filename; // "out.sr"
+    argv[arg++]    = "version";
+    argv[arg++]    = "metadata";
+
+    if (probes) {
+        argv[arg++] = "logic-1-1";
+    }
+
+    char **argv_analog = &argv[arg];
+    for (unsigned i = probes + 1; i <= probes + analogs; ++i) {
+        asprintf(&argv[arg++], "analog-1-%u-1", i);
+    }
+
+    pid_t pid = fork();
+    if (pid < 0) {
+        perror("forking zip");
+        return;
+    }
+    else if (pid == 0) {
+        // child process because return value zero
+        execvp(argv[0], argv);
+        // execvp() returns only on error
+        for (int i = 0; i < arg; ++i)
+            fprintf(stderr, "%s ", argv[i]);
+        fprintf(stderr, "\n");
+        perror("execvp");
+        exit(1);
+    }
+    else {
+        // parent process because return value non-zero
+        wait(NULL);
+        printf("Done!\n");
+    }
+
+    // rm version metadata logic-1-1 analog-1-4-1 analog-1-5-1 analog-1-6-1 analog-1-7-1
+    if (unlink("version")) {
+        perror("unlinking Sigrok \"version\" file");
+    }
+    if (unlink("metadata")) {
+        perror("unlinking Sigrok \"metadata\" file");
+    }
+
+    if (probes) {
+        if (unlink("logic-1-1")) {
+            perror("unlinking Sigrok \"logic-1-1\" file");
+        }
+    }
+    for (unsigned i = 0; i < analogs; ++i) {
+        if (unlink(argv_analog[i])) {
+            perror("unlinking Sigrok \"analog-1-N-1\" file");
+        }
+        free(argv_analog[i]);
+    }
+#endif
+}
+
+void open_pulseview(char const *filename)
+{
+#ifdef _WIN32
+    fprintf(stderr, "Opening Pulseview not implemented for win32\n");
+#else
+    char *argv[9] = {0};
+    int arg       = 0;
+    char *abspath = realpath(filename, NULL);
+#ifdef __APPLE__
+    argv[arg++] = "open";
+    argv[arg++] = "-b";
+    argv[arg++] = "org.sigrok.PulseView";
+    argv[arg++] = "--fresh";
+    argv[arg++] = "--new";
+    argv[arg++] = "--args";
+    argv[arg++] = "-i";
+    argv[arg++] = (char *)abspath;
+#else
+    argv[arg++] = "pulseview";
+    argv[arg++] = "-i";
+    argv[arg++] = abspath;
+#endif
+
+    fprintf(stderr, "Opening Pulseview...\n");
+    pid_t pid = fork();
+    if (pid < 0) {
+        perror("forking pulseview");
+        return;
+    }
+    else if (pid == 0) {
+        // child process because return value zero
+        execvp(argv[0], argv);
+        // execvp() returns only on error
+        for (int i = 0; i < arg; ++i)
+            fprintf(stderr, "%s ", argv[i]);
+        fprintf(stderr, "\n");
+        perror("execvp");
+        exit(1);
+    }
+    else {
+        // parent process because return value non-zero
+        wait(NULL);
+    }
+    free(abspath);
+#endif
+}