Tutorial : using SDRNode scripting to monitor propagation and tell users what is going on

Abstract

SDRNode can do IQ streaming but has also scripting capabilities to run in ‘standalone’ mode and do some Digital Signal Processing in the background. In this tutorial we will explore these capabilities and show how to add a script that scans HF bands to monitor activity and relay the information to users.

In this first post we will explore the most basic way to do this by adding a specific Gauge (see screenshot) that is automatically updated by each measurement done remotely on the server.

Cloud-SDR remote propagation

Other tutorials explore logging these values to files or publishing to a remote server for Web publishing.

Tutorial setup

We will use here two different receiving hardware :

  1. For normal receiving to remote users, an AirSpy receiver. This SDR hardware will be set to stream a 100 KHz wide compressed stream to remote listeners,
  2. For band monitoring we will use a RTLSDR from www.rtl-sdr.com.

both receivers will receive their signals from a SV1AFN . With this setup the RTLSDR dongle will be used periodically, only when a measurement as to be performed.

Note that such monitoring could be used with only one single hardware, but by default SDRNode postpones acquisition tasks if a user is listening to a stream that is claimed by a background script… We could also use two RTLSDR (for example like in this tutorial) or use the AirSpy for monitoring.

This setup is shown in the following figure:

Cloud-SDR_monitoring

Step 1 : configuring SDRNode streaming

We have to :

  1. Configure the RTLSDR and the AirSpy to declare the up-converter,
  2. Configure the streams.

We update the conf_AirSpy_<serial_number>.conf file located in the conf subfolder of the SDRnode install :

[Preferences]
serial_number=xxxxxxx
LO=200000000
In_Min=1000000
In_Max=50000000

And we do the same for the RTLSDR in the conf_RTL820T_<serial_number>.conf file :

[Preferences]
serial_number=2
LO=200000000
In_Min=1000000
In_Max=50000000

Now, when a frequency request of – for example – 14.010 MHz comes to the device, the SDR will be tuned to 14.010 + 200 MHz = 214.010 MHz automatically, we do not have to bother anymore.

We can check in the log after restarting SDRNode service that the configuration as been correctly applied:

[14-09-2016 21:45:19.512] Found 1 RTLSDR devices connected
[14-09-2016 21:45:19.512] Attaching device #0
[14-09-2016 21:45:19.528] RTLSDR::RTLSDR() device ID:0
[14-09-2016 21:45:20.564] RTLSDR::RTLSDR Found manufacturer:Realtek,  product:RTL2838UHIDIR,  serial:2
[14-09-2016 21:45:20.580] Converter installed with LO :200.000MHz, freq range changed to [1.000MHz - 50.000MHz]
...
[14-09-2016 21:45:20.830] RadioHardwareProvider::RadioHardwareProvider looking for AirSpy
[14-09-2016 21:45:21.455] RadioHardwareProvider::RadioHardwareProvider 1 AirSpy connected
[14-09-2016 21:45:21.455] AirSpyBoard::AirSpyBoard - Driver embedding AirSpy API V1.0.8 -(c) AirSpy
[14-09-2016 21:45:21.471] AirSpyBoard::AirSpyBoard airspy_get_samplerates=2
[14-09-2016 21:45:21.471] AirSpyBoard::AirSpyBoard sample_rate[0]=10000000
[14-09-2016 21:45:21.471] AirSpyBoard::AirSpyBoard sample_rate[1]=2500000
[14-09-2016 21:45:21.486] AirSpyBoard::AirSpyBoard Preferred sampling rate: 10000000
[14-09-2016 21:45:21.486] Converter installed with LO :200.000MHz, freq range changed to [1.000MHz - 50.000MHz]

We now create a specific boot script sdrnode.js (located in script folder) as follows:

// load the configuration utilities
load('scripts/sources.js');
 
/*----------------------------------------------------------------*/
/* Boot function to set the hardware                            --*/
/*----------------------------------------------------------------*/
function simpleboot() {
	// get a detailed description of what hardware is here
	var sources = GetSources();
	var i ;
	// loop through the sources
	for( i=0 ; i < sources.length ; i++ ) {
		var sourceDef = sources[i] ;	
		if( sourceDef.name == 'AirSpy') {
			// do specific for AirSpy
			// Connect to the AirSpy
			var broadcast_sdr = new RFSource( sourceDef.UID );
			
			// declare and share a stream
			var stream   = broadcast_sdr.createStream( 100e3, 'HF_Shared' ) ;
			stream.setCompressionType( 3 ) ; // max compression
			stream.setCloneable(true); // this allows each user to have its own life within the received band
		}
		
		if( sourceDef.name == 'RTL820T') {			
			var capture_sdr = new RFSource( sourceDef.UID );
			// do specific stuff for RTLSDR
			capture_sdr.setSampleRate(1e6) ;
		} 
	}
}
/*----------------------------------------------------------------*/
/* Entry point (main)                                            */
/*----------------------------------------------------------------*/
print('booting sdrNode');
// Set this node name
SDRNode.setName('MyRemoteSDR');

// do the configuration
simpleboot();

Remember that ‘fun’ starts at line 40 by calling the function ‘simpleboot()‘ that iterates over the different detected devices. At line 14 we create a shared stream of 100 KHz for the AirSpy, while at line 25 we just configure the RTLSDR sampling rate.

We can check everything goes right by restarting SDRNode and trying a client connection. We should see the following:

Cloud-SDR capture stream

Of course only one stream is available from our server : we did not publish anything from the RTLSDR…

Good ! now lets configure the acquisition mode.

Step 2 : Configuring the acquisition

We will use the Capture module that enables automatic acquisition. We create a new script file where we define our configuration function :

// the capture definition function

function configureTutorialCaptures( device ) {
	var cap = new Capture();	
	// define capture parameters
	cap.setCenterFrequency( 14.020 ) ;
	cap.setBandwidth( 20e3 ) ; // 20 Khz -> [14.010 to 14.030]
	cap.setSampleCountRequired( 20e3 ); // 20k samples at 20 Khz -> 1 seconds 
	cap.setScriptCallBack( 'oncapture_done.js' );
	
	// check that parameters are okay 
	if( cap.isCaptureReady() ) {
		// attach capture to device
		if( device.scheduleCapture( cap ) ) {
			 print('OK capture defined.');
			 // ask for capture engine start in 20 seconds
			 CaptureEngine.proceedCaptures(20e3);  // 20 000 millisecs
		}
	}
}

This function does the following:

  1. Create the capture object,
  2. Defines the capture at 14.020 MHz with a band of 20 KHz,
  3. Say we want 20 000 samples (1 second of signal),
  4. Call the the script oncapture_done.js when the capture is finished (line 9),
  5. Attach the capture to the device passed as a parameter of the function (line 14),
  6. Ask the capture to start in 20 000 milliseconds.

This new script is saved as configure_capture.js . We now have to call from our boot script as follows (note that only the changed sections are shown here, see highlighted sections):

// load the configuration utilities
load('scripts/sources.js');
load('scripts/configure_capture.js'); 

/*----------------------------------------------------------------*/
/* Boot function to set the hardware                            --*/
/*----------------------------------------------------------------*/
function simpleboot() {
...
        if( sourceDef.name == 'RTL820T') {			
			var capture_sdr = new RFSource( sourceDef.UID );
			// do specific stuff for RTLSDR
			capture_sdr.setSampleRate(1e6) ;
			// call our function and bind device
			configureTutorialCaptures( capture_sdr ) ;
        } 
...
}
...

Step 3 : Doing the math

We want to have a number saying if there is someone on the band or not… Many ways to do this, but the simplest is just to compute the RMS power of the 20 000 samples we have collected. Note that we could have done an FFT or many other things, but we will use the provided getRMS() function to do it easily.

We also have to add the oncapture_done.js file, called when the capture is finished and samples available:

// this function is called automatically when the capture is done
// parameter <id> is the internal unique Id of the capture that
// was just performed
function dataReady(id ) {
	var frequency = CaptureEngine.getCenterFrequency(id);		
	var power = CaptureEngine.getRMS( id );
	var BW = CaptureEngine.getCaptureBandwidth( id );
	
	print('RMS power at F= ' + frequency + ' MHz , BW='+BW+' p is =' + power );
	
	// reschedule another capture 
	CaptureEngine.proceedCaptures( 15000 );
}

Okay… what’s going on here :

  1. We retrieve the parameters of the capture into the variables called frequency, power using the RMS, BW;
  2. We print them to the log file;
  3. We reschedule next capture to append in 15 seconds (15000 millisecs.).

Nice but not that useful… Who will check the log file … except us for debug?

[15-09-2016 21:50:41.579] Loading script : C:/SDRNode/scripts/oncapture_done.js
[15-09-2016 21:50:41.583] Script console> RMS power at F= 14.02 MHz , BW=20000 p is =-68.1543197631836
[15-09-2016 21:50:58.759] Loading script : C:/SDRNode/scripts/oncapture_done.js
[15-09-2016 21:50:58.764] Script console> RMS power at F= 14.02 MHz , BW=20000 p is =-67.617919921875
...

We see that each time the script is loaded and executed. Works, but… we need a way to display this value at the client side… See next chapter of this tutorial.

Step 4 : Publishing the value as a gauge to the client application

A specific tab can be added on the client program to display indicators. An indicator can be anything coming from outside (antenna rotator, SWR measurement etc.). This value can also be sent from the server and displayed in this tab.

To enable this we have two things to do:

  1. Tell the client that it must display a gauge for an incoming value,
  2. Tell the server that the measurement as to be sent to update this gauge.

That’s quite straightforward.

First, open the client configuration file cloud-sdr.conf located in the conf folder, look for the Indicators sections, and change it as follows:

[Indicators]
count=1
I0_Name=20Meters

[20Meters]
min=-100
max=-40

We have declared a new indicator called ’20Meters’ and we have set the indicator to range from -100 to -40.

We now just have to update our  oncapture_done.js file, called when the capture is finished and samples available, and send the level :

// this function is called automatically when the capture is done
// parameter <id> is the internal unique Id of the capture that
// was just performed
function dataReady(id ) {
	var frequency = CaptureEngine.getCenterFrequency(id);		
	var power = CaptureEngine.getRMS( id );
	var BW = CaptureEngine.getCaptureBandwidth( id );
	
	//print('RMS power at F= ' + frequency + ' MHz , BW='+BW+' p is =' + power );
	// publish an indicator for this value
	CloudSDR.sendIndicatorValue( '20Meters', 'dBm', power );
	
	// reschedule another capture 
	CaptureEngine.proceedCaptures( 15000 );
}

Warning… names must match between script and client configuration !

Only one band ??? What’s next ?

Next Steps

Stay tuned !

next tutorial will show how to change the scanned band at each call.