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.
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 :
- 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,
- 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:
Step 1 : configuring SDRNode streaming
We have to :
- Configure the RTLSDR and the AirSpy to declare the up-converter,
- 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:
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:
- Create the capture object,
- Defines the capture at 14.020 MHz with a band of 20 KHz,
- Say we want 20 000 samples (1 second of signal),
- Call the the script oncapture_done.js when the capture is finished (line 9),
- Attach the capture to the device passed as a parameter of the function (line 14),
- 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 :
- We retrieve the parameters of the capture into the variables called frequency, power using the RMS, BW;
- We print them to the log file;
- 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:
- Tell the client that it must display a gauge for an incoming value,
- 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.