Raspberry Pi and Dual Watch (2)

You can change your BFO frequency by clicking either BFO+ or BFO-.

We have two BPFs, each is a 151 tap FIR filter. The signals out of the first and the second BPFs, shown as gray and dark red bands in the figure, come out of the left and right channesl of the audio output, respectively.

Two programs, sprig_audio and node are running on a Raspberry Pi. For the two way communications between the programs, named FIFOs are used.

% mkfifo myfifo
% mkfifo myfifo2
//
%  (./sprig_audio /dev/ttyUSB0 hw:1,0 hw:0,0 > myfifo < myfifo2 &) ; node index.js < myfifo > myfifo2

Raspberry Pi and Dual Watch

As an example of digital signal processing capabilities of a Raspberry Pi, dual watch functionality is implemented.

The audio signal from IC-7410 is converted to an analytic signal using a Hilbert filter with 51 taps, and down converted with a BFO generating a 300 Hz complex sinusoid. The converted signal goes into the left channel of the audio output, while the original signal, after going through a 151 tap BPF, goes to the right channel.

To generate a single tone, say, around 660 Hz, an AM broadcasting signal at 954 kHz is received with IC-7410 set to a CW mode. The left and right channels show a 360 Hz and a 660 Hz signal, respectively.

You observe two peaks, each comming from the left and the right channels.

const double bfo_freq = +300.0; 
double bfo_phase      =    0.0;
//
double complex cbfo  = cos(bfo_phase) - I * sin(bfo_phase);
double complex cleft = cright * cbfo
bfo_phase += bfo_delta;
//
sample[2*i  ] = creal(cleft );
sample[2*i+1] = creal(cright);

IC-7410 Rig Control with http (8)

You can click on a trace on the waterfall image to tune into that frequency.

index.html

var cwmarker1 = Math.floor(  cwpitch        / (32000 / 4096) );
var cwmarker2 = Math.floor( (cwpitch - 100) / (32000 / 4096) );
var cwmarker3 = Math.floor( (cwpitch + 100) / (32000 / 4096) );

function onClick(e) {
  var rect = e.target.getBoundingClientRect();
  var mx   = e.clientX - rect.left;
  var my   = e.clientY - rect.top;
  socket.emit('message7', mx);
}
canvas1.addEventListener('click', onClick, false);

index.js

  socket.on('message7', function(mx) {
    var newfreq = freqHz - (32000/4096 * mx - cwpitch);
    setfreq(newfreq);
  });

Of course, you must consider which sideband you are listening to in calculating your new frequency.

IC-7410 Rig Control with http (7)

You can change the VFO frequency of your remote IC-7410 by clicking the buttons on the browser.

index.js

const bufa = new Buffer('fefe80e017', 'hex');              // preamble
const bufz = new Buffer('fd', 'hex');                      // postamble
const buf1 = new Buffer('fefe80e0174351fd', 'hex');        // CQ
const buf2 = new Buffer('fefe80e01751525a3ffd', 'hex');    // QRZ?
const buf3 = new Buffer('fefe80e003fd', 'hex');            // read freq
var buf4   = new Buffer('fefe80e0050000000000fd', 'hex');  // send freq

var SerialPort = require('serialport');
var serial     = new SerialPort(
    '/dev/ttyUSB0',
    {baudrate : 19200, parser : SerialPort.parsers.byteDelimiter([ 0xfd ])});

var http  = require('http');
var fs    = require('fs');
var index = fs.readFileSync(__dirname + '/index.html');
var app   = http.createServer(function(req, res) {
                res.writeHead(200, {'Content-Type' : 'text/html'});
                res.end(index);
              })
              .listen(3000);

var io     = require('socket.io').listen(app);
var freqHz = 7026000;

var ndata   = 512;  // data[512] + 0x0a
var pos     = 0;
var myarray = new Array();

// -- water fall --

var count = 0;
process.stdin.on('readable', function() {
  var buf = process.stdin.read();
  if (buf !== null) {
    for (var i = 0; i < buf.length; i++) {
      if (buf[i] == 0x0a) {
        if (pos >= 512) {
          io.emit('waterfall', myarray);
        }
        pos = 0;
      } else {
        myarray[pos++] = buf[i];
      }
    }
  }
});

// -- serial for IC-7410 --

serial.on('open',
          function() { console.log('serial port /dev/ttyUSB0 is opened.'); });

var count2 = 0;
serial.on('data', function(data) {
  //    console.log('received data: ', data.length, data);
  if (!(data[0] == 0xfe & data[1] == 0xfe)) {
    console.log('------------- received serial data error');
    }
  if (data[2] == 0xe0 & data[3] == 0x80 & data[4] == 0x03) {
    var f10   = data[5] >> 4 & 0x0f;
    var f1    = data[5] & 0x0f;
    var f1k   = data[6] >> 4 & 0x0f;
    var f100  = data[6] & 0x0f;
    var f100k = data[7] >> 4 & 0x0f;
    var f10k  = data[7] & 0x0f;
    var f10m  = data[8] >> 4 & 0x0f;
    var f1m   = data[8] & 0x0f;
    var freq  = f10m.toString() + f1m.toString() + "," + f100k.toString() +
               f10k.toString() + f1k.toString() + "." + f100.toString() +
               f10.toString() + f1 + " kHz";
    freqHz = f10m * 10000000 + f1m * 1000000 + f100k * 100000 + f10k * 10000 +
             f1k * 1000 + f100 * 100 + f10 * 10 + f1;
    //        console.log('count2 = ', count2++, 'freq: ' + freq, freqHz);
    io.emit('freqmsg', 'VFO A: ' + freq);
  }

});

serial
    .on('error', function(err) { console.log('Error: ', err.message); })

    // -- set frequency --

    function setfreq(f) {
      var f10m  = Math.floor(f / 10000000); f -= f10m  * 10000000;
      var f1m   = Math.floor(f / 1000000);  f -= f1m   * 1000000;
      var f100k = Math.floor(f / 100000);   f -= f100k * 100000;
      var f10k  = Math.floor(f / 10000);    f -= f10k  * 10000;
      var f1k   = Math.floor(f / 1000);     f -= f1k   * 1000;
      var f100  = Math.floor(f / 100);      f -= f100  * 100;
      var f10   = Math.floor(f / 10);       f -= f10   * 10;
      var f1    = Math.floor(f / 1);

      var data = new Array(
          [ 0xfe, 0xfe, 0x80, 0xe0, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd ]);
      buf4[5] = f10   << 4 | f1;
      buf4[6] = f1k   << 4 | f100;
      buf4[7] = f100k << 4 | f10k;
      buf4[8] = f10m  << 4 | f1m;
      buf4[9] = 0;
      serial.write(buf4);
    }

// -- socket --

io.on('connection', function(socket) {

  socket.on('message1', function() { serial.write(buf1); });
  socket.on('message2', function() { serial.write(buf2); });

  socket.on('message3', function() {
    var newfreq = freqHz + 1000;
    setfreq(newfreq);
  });

  socket.on('message4', function() {
    var newfreq = freqHz + 100;
    setfreq(newfreq);
  });

  socket.on('message5', function() {
    var newfreq = freqHz - 100;
    setfreq(newfreq);
  });

  socket.on('message6', function() {
    var newfreq = freqHz - 1000;
    setfreq(newfreq);
  });

  socket.on('your message', function(msg) {
    serial.write(bufa);
    serial.write(msg);
    serial.write(bufz);
    io.emit('your message', msg);
  });

});

// -- request freq --

function sendTime() {
  serial.write(buf3);
  // console.log('Freq asked..');
}

setInterval(sendTime, 100);

// -- EOF --

index.html

<!doctype html>
<html>
<head>

<style>
* { margin: 0; padding: 0; box - sizing : border - box; }
body { font: 13px Helvetica, Arial; }
form { background: # 000; padding: 3px; position: fixed; bottom: 0; width: 100 % ; }
form input { border: 0; padding: 10px; width: 80 % ; margin - right : .5 % ; }
form button { width: 15 % ; background: rgb(130, 224, 255); border: none; padding: 10px; }
#messages{list - style - type : none; margin : 0; padding : 0; }
#messages li{padding : 5px 10px; }
#messages li : nth - child(odd){background : #eee; }
#messages {margin - bottom : 40px }
#messages {font - size : 2em }
#btn1 {position : fixed; top : 10px; left :  10px; font - size : 2em }
#btn2 {position : fixed; top : 10px; left :  70px; font - size : 2em }
#btn3 {position : fixed; top : 10px; left : 150px; font - size : 2em; width : 30px }
#btn4 {position : fixed; top : 10px; left : 190px; font - size : 2em; width : 30px }
#btn5 {position : fixed; top : 10px; left : 230px; font - size : 2em; width : 30px }
#btn6 {position : fixed; top : 10px; left : 270px; font - size : 2em; width : 30px }
#frequency {position : fixed; top :  10px; left : 310px; font - size : 2em }
#mycanvas  {position : fixed; top :  60px; left :  10px; font - size : 2em }
#mycanvas2 {position : fixed; top : 600px; left :  10px; font - size : 2em }
</style>

<script>
  window.addEventListener("load", init);
  function init(){}
</script>
</head>

 <body>
    <script src="https://code.jquery.com/jquery-1.11.1.js"></script>
    <script src='/socket.io/socket.io.js'></script>
    <script src="https://code.createjs.com/createjs-2015.11.26.min.js"></script>

    <button id="btn1"><font size="5">CQ</font></button>
    <button id="btn2"><font size="5">QRZ?</font></button>
    <button id="btn3"><font size="5">++</ font></button>
    <button id="btn4"><font size="5">+</font></button>
    <button id="btn5"><font size="5">-</font></button>
    <button id="btn6"><font size="5">--</font></button>

    <div id="frequency"></div>
    <div id="messages"></div>
    <form action="">
      <input id="m" autocomplete="off"/><button>Send</button>
    </form>
    <canvas id="mycanvas"  width="512" height="512" style="background:white;"></canvas>
    <canvas id="mycanvas2" width="512" height= "90" style="background:white;"></canvas>

<script>
var myarray = new Array();
var socket  = io();
var canvas;
var ctx;
var imgData;
var rgb = new Array(3);

var stage = new createjs.Stage("mycanvas2");
var t     = new createjs.Text("IC-7410", "26px serif", "DarkRed");
t.x       = 10;
t.y       = 10;
stage.addChild(t);
stage.update();

function colormap(charcode) {         // 0~9 or 0x30~0x39
  var tmp = (charcode - 0x30) * 0.1;  // 0.0~0.9
  var val;
  var r, g, b;
  if (tmp < 0.50) {
    r = 0.0;
  } else if (tmp > 0.75) {
    r = 1.0;
  } else {
    r = 4.0 * tmp - 2.0;
  }

  if (tmp < 0.25) {
    g = 4.0 * tmp;
  } else if (tmp > 0.75) {
    g = -4.0 * tmp + 4.0;
  } else {
    g = 1.0;
  }

  if (tmp < 0.25) {
    b = 1.0;
  } else if (tmp > 0.50) {
    b = 0.0;
  } else {
    b = -4.0 * tmp + 2.0;
  }

  rgb[1] = 255.0 * g;
  rgb[0] = 255.0 * r;
  rgb[2] = 255.0 * b;
}

function waterFall(myarray) {
  ctx.putImageData(imgData, 0, 1);
  imgData = ctx.getImageData(0, 0, 512, 512);
  for (j = 0; j < 512; j++) {
    colormap(myarray[j]);
    imgData.data[0 + j * 4] = rgb[0];
    imgData.data[1 + j * 4] = rgb[1];
    imgData.data[2 + j * 4] = rgb[2];
    imgData.data[3 + j * 4] = 255;
  }
}

canvas = document.getElementById('mycanvas');
ctx    = canvas.getContext('2d');

var mx, my;
function onClick(e) {
  var rect = e.target.getBoundingClientRect();
  mx       = e.clientX - rect.left;
  my       = e.clientY - rect.top;
  console.log('mouse clicked: ', mx, my);
}
canvas.addEventListener('click', onClick, false);

imgData = ctx.createImageData(512, 512);
for (i = 0; i < 512; i++) {
  for (j = 0; j < 512; j++) {
    imgData.data[0 + j * 4 + i * imgData.width * 4] = j % 256;
    imgData.data[1 + j * 4 + i * imgData.width * 4] = i % 256;
    imgData.data[2 + j * 4 + i * imgData.width * 4] = 128;
    imgData.data[3 + j * 4 + i * imgData.width * 4] = 255;
  }
}
ctx.putImageData(imgData, 0, 0);

var socket = io();

$('#btn1').click(function() { socket.emit('message1'); });
$('#btn2').click(function() { socket.emit('message2'); });
$('#btn3').click(function() { socket.emit('message3'); });
$('#btn4').click(function() { socket.emit('message4'); });
$('#btn5').click(function() { socket.emit('message5'); });
$('#btn6').click(function() { socket.emit('message6'); });

$('form').submit(function() {
  socket.emit('your message', $('#m').val());
  $('#m').val('');
  return false;
});

socket.on('your message', function(msg) {
  $('#messages').append($('<div>').text(msg));
  window.scrollTo(0, document.body.scrollHeight);
});

socket.on('waterfall', function(data) { waterFall(data); });

socket.on('freqmsg', function(msg) {
  t.text = msg;
  stage.update();
  document.getElementById("frequency").innerHTML = msg;
});

</script>

</body>
</html>

IC-7410 Rig Control with http (6)

This is a real waterfall.

By the way, if you get an error message, undefined reference to ‘alloca’, with your gcc, the following line may help.

#include <alloca.h>

If you are using “stdout” instead of “stderr”, you may need to specify the mode for file buffering.

% sprig_audio_only /dev/ttyUSB0 hw:2,0 |& node index.js
% sprig_audio_only /dev/ttyUSB0 hw:2,0 |  node index.js

sprig_audio_only.c

#include <stdio.h>
int main () {
 setvbuf(stdout, NULL, _IOLBF, 0); // line buffering mode
//
 for(int i=0;i<512<i++) {
  fprintf(stdout, "%1d", audio_signal_ffted[i]);
 }
 fprintf(stdout,"\n");
 fflush(stdout); // this will also do with full buffering mode
//
}

IC-7410 Rig Control with http (5)

If we put the different parts together, you will have something like this. Note that the waterfall data is a computer generated one.

index.js

// file name = index20.js, reads index20.html

const bufa = new Buffer('fefe80e017'          , 'hex'); // preamble
const bufz = new Buffer('fd'                  , 'hex'); // postamble
const buf1 = new Buffer('fefe80e0174351fd'    , 'hex'); // CQ
const buf2 = new Buffer('fefe80e01751525a3ffd', 'hex'); // QRZ?
const buf3 = new Buffer('fefe80e003fd'        , 'hex'); // read freq

var SerialPort = require('serialport');
var serial = new SerialPort('/dev/ttyUSB0',{
    baudrate: 19200,
    parser: SerialPort.parsers.byteDelimiter([0xfd])
});

var http  = require('http');
var fs    = require('fs');
var index = fs.readFileSync(__dirname + '/index20.html');
var app = http.createServer(function(req, res) {
                res.writeHead(200, {'Content-Type' : 'text/html'});
                res.end(index);
              })
              .listen(3000);

var io = require('socket.io').listen(app);

var ndata   = 512;  // data[512] + 0x0a
var pos     = 0;
var myarray = new Array();

// -- water fall --

process.stdin.on('readable', function() {
  var buf = process.stdin.read();
  if (buf !== null) {
    for (var i = 0; i < buf.length; i++) {
      if (buf[i] == 0x0a) {
        pos = 0;
        io.emit('waterfall', myarray);
      } else {
        myarray[pos++] = buf[i];
      }
    }
  }
});

// -- serial for IC-7410 --

serial.on('open',function(){
    console.log('serial port /dev/ttyUSB0 is opened.');
});

serial.on('data',function(data){
    console.log('received data: ', data.length, data);
    if ( !(data[0] == 0xfe & data[1] == 0xfe) ) {
      console.log('** received serial data error **');
    }
    if (data[2] == 0xe0 & data[3] == 0x80 & data[4] == 0x03) {
        var f10   = data[5]>>4 & 0x0f;
        var f1    = data[5]    & 0x0f;
        var f1k   = data[6]>>4 & 0x0f;
        var f100  = data[6]    & 0x0f;
        var f100k = data[7]>>4 & 0x0f;
        var f10k  = data[7]    & 0x0f;
        var f10m  = data[8]>>4 & 0x0f;
        var f1m   = data[8]    & 0x0f;
        var freq  =  f10m.toString()+ f1m.toString()+","
                   +f100k.toString()+f10k.toString()+f1k.toString()+"."
                   + f100.toString()+ f10.toString()+f1+" kHz";
        console.log('freq: ' + freq);
        io.emit('freqmsg', 'VFO A: ' + freq);
    }

});

serial.on('error', function(err) {
  console.log('Error: ', err.message);
})

// -- socket --

io.on('connection', function(socket){

    socket.on('message1', function(){
    serial.write(buf1);
  });

    socket.on('message2', function(){
    serial.write(buf2);
  });

  socket.on('your message', function(msg){
    serial.write(bufa);
    serial.write(msg);
    serial.write(bufz);
    io.emit('your message', msg);
  });

});

// -- request freq --

function sendTime() {
    serial.write(buf3);
}

setInterval(sendTime, 100);

// -- EOF --

IC-7410 Rig Control with http (4)

Changed the color mapping and some parts of the code.

index.js

var http  = require('http');
var fs    = require('fs');
var index = fs.readFileSync(__dirname + '/index.html');

var app = http.createServer(function(req, res) {
                res.writeHead(200, {'Content-Type' : 'text/html'});
                res.end(index);
              })
              .listen(3000);

var io = require('socket.io').listen(app);

var ndata   = 512;  // data[512] + 0x0a
var pos     = 0;
var myarray = new Array();

process.stdin.on('readable', function() {
  var buf = process.stdin.read();
  if (buf !== null) {
    for (var i = 0; i < buf.length; i++) {
      if (buf[i] == 0x0a) {
        pos = 0;
        io.emit('waterfall', myarray);
      } else {
        myarray[pos++] = buf[i];
      }
    }
  }
});

process.stdin.on('end', function() {
});

index.html

<!doctype html>
<html>
<head>
<script src='/socket.io/socket.io.js'></script>
<script src="https://code.createjs.com/createjs-2015.11.26.min.js"></script>

<script>
var myarray = new Array();
var socket  = io();
var canvas;
var ctx;
var imgData;

window.addEventListener("load", init);

function init() {
  canvas = document.getElementById('myCanvas');
  ctx    = canvas.getContext('2d');

  imgData = ctx.createImageData(512, 512);
  for (i = 0; i < 512; i++) {
    for (j = 0; j < 512; j++) {
      imgData.data[0 + j * 4 + i * imgData.width * 4] = j % 256;
      imgData.data[1 + j * 4 + i * imgData.width * 4] = i % 256;
      imgData.data[2 + j * 4 + i * imgData.width * 4] = 128;
      imgData.data[3 + j * 4 + i * imgData.width * 4] = 255;
    }
  }
  ctx.putImageData(imgData, 0, 0);
}

function waterFall(myarray) {
  ctx.putImageData(imgData, 0, 1);
  imgData = ctx.getImageData(0, 0, 512, 512);
  for (j = 0; j < 512; j++) {
    var tmp                 = ((myarray[j] - 48) * 25 % 256) / 256.0;
    imgData.data[0 + j * 4] = colormap_r(tmp);
    imgData.data[1 + j * 4] = colormap_g(tmp);
    imgData.data[2 + j * 4] = colormap_b(tmp);
    imgData.data[3 + j * 4] = 255;
  }
}

socket.on('waterfall', function(data) { waterFall(data); });

function colormap_r(tmp) {
  var val;
  if (tmp < 0.50) {
    val = 0.0;
  } else if (tmp > 0.75) {
    val = 1.0;
  } else {
    val = 4.0 * tmp - 2.0;
  }
  return 255.0 * val;
}

function colormap_g(tmp) {
  var val;
  if (tmp < 0.25) {
    val = 4.0 * tmp;
  } else if (tmp > 0.75) {
    val = -4.0 * tmp + 4.0;
  } else {
    val = 1.0;
  }
  return 255.0 * val;
}

function colormap_b(tmp) {
  var val;
  if (tmp < 0.25) {
    val = 1.0;
  } else if (tmp > 0.50) {
    val = 0.0;
  } else {
    val = -4.0 * tmp + 2.0;
  }
  return 255.0 * val;
}

</script>
</head>

<body>
    <canvas id="myCanvas" width="512" height="512" style="background:white;"></canvas>
</body>
</html>

IC-7410 Rig Control with http (3)

The window on the right is with sprig, a C program running on a remote host to which IC-7410 is attached via an USB I/F.

The window on the left is a browser showing the same waterfall image.

You see four different browsers on a local host. They are Chrome, Firefox, Opera, and Safari. Can you tell which is which?

Remote host:

% sprig /dev/ttyUSB0 HW:2,0 |& node index.js

sprig.c

  for(int j=0;j<WATERFALL_XSIZE;j++) {
       double tmp  =  audio_signal_ffted[j];
       int    iii  = 10.0*tmp; fprintf(stderr, "%1d", iii);
       *p++ = *q++ = colormap_r(tmp);
       *p++ = *q++ = colormap_g(tmp);
       *p++ = *q++ = colormap_b(tmp);
  }

index.js

var http  = require('http');
var fs    = require('fs');
var index = fs.readFileSync(__dirname + '/index.html');
var count = 0;

var app = http.createServer(function(req, res) {
  res.writeHead(200, {'Content-Type' : 'text/html'});
  res.end(index);
});

var io = require('socket.io').listen(app);
app.listen(3000);

var ndata = 512;
var pos   = 0;
var myarray = new Array(ndata);

process.stdin.on('readable', function() {
  var buf = process.stdin.read();
  for (var i = 0; i < buf.length; i++) {
    myarray[pos++] = buf[i];
    if(pos == ndata) {
      pos = 0;
      console.log(myarray[0], myarray[1], myarray[2]);
      io.emit('time', {buffer : myarray});
    }
  }
});

index.html

<!doctype html>
<html>
<head>
<script src='/socket.io/socket.io.js'></script>
<script src="https://code.createjs.com/createjs-2015.11.26.min.js"></script>
<script>
var myarray = Array();
var socket  = io();
var canvas;
var ctx;
var imgData;
function init() {
  canvas = document.getElementById('myCanvas');
  ctx    = canvas.getContext('2d');

    imgData = ctx.createImageData(512, 512);
    for (i = 0; i < 512; i++) {
      for (j = 0; j < 512; j++) {
        imgData.data[0 + j * 4 + i * imgData.width * 4] = j % 256;
        imgData.data[1 + j * 4 + i * imgData.width * 4] = i % 256;
        imgData.data[2 + j * 4 + i * imgData.width * 4] = 0;
        imgData.data[3 + j * 4 + i * imgData.width * 4] = 255;
      }
    }
    ctx.putImageData(imgData, 0, 0);
}

function emitParticles(myarray) {
  ctx.putImageData(imgData, 0, 1);
  imgData = ctx.getImageData(0, 0, 512, 512);
  for (j = 0; j < 512; j++) {
    var index = (myarray[j] - 48) * 32 % 256;
    imgData.data[0 + j * 4] = index;
    imgData.data[1 + j * 4] = 256 - index;
    imgData.data[2 + j * 4] = 0;
    imgData.data[3 + j * 4] = 255;
  }
}

socket.on('time', function(data) {
  myarray = data.buffer;
  emitParticles(myarray);
console.log(count);
});
</script>
</head>

<body>
    <canvas id="myCanvas" width="512" height="512" style="background:white;"></canvas>
    <p id='messages'></p>
</body>
</html>