summaryrefslogtreecommitdiffstats
path: root/virtual-programs
diff options
context:
space:
mode:
authorOmar Rizwan <omar@omar.website>2022-11-01 02:31:38 +0000
committerOmar Rizwan <omar@omar.website>2022-11-01 02:31:38 +0000
commit5360c625622d11608f4ffc76e87489134f4145fd (patch)
tree5eb7bafd60a47d3d50ac69c350fa35ab3ec3be0b /virtual-programs
parentAdd Ctrl-N shortcut while in program editor (diff)
downloadfolk-5360c625622d11608f4ffc76e87489134f4145fd.tar.gz
folk-5360c625622d11608f4ffc76e87489134f4145fd.zip
Start on radio stuff
Diffstat (limited to 'virtual-programs')
-rw-r--r--virtual-programs/radio-stream.folk12
-rw-r--r--virtual-programs/radio-tick-count.folk17
-rw-r--r--virtual-programs/radio.folk603
3 files changed, 632 insertions, 0 deletions
diff --git a/virtual-programs/radio-stream.folk b/virtual-programs/radio-stream.folk
new file mode 100644
index 00000000..b1731b13
--- /dev/null
+++ b/virtual-programs/radio-stream.folk
@@ -0,0 +1,12 @@
+Wish $this is highlighted red
+Wish $this has filename "radio-stream.folk"
+
+When the ssh tunnel is /something/ {
+When $::nodename has radio tick count /c/ {
+ Wish $this is labelled [time {
+ set sock [socket localhost 8099]
+ set data [read $sock]
+ close $sock
+ }]
+}
+} \ No newline at end of file
diff --git a/virtual-programs/radio-tick-count.folk b/virtual-programs/radio-tick-count.folk
new file mode 100644
index 00000000..89e6c743
--- /dev/null
+++ b/virtual-programs/radio-tick-count.folk
@@ -0,0 +1,17 @@
+Wish $this is highlighted red
+Wish $this has filename "radio-tick-count.folk"
+
+proc every {ms body} {
+ try $body
+ after $ms [list after idle [namespace code [info level 0]]]
+}
+set ::radioTickCount 0
+every 32 {
+ Retract $::nodename has radio tick count /c/
+ Assert $::nodename has radio tick count [incr ::radioTickCount]
+ Step
+}
+
+When $::nodename has radio tick count /c/ {
+ Wish $this is labelled $c
+} \ No newline at end of file
diff --git a/virtual-programs/radio.folk b/virtual-programs/radio.folk
new file mode 100644
index 00000000..27e08ac5
--- /dev/null
+++ b/virtual-programs/radio.folk
@@ -0,0 +1,603 @@
+Wish $this has filename "radio.folk"
+Wish $this is highlighted blue
+
+set plutosdrC {
+
+// modified by Omar Rizwan based on:
+//
+// https://github.com/analogdevicesinc/libiio/blob/master/examples/ad9361-iiostream.c
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ * libiio - AD9361 IIO streaming example
+ *
+ * Copyright (C) 2014 IABG mbH
+ * Author: Michael Feilen <feilen_at_iabg.de>
+ **/
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <signal.h>
+#include <stdio.h>
+#include <iio.h>
+#include <math.h>
+
+#include <unistd.h>
+#include <pthread.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+/* helper macros */
+#define KHZ(x) ((long long)(x*1000.0 + .5))
+#define MHZ(x) ((long long)(x*1000000.0 + .5))
+#define GHZ(x) ((long long)(x*1000000000.0 + .5))
+
+#define IIO_ENSURE(expr) { \
+ if (!(expr)) { \
+ (void) fprintf(stderr, "assertion failed (%s:%d)\n", __FILE__, __LINE__); \
+ (void) abort(); \
+ } \
+ }
+
+/* RX is input, TX is output */
+enum iodev { RX, TX };
+
+/* common RX and TX streaming params */
+struct stream_cfg {
+ long long bw_hz; // Analog banwidth in Hz
+ long long fs_hz; // Baseband sample rate in Hz
+ long long lo_hz; // Local oscillator frequency in Hz
+ double gain;
+ const char* rfport; // Port name
+};
+
+/* static scratch mem for strings */
+static char tmpstr[64];
+
+/* IIO structs required for streaming */
+static struct iio_context *ctx = NULL;
+static struct iio_channel *rx0_i = NULL;
+static struct iio_channel *rx0_q = NULL;
+static struct iio_channel *tx0_i = NULL;
+static struct iio_channel *tx0_q = NULL;
+static struct iio_buffer *rxbuf = NULL;
+static struct iio_buffer *txbuf = NULL;
+
+static bool stop;
+
+/* cleanup and exit */
+static void end()
+{
+ printf("* Destroying buffers\n");
+ if (rxbuf) { iio_buffer_destroy(rxbuf); }
+ if (txbuf) { iio_buffer_destroy(txbuf); }
+
+ printf("* Disabling streaming channels\n");
+ if (rx0_i) { iio_channel_disable(rx0_i); }
+ if (rx0_q) { iio_channel_disable(rx0_q); }
+ if (tx0_i) { iio_channel_disable(tx0_i); }
+ if (tx0_q) { iio_channel_disable(tx0_q); }
+
+ printf("* Destroying context\n");
+ if (ctx) { iio_context_destroy(ctx); }
+ exit(0);
+}
+
+static void handle_sig(int sig)
+{
+ printf("Waiting for process to finish... Got signal %d\n", sig);
+ stop = true;
+}
+
+/* check return value of attr_write function */
+static void errchk(int v, const char* what) {
+ if (v < 0) { fprintf(stderr, "Error %d writing to channel \"%s\"\nvalue may not be supported.\n", v, what); end(); }
+}
+
+/* write attribute: long long int */
+static void wr_ch_lli(struct iio_channel *chn, const char* what, long long val)
+{
+ errchk(iio_channel_attr_write_longlong(chn, what, val), what);
+}
+
+/* write attribute: string */
+static void wr_ch_str(struct iio_channel *chn, const char* what, const char* str)
+{
+ errchk(iio_channel_attr_write(chn, what, str), what);
+}
+
+/* helper function generating channel names */
+static char* get_ch_name(const char* type, int id)
+{
+ snprintf(tmpstr, sizeof(tmpstr), "%s%d", type, id);
+ return tmpstr;
+}
+
+/* returns ad9361 phy device */
+static struct iio_device* get_ad9361_phy(void)
+{
+ struct iio_device *dev = iio_context_find_device(ctx, "ad9361-phy");
+ IIO_ENSURE(dev && "No ad9361-phy found");
+ return dev;
+}
+
+/* finds AD9361 streaming IIO devices */
+static bool get_ad9361_stream_dev(enum iodev d, struct iio_device **dev)
+{
+ switch (d) {
+ case TX: *dev = iio_context_find_device(ctx, "cf-ad9361-dds-core-lpc"); return *dev != NULL;
+ case RX: *dev = iio_context_find_device(ctx, "cf-ad9361-lpc"); return *dev != NULL;
+ default: IIO_ENSURE(0); return false;
+ }
+}
+
+/* finds AD9361 streaming IIO channels */
+static bool get_ad9361_stream_ch(enum iodev d, struct iio_device *dev, int chid, struct iio_channel **chn)
+{
+ *chn = iio_device_find_channel(dev, get_ch_name("voltage", chid), d == TX);
+ if (!*chn)
+ *chn = iio_device_find_channel(dev, get_ch_name("altvoltage", chid), d == TX);
+ return *chn != NULL;
+}
+
+/* finds AD9361 phy IIO configuration channel with id chid */
+static bool get_phy_chan(enum iodev d, int chid, struct iio_channel **chn)
+{
+ switch (d) {
+ case RX: *chn = iio_device_find_channel(get_ad9361_phy(), get_ch_name("voltage", chid), false); return *chn != NULL;
+ case TX: *chn = iio_device_find_channel(get_ad9361_phy(), get_ch_name("voltage", chid), true); return *chn != NULL;
+ default: IIO_ENSURE(0); return false;
+ }
+}
+
+/* finds AD9361 local oscillator IIO configuration channels */
+static bool get_lo_chan(enum iodev d, struct iio_channel **chn)
+{
+ switch (d) {
+ // LO chan is always output, i.e. true
+ case RX: *chn = iio_device_find_channel(get_ad9361_phy(), get_ch_name("altvoltage", 0), true); return *chn != NULL;
+ case TX: *chn = iio_device_find_channel(get_ad9361_phy(), get_ch_name("altvoltage", 1), true); return *chn != NULL;
+ default: IIO_ENSURE(0); return false;
+ }
+}
+
+/* applies streaming configuration through IIO */
+bool cfg_ad9361_streaming_ch(struct stream_cfg *cfg, enum iodev type, int chid)
+{
+ struct iio_channel *chn = NULL;
+
+ // Configure phy and lo channels
+ printf("* Acquiring AD9361 phy channel %d\n", chid);
+ if (!get_phy_chan(type, chid, &chn)) { return false; }
+ wr_ch_str(chn, "rf_port_select", cfg->rfport);
+ wr_ch_lli(chn, "rf_bandwidth", cfg->bw_hz);
+ wr_ch_lli(chn, "sampling_frequency", cfg->fs_hz);
+ // https://ez.analog.com/linux-software-drivers/f/q-a/85564/why-use-this-method-to-set-the-hardwaregain-cause-error
+ if (type == RX) {
+ printf("setting gain control mode to manual\n");
+ wr_ch_str(chn, "gain_control_mode", "manual");
+ wr_ch_lli(chn, "quadrature_tracking_en", 0);
+ wr_ch_lli(chn, "bb_dc_offset_tracking_en", 0);
+ wr_ch_lli(chn, "rf_dc_offset_tracking_en", 0);
+ }
+ printf("set gain %s %d\n", type == TX ? "TX" : "RX", iio_channel_attr_write_double(chn, "hardwaregain", cfg->gain));
+
+ // Configure LO channel
+ printf("* Acquiring AD9361 %s lo channel\n", type == TX ? "TX" : "RX");
+ if (!get_lo_chan(type, &chn)) { return false; }
+ wr_ch_lli(chn, "frequency", cfg->lo_hz);
+ return true;
+}
+
+// from https://github.com/zaphoxx/zuhf-rfid/blob/main/libraries/ZUHF_CC1101/ZUHF_CRC.h
+// FIXME: rephrase
+void crc5Append(uint8_t *q)
+{
+ uint8_t crc[] = {1,0,0,1,0};
+ for(uint8_t i = 0; i < 17; i++)
+ {
+ uint8_t tmp[] = {0,0,0,0,0};
+ tmp[4] = crc[3];
+ if(crc[4] == 1)
+ {
+ if (q[i] == 1)
+ {
+ tmp[0] = 0;
+ tmp[1] = crc[0];
+ tmp[2] = crc[1];
+ tmp[3] = crc[2];
+ }
+ else
+ {
+ tmp[0] = 1;
+ tmp[1] = crc[0];
+ tmp[2] = crc[1];
+ if(crc[2] == 1)
+ {
+ tmp[3] = 0;
+ }
+ else
+ {
+ tmp[3] = 1;
+ }
+ }
+ }
+ else
+ {
+ if (q[i] == 1)
+ {
+ tmp[0] = 1;
+ tmp[1] = crc[0];
+ tmp[2] = crc[1];
+ if(crc[2] == 1)
+ {
+ tmp[3] = 0;
+ }
+ else
+ {
+ tmp[3] = 1;
+ }
+ }
+ else
+ {
+ tmp[0] = 0;
+ tmp[1] = crc[0];
+ tmp[2] = crc[1];
+ tmp[3] = crc[2];
+ }
+ }
+ memcpy(&crc,&tmp,5);
+ }
+ for (int i = 0; i < 5; i++){
+ memcpy(q+17+i, &crc[4-i], 1);
+ }
+}
+
+typedef uint8_t symbol_t; // logical symbols, not physical (not RF on/off)
+#define Delimiter 2
+#define RTcal 3
+#define TRcal 4
+
+typedef uint8_t pulse_t; // physical pulsewidth symbols (RF on/off)
+
+// Tari = 25 us
+// PW = Tari/2 = 12.5 us
+
+int to_pulses(pulse_t* pulsebuf, symbol_t* syms, size_t symcount) {
+ int pulsecount = 0;
+ for (size_t i = 0; i < symcount; i++) {
+ switch (syms[i]) {
+ case 0:
+ pulsebuf[pulsecount++] = 1;
+ pulsebuf[pulsecount++] = 0;
+ break;
+
+ case 1:
+ pulsebuf[pulsecount++] = 1;
+ pulsebuf[pulsecount++] = 1;
+ pulsebuf[pulsecount++] = 0;
+ break;
+
+ case Delimiter:
+ pulsebuf[pulsecount++] = 0; // always 12.5 us
+ break;
+
+ case RTcal: // (length of '0') + (length of '1')
+ pulsebuf[pulsecount++] = 1;
+ pulsebuf[pulsecount++] = 1;
+ pulsebuf[pulsecount++] = 1;
+ pulsebuf[pulsecount++] = 1;
+ pulsebuf[pulsecount++] = 0;
+ break;
+
+ case TRcal: // max 3 * (length of RTcal)
+ // currently 10 pulses = 125 us
+ // => BLF = 64 KHz, Tpri = 15.6 us
+ pulsebuf[pulsecount++] = 1;
+ pulsebuf[pulsecount++] = 1;
+ pulsebuf[pulsecount++] = 1;
+ pulsebuf[pulsecount++] = 1;
+ pulsebuf[pulsecount++] = 1;
+ pulsebuf[pulsecount++] = 1;
+ pulsebuf[pulsecount++] = 1;
+ pulsebuf[pulsecount++] = 1;
+ pulsebuf[pulsecount++] = 1;
+ pulsebuf[pulsecount++] = 1;
+ pulsebuf[pulsecount++] = 0;
+ break;
+ }
+ }
+
+ /* for (size_t i = 0; i < pulsecount; i++) { */
+ /* printf("pulsebuf[%d] = %d\n", i, pulsebuf[i]); */
+ /* } */
+ return pulsecount;
+}
+
+typedef struct { int16_t i; int16_t q; } iq_t;
+
+int cw(iq_t* iqbuf, size_t iqbufsize, long long samplerate, int us) {
+ // how many samples make 1 us ?
+ // how many samples make 12.5 us ?
+ // samplerate = how many samples make 1 second
+ // samplerate/1000000 = how many samples make 1 microsecond
+ int samplecount = us * samplerate/1000000;
+ int iqcount = 0;
+ for (int i = 0; i < samplecount; i++) {
+ iqbuf[iqcount++] = (iq_t) { 2047, 0 };
+ }
+ return iqcount;
+}
+
+int pulses_to_iq(iq_t* iqbuf, size_t iqbufsize, long long samplerate, pulse_t* pulses, size_t pulsecount) {
+ // how many samples make 1 us ?
+ // how many samples make 12.5 us ?
+ // samplerate = how many samples make 1 second
+ // samplerate/1000000 = how many samples make 1 microsecond
+ int samplesperpulse = 12.5 * samplerate/1000000;
+ /* printf("samplerate = %lld\nsamplesperpulse = %d\n", samplerate, samplesperpulse); */
+
+ int iqcount = 0;
+ for (size_t i = 0; i < pulsecount; i++) {
+ for (int j = 0; j < samplesperpulse; j++) {
+ iqbuf[iqcount++] = (iq_t) { pulses[i] * 2047, 0 };
+ }
+ }
+ return iqcount;
+}
+int query(iq_t* iqbuf, size_t iqbufsize, long long samplerate) {
+ const symbol_t PREAMBLE[] = {
+ Delimiter,
+ 0, // '0' (Tari)
+ RTcal,
+ TRcal
+ };
+ symbol_t QUERY[] = {
+ // Command (4)
+ 1, 0, 0, 0,
+ // DR (TRcal divide ratio) (1)
+ 0, // DR = 8
+ // M (cycles per symbol) (2)
+ 0, 0, // FM0 encoding
+ // TRext (no pilot tone) (1)
+ 0,
+ // Sel (2)
+ 0, 0,
+ // Session (2)
+ 0, 0,
+ // Target (1)
+ 0,
+ // Q (4)
+ 0, 0, 0, 0,
+ // CRC (5)
+ 255, 255, 255, 255, 255
+ };
+ // find the crc
+ crc5Append(QUERY);
+ // concatenate the PREAMBLE+QUERY
+ symbol_t symbuf[sizeof(PREAMBLE) + sizeof(QUERY)];
+ memcpy(symbuf, PREAMBLE, sizeof(PREAMBLE));
+ memcpy(symbuf + sizeof(PREAMBLE), QUERY, sizeof(QUERY));
+
+ // compile symbols to pulses
+ pulse_t pulsebuf[sizeof(symbuf) * 10]; // multiply by pulse count of longest symbol
+ int pulsecount = to_pulses(pulsebuf, symbuf, sizeof(symbuf));
+
+ return pulses_to_iq(iqbuf, iqbufsize, samplerate, pulsebuf, pulsecount);
+}
+
+struct stream_cfg rxcfg;
+
+// keep a global stream buffer
+pthread_rwlock_t stream_rwlock;
+iq_t *stream_buffer;
+int stream_buffer_length;
+
+// spawn a pthread that spawns a tcp server that can be connected to
+void *server_main(void *arg) {
+ (void)arg;
+ printf("server_main\n");
+
+ int fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (fd == -1) {
+ perror("socket"); return NULL;
+ }
+
+ int reuse = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) < 0)
+ perror("setsockopt(SO_REUSEADDR) failed");
+#ifdef SO_REUSEPORT
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuse, sizeof(reuse)) < 0)
+ perror("setsockopt(SO_REUSEPORT) failed");
+#endif
+
+ struct sockaddr_in addr;
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(8099);
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
+ perror("bind"); return NULL;
+ }
+ listen(fd, 10);
+
+ while (1) {
+ int client = accept(fd, NULL, NULL);
+ pthread_rwlock_rdlock(&stream_rwlock);
+ send(client, &rxcfg.lo_hz, sizeof(rxcfg.lo_hz), 0);
+ send(client, stream_buffer, sizeof(iq_t) * stream_buffer_length, 0);
+ pthread_rwlock_unlock(&stream_rwlock);
+ close(client);
+ }
+}
+
+
+/* simple configuration and streaming */
+/* usage:
+ * Default context, assuming local IIO devices, i.e., this script is run on ADALM-Pluto for example
+ $./a.out
+ * URI context, find out the uri by typing `iio_info -s` at the command line of the host PC
+ $./a.out usb:x.x.x
+*/
+int main (int argc, char **argv)
+{
+ struct iio_device *tx;
+ struct iio_device *rx;
+
+ // RX and TX sample counters
+ size_t nrx = 0;
+ size_t ntx = 0;
+
+ // Stream configurations
+ struct stream_cfg txcfg;
+
+ // Listen to ctrl+c and IIO_ENSURE
+ signal(SIGINT, handle_sig);
+
+ // RX stream config
+ rxcfg.bw_hz = MHZ(2.5); // 2 MHz rf bandwidth
+ rxcfg.fs_hz = MHZ(2.5); // 2.5 MS/s rx sample rate
+ rxcfg.lo_hz = MHZ(902.75);
+ rxcfg.gain = 10;
+ rxcfg.rfport = "A_BALANCED"; // port A (select for rf freq.)
+
+ // TX stream config
+ txcfg.bw_hz = MHZ(2.5);
+ txcfg.fs_hz = MHZ(2.5);
+ txcfg.lo_hz = MHZ(902.75);
+ txcfg.gain = -10; // assumes Nooelec amp
+ txcfg.rfport = "A"; // port A (select for rf freq.)
+
+ // stream_buffer_length = 5000 * rxcfg.fs_hz/1000000; // record 5000 us
+ stream_buffer_length = 16384;
+ stream_buffer = calloc(stream_buffer_length, sizeof(iq_t));
+
+ pthread_rwlock_init(&stream_rwlock, NULL);
+
+ pthread_t server;
+ pthread_create(&server, NULL, server_main, NULL);
+
+ printf("* Acquiring IIO context\n");
+ if (argc == 1) {
+ IIO_ENSURE((ctx = iio_create_default_context()) && "No context");
+ }
+ else if (argc == 2) {
+ IIO_ENSURE((ctx = iio_create_context_from_uri(argv[1])) && "No context");
+ }
+ IIO_ENSURE(iio_context_get_devices_count(ctx) > 0 && "No devices");
+
+ printf("* Acquiring AD9361 streaming devices\n");
+ IIO_ENSURE(get_ad9361_stream_dev(TX, &tx) && "No tx dev found");
+ IIO_ENSURE(get_ad9361_stream_dev(RX, &rx) && "No rx dev found");
+
+ printf("* Configuring AD9361 for streaming\n");
+ IIO_ENSURE(cfg_ad9361_streaming_ch(&rxcfg, RX, 0) && "RX port 0 not found");
+ IIO_ENSURE(cfg_ad9361_streaming_ch(&txcfg, TX, 0) && "TX port 0 not found");
+
+ printf("* Initializing AD9361 IIO streaming channels\n");
+ IIO_ENSURE(get_ad9361_stream_ch(RX, rx, 0, &rx0_i) && "RX chan i not found");
+ IIO_ENSURE(get_ad9361_stream_ch(RX, rx, 1, &rx0_q) && "RX chan q not found");
+ IIO_ENSURE(get_ad9361_stream_ch(TX, tx, 0, &tx0_i) && "TX chan i not found");
+ IIO_ENSURE(get_ad9361_stream_ch(TX, tx, 1, &tx0_q) && "TX chan q not found");
+
+ printf("* Enabling IIO streaming channels\n");
+ iio_channel_enable(rx0_i);
+ iio_channel_enable(rx0_q);
+ iio_channel_enable(tx0_i);
+ iio_channel_enable(tx0_q);
+
+ printf("* Creating non-cyclic IIO buffers\n");
+ rxbuf = iio_device_create_buffer(rx, 16384, false);
+ if (!rxbuf) {
+ perror("Could not create RX buffer");
+ end();
+ }
+ txbuf = iio_device_create_buffer(tx, 16384, false);
+ if (!txbuf) {
+ perror("Could not create TX buffer");
+ end();
+ }
+
+ printf("* Starting IO streaming (press CTRL+C to cancel)\n");
+ while (!stop) {
+ ssize_t nbytes_rx, nbytes_tx;
+ char *p_dat, *p_end;
+ ptrdiff_t p_inc;
+
+ // Schedule TX buffer
+ nbytes_tx = iio_buffer_push(txbuf);
+ if (nbytes_tx < 0) { printf("Error pushing buf %d\n", (int) nbytes_tx); end(); }
+
+ // Refill RX buffer
+ nbytes_rx = iio_buffer_refill(rxbuf);
+ if (nbytes_rx < 0) { printf("Error refilling buf %d\n",(int) nbytes_rx); end(); }
+
+ // READ: Get pointers to RX buf and read IQ from RX buf port 0
+ {
+ p_inc = iio_buffer_step(rxbuf);
+ p_end = iio_buffer_end(rxbuf);
+ int streamidx = 0;
+ pthread_rwlock_wrlock(&stream_rwlock);
+ for (p_dat = (char *)iio_buffer_first(rxbuf, rx0_i); p_dat < p_end; p_dat += p_inc) {
+ const int16_t i = ((int16_t*)p_dat)[0]; // Real (I)
+ const int16_t q = ((int16_t*)p_dat)[1]; // Imag (Q)
+
+ // Stream to buffer that gets sent out by the server thread
+ if (streamidx < stream_buffer_length) {
+ stream_buffer[streamidx++] = (iq_t) { i, q };
+ }
+ }
+ pthread_rwlock_unlock(&stream_rwlock);
+ }
+
+ // WRITE: Get pointers to TX buf and write IQ to TX buf port 0
+ {
+ iq_t iqbuf[1000000];
+ int iqcount = 0;
+ iqcount += cw(iqbuf + iqcount, sizeof(iqbuf)/sizeof(iq_t) - iqcount, txcfg.fs_hz, 1500);
+ iqcount += query(iqbuf + iqcount, sizeof(iqbuf)/sizeof(iq_t) - iqcount, txcfg.fs_hz);
+ // FIXME: how many microseconds should this followup CW actually be?
+ iqcount += cw(iqbuf + iqcount, sizeof(iqbuf)/sizeof(iq_t) - iqcount, txcfg.fs_hz, 16384 - iqcount);
+
+ int iqidx = 0;
+ p_inc = iio_buffer_step(txbuf);
+ p_end = iio_buffer_end(txbuf);
+ for (p_dat = (char *)iio_buffer_first(txbuf, tx0_i); p_dat < p_end; p_dat += p_inc) {
+ int16_t i = 0;
+ int16_t q = 0;
+ if (iqidx < iqcount) {
+ i = iqbuf[iqidx].i;
+ q = iqbuf[iqidx].q;
+ iqidx++;
+ }
+ ((int16_t*)p_dat)[0] = i << 4; // Real (I)
+ ((int16_t*)p_dat)[1] = q << 4; // Imag (Q)
+ }
+ }
+
+ // Sample counter increment and status output
+ nrx += nbytes_rx / iio_device_get_sample_size(rx);
+ ntx += nbytes_tx / iio_device_get_sample_size(tx);
+ /* printf("\tRX %8.2f MSmp, TX %8.2f MSmp\n", nrx/1e6, ntx/1e6); */
+ }
+
+ end();
+
+ return 0;
+}
+}
+
+set plutosdrCFile "[exec mktemp].c"
+set fd [open $plutosdrCFile w]; puts $fd $plutosdrC; close $fd
+
+if {[catch {set cc [exec which arm-linux-gnueabihf-gcc]}]} { set cc [exec which arm-unknown-linux-gnueabihf-gcc] }
+
+exec -ignorestderr $cc -mfloat-abi=hard --sysroot=$::env(HOME)/pluto-0.30.sysroot -std=gnu99 -g -o ad9361-iiostream $plutosdrCFile -lpthread -liio -lm -Wall -Wextra
+
+catch {exec sshpass -p analog ssh root@folk-sdr-pluto-ib fuser -k 8099/tcp}
+catch {pkill -9 $::ssh}
+exec sshpass -p analog scp ad9361-iiostream root@folk-sdr-pluto-ib:/root
+set ::ssh [exec sshpass -p analog ssh root@folk-sdr-pluto-ib -L 8099:localhost:8099 ./ad9361-iiostream >@stdout &]
+Claim the ssh tunnel is $::ssh
+Wish $this is labelled "Tunnel: $::ssh"
+
+