Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

SDR Scanner(aka Threaded wxPerl Example)

by jmlynesjr (Deacon)
on Oct 20, 2015 at 02:01 UTC ( [id://1145372]=CUFP: print w/replies, xml ) Need Help??

SDR Scanner(aka Threaded wxPerl Example)

Background: The July 2015 issue of Nuts and Volts magazine had an article on using an inexpensive DVB-T(Digital Video Broadcast-Terrestrial-EU Standard) USB dongle along with a 24MHz upconverter as the basis for an HF and higher Software Defined Receiver(SDR). The software used in the article was SDR# an MS-Windows application. As I am an Ubuntu user, I looked for a similar Linux application. I found gqrx. While waiting on parts to build the upconverter, I installed gqrx-sdr from the Ubuntu Software Center. It ran and pulled in various signals from 24MHz up, but was unstable. Turns out this was a pretty old version and after uninstalling and then installing via sudo add-apt-repository ppa:gqrx/snapshots, a current stable version was found. The output of gqrx can be routed through pulseaudio to other software(like fldigi) that can decode various digital modes.

gqrx has a Remote Control feature that can accept commands(a subset of the Amateur Radio Rigctrl protocol) over a Telnet connection and more digging turned up a perl script(gqrx-scan) that uses this interface, through Net::Telnet, to implement a scanner feature. gqrx-scan has many bells and whistles that I wouldn't ever use so I wrote a lite/crude version(lite.pl) that just scans from F1 to F2 repeatedly. For more flexibility(and practice) I decided to expand lite.pl into a wxPerl application(threadedgqrxLite.pl), mostly a bunch of text controls and sizers. A future enhancement might include one or more lists of frequencies of interest, i.e. local repeaters, satellites, etc.

For a hands-on introduction to SDR, take a look at websdr.com, a world-wide collection of SDR servers.

What resulted is also an example of a wxPerl GUI operating in parallel with Perl Threads. YMMV. TMTOWTDI.

#! /usr/bin/perl # Name: threadedgqrxLite.pl - Simple gqrx SDR Scanner - wxP +erl Version # Author: James M. Lynes, Jr. # Created: September 17, 2015 # Modified By: James M. Lynes, Jr. # Last Modified: October 19, 2015 # Environment: Ubuntu 14.04LTS / perl v5.18.2 / wxPerl 3.0.1 / +HP 15 Quad Core # Change Log: 9/17/2015 - Program Created # 9/19/2015 - Added Title Text, Connection Error Popup, Siz +ers and Event Handlers # - Scan and Listen Timers, Button Label Change as St +atus Indicator # 9/20/2015 - Additional Comments, add additional error che +cking/processing # 9/26/2015 - Restructure to a threaded implementation # 9/28/2015 - Stubbed threaded structure working, flesh out + thread code, # - modify event code # - Test thread commands and button interlocking flag +s # - Add error message timer/event # 9/29/2015 - Set error message timer to 1 sec # - Redesign threads, collapse to 1 Telnet Server thr +ead # - Object sharing not easily supported by Thread imp +lementation # 10/19/2015- Fixed scanning loop in Telnet Thread # - Scaled {pause} and {listen} to be in msecs # - Scaled frequency values to KHz fro +m Hz # - Added Modulation Mode setting # Description: "Simple" interface to gqrx to implement a Softwa +re Defined Radio(SDR) # scanner function using the remote control feature of gqrx # (a small subset of the amateur radio rigctrl protocol) # # gqrx is a software defined receiver powered by GNU-Radio + and QT # Developed by Alex Csete - gqrx.dk # The latest version is at sudo add-apt-repository ppa:gqrx +/snapshots # # gqrx uses inexpensive($12 USD) DVB-T USB Dongles to provi +de I and Q signals for DSP # DVB-T is the EU standard for Digital Video Broadcast # See Alex's website for a list of supported dongles # # This code is inspired by "Controlling gqrx from a Remote + Host" by Alex Csete # and gqrx-scan by Khaytsus - github.com/khaytsus/gqrx-sca +n # # Net::Telnet is not in the perl core and was installed fro +m cpan # sudo cpanm Net::Telnet # # Start gqrx and the gqrx remote control option before run +ning this perl code. # Also, check that the gqrx LNA and audio gains are set. # An error window will popup if gqrx is not running with Re +mote Control enabled, # or if the dongle is not plugged in, when a Telnet connect +ion request is made. # # Notes: To change parameters: Stop Scanning, Change Parameters +, Start Scanning. The # previous frequency range will complete scanning before th +e new range takes effect. # Modulation Mode change requires disconnect/reconnect. # package main; use strict; use warnings; use Net::Telnet; use Time::HiRes qw(sleep); use threads; use threads::shared; use Data::Dumper; # ----------------------------------- Thread setup must occur before W +x GUI setup --------------------------- # Define the Thread shared data area my %common : shared; $common{ip} = "127.0.0.1"; # Localhost $common{port} = "7356"; # Local Port as defined in gqrx $common{tnerror} = 0; # Status, Telnet Error $common{connect} = 0; # Command $common{connected} = 0; # Status $common{disconnect} = 0; # Command $common{scanstart} = 0; # Command $common{scanstarted} = 0; # Status $common{beginf} = 0; # Scan - Beginning Frequency $common{endf} = 0; # Scan - Ending Frequency $common{nextf} = 0; # Scan - Variable Frequency(loop co +unter) $common{step} = 0; # Scan - Frequency Step $common{squelch} = 0; # Scan - Minimum RSSI(Recieved Signal + Strength Indicator) $common{rssi} = 0; # Scan - Latest RSSI $common{rssiupdate} = 0; # Scan - RSSI Update Command $common{pause} = 0; # Scan - Time between scan cycles - + msec $common{listen} = 0; # Scan - Time to Listen to a strong si +gnal - msec $common{mode} = 0; # Scan - Demodulator Type $common{stopthreads} = 0; # Command # Create Threads and Detach my $thconnect = threads->create(\&TelnetServer); $thconnect->detach(); # Define Telnet Server Thread Processing sub TelnetServer { my $telnetsession; print "\nTelnet Server Thread Started\n"; print " Check that gqrx Remote Control is enabled\n and that L +NA and Audio gains are set.\n"; while(1) { if($common{stopthreads}) {print "\nTelnet Server Thread Termin +ated\n"; return}; if($common{connect}) { # Process Connec +t Command if(!$common{connected}) { print "Open Telnet Connection to gqrx\n"; $telnetsession = Net::Telnet->new(Timeout => 2, port => + $common{port}, Errmode => sub {$common{tnerro +r} = 1;}); $telnetsession->open($common{ip}); $telnetsession->print("M $common{mode}"); # Set +the demodulator type $telnetsession->waitfor(Match=> '/RPRT', Timeout=>5, Er +rmode=>"return"); $common{connected} = 1; $common{connect} = 0; } } if($common{disconnect}) { # Process Disconn +ect Command if($common{connected}) { print "Close Telnet Connection to gqrx\n"; $telnetsession->print("c"); $common{disconnect} = 0; $common{connected} = 0; } } if($common{scanstart}) { # Process Scan Com +mand $common{scanstarted} = 1; $telnetsession->print("F $common{nextf}"); # Up +date frequency $telnetsession->waitfor(Match => 'RPRT', Timeout => 5, + Errmode => "return"); $telnetsession->print("l"); # Get RSSI my ($prematch, $rssi) = $telnetsession->waitfor(Match +=> '/-{0,1}\d+\.\d/', Timeout => 5, Errmode => "retu +rn"); if(defined($rssi)) { if($rssi >= $common{squelch}) { # Found + a strong signal $common{rssi} = $rssi; $common{rssiupdate} = 1; Time::HiRes::sleep($common{listen}); # +Pause and listen awhile } } $common{nextf} = $common{nextf} + $common{step}; # +Loop for next frequency if($common{nextf} >= $common{endf}) {$common{nextf} = +$common{beginf}}; Time::HiRes::sleep($common{pause}); } threads->yield(); } } # ------------ Start up the Wx GUI Processing, must happen after the t +hreads are started ------------ my $app = App->new(); $app->MainLoop; package App; use strict; use warnings; use base 'Wx::App'; sub OnInit { my $frame = Frame->new(); $frame->Show(1); } package Frame; use strict; use warnings; use Wx qw(:everything); use base qw(Wx::Frame); sub new { my ($class, $parent) = @_; # Create top level frame my $self = $class->SUPER::new($parent, -1, "gqrx Lite Scanner", wx +DefaultPosition, wxDefaultSize); # Create Title Text $self->{titletext} = Wx::StaticText->new($self, -1, "Threaded gqrx + Lite Scanner", wxDefaultPosition, wxDefaultSize); # Create Modulation Radio Box - First entry is the default my $modulators = ["FM", "AM", "WFM_ST", "WFM", "LSB", "USB", "CW", + "CWL", "CWU"]; $self->{modbox} = Wx::RadioBox->new($self, -1, "Modulation", wxDef +aultPosition, wxDefaultSize, $modulators, 3, wxRA_SPECIFY_COLS); # Create Buttons $self->{startbutton} = Wx::Button->new($self, -1, "Start Scan +ning", wxDefaultPosition, wxDefaultSize); $self->{stopbutton} = Wx::Button->new($self, -1, "Stop Scann +ing", wxDefaultPosition, wxDefaultSize); $self->{connectbutton} = Wx::Button->new($self, -1, "Connect", +wxDefaultPosition, wxDefaultSize); $self->{disconnectbutton} = Wx::Button->new($self, -1, "Disconnect +", wxDefaultPosition, wxDefaultSize); $self->{quitbutton} = Wx::Button->new($self, -1, "Quit", wxD +efaultPosition, wxDefaultSize); # Create Data Entry Prompts and Boxes $self->{bflabel} = Wx::StaticText->new($self, -1, "Beginning Frequ +ency, KHz", wxDefaultPosition, wxDefaultSize); $self->{bftext} = Wx::TextCtrl->new($self, -1, "144000", wxDefault +Position, wxDefaultSize); $self->{eflabel} = Wx::StaticText->new($self, -1, "Ending Frequenc +y, KHz", wxDefaultPosition, wxDefaultSize); $self->{eftext} = Wx::TextCtrl->new($self, -1, "144100", wxDefault +Position, wxDefaultSize); $self->{fslabel} = Wx::StaticText->new($self, -1, "Frequency Step, + Hz", wxDefaultPosition, wxDefaultSize); $self->{fstext} = Wx::TextCtrl->new($self, -1, "1000", wxDefaultPo +sition, wxDefaultSize); $self->{sllabel} = Wx::StaticText->new($self, -1, "Squelch Level", + wxDefaultPosition, wxDefaultSize); $self->{sltext} = Wx::TextCtrl->new($self, -1, "-60.0", wxDefaultP +osition, wxDefaultSize); $self->{splabel} = Wx::StaticText->new($self, -1, "Scan Pause, ms" +, wxDefaultPosition, wxDefaultSize); $self->{sptext} = Wx::TextCtrl->new($self, -1, "20", wxDefaultPosi +tion, wxDefaultSize); $self->{lplabel} = Wx::StaticText->new($self, -1, "Listen Pause, m +s", wxDefaultPosition, wxDefaultSize); $self->{lptext} = Wx::TextCtrl->new($self, -1, "1000", wxDefaultPo +sition, wxDefaultSize); $self->{rssilabel} = Wx::StaticText->new($self, -1, "RSSI", wxDefa +ultPosition, wxDefaultSize); $self->{rssitext} = Wx::TextCtrl->new($self, -1, "0", wxDefaultPos +ition, wxDefaultSize); # Define Sizer Structure - My "Standard" Layout # Assumes: One Main Sizer(Horizontal) # One Header Sizer(Horizontal) # One Body Sizer(Horizontal) containing # Left Body Sizer(Vertical) # Right Body Sizer(Vertical) # Three Footer Sizers(horizontal) # # Create Sizers my $mainSizer = Wx::BoxSizer->new(wxVERTICAL); $self->SetSizer($mainSizer); my $headerSizer = Wx::BoxSizer->new(wxHORIZONTAL); my $bodySizer = Wx::BoxSizer->new(wxHORIZONTAL); my $leftbodySizer = Wx::BoxSizer->new(wxVERTICAL); my $rightbodySizer = Wx::BoxSizer->new(wxVERTICAL); my $footer1Sizer = Wx::BoxSizer->new(wxHORIZONTAL); my $footer2Sizer = Wx::BoxSizer->new(wxHORIZONTAL); my $footer3Sizer = Wx::BoxSizer->new(wxHORIZONTAL); # Layout Main Sizer $mainSizer->Add($headerSizer,0,0,0); $mainSizer->AddSpacer(20); $mainSizer->Add($bodySizer,0,0,0); $mainSizer->AddSpacer(30); $mainSizer->Add($footer1Sizer,0,0,0); $mainSizer->AddSpacer(10); $mainSizer->Add($footer2Sizer,0,0,0); $mainSizer->AddSpacer(10); $mainSizer->Add($footer3Sizer,0,0,0); # Layout Header Sizer $headerSizer->AddSpacer(150); $headerSizer->Add($self->{titletext},0,0,0); # Layout Body Sizer $bodySizer->Add($leftbodySizer,0,0,0); $bodySizer->AddSpacer(50); $bodySizer->Add($rightbodySizer,0,0,0); # Layout Right and Left Body Sizers $leftbodySizer->Add($self->{bflabel},0,0,0); $leftbodySizer->Add($self->{bftext},0,0,0); $leftbodySizer->Add($self->{eflabel},0,0,0); $leftbodySizer->Add($self->{eftext},0,0,0); $leftbodySizer->Add($self->{fslabel},0,0,0); $leftbodySizer->Add($self->{fstext},0,0,0); $leftbodySizer->Add($self->{sllabel},0,0,0); $leftbodySizer->Add($self->{sltext},0,0,0); $leftbodySizer->Add($self->{splabel},0,0,0); $leftbodySizer->Add($self->{sptext},0,0,0); $leftbodySizer->Add($self->{lplabel},0,0,0); $leftbodySizer->Add($self->{lptext},0,0,0); $rightbodySizer->Add($self->{modbox},0,0,0); $rightbodySizer->AddSpacer(10); $rightbodySizer->Add($self->{rssilabel},0,0,0); $rightbodySizer->AddSpacer(10); $rightbodySizer->Add($self->{rssitext},0,0,0); # Layout Footer Sizers $footer1Sizer->Add($self->{startbutton},0,0,0); $footer1Sizer->AddSpacer(10); $footer1Sizer->Add($self->{stopbutton},0,0,0); $footer2Sizer->Add($self->{connectbutton},0,0,0); $footer2Sizer->AddSpacer(10); $footer2Sizer->Add($self->{disconnectbutton},0,0,0); $footer3Sizer->Add($self->{quitbutton},0,0,0); # Define Messaging Timer to schedule checking flags and displaying err +ors from the threads $self->{msgtimer} = Wx::Timer->new($self); # Define Event Handlers Wx::Event::EVT_BUTTON($self, $self->{startbutton}, sub { my ($self, $event) = @_; if(!$common{connected}) { # Can't start s +caning if not connected Wx::MessageBox("Telnet is not connected\nCannot star +t scanning", "Telnet Connection Error", wxICON_ERROR, $self); } else { $common{beginf} = $self->{bftext}->GetValue*1000; + # Scale KHz to Hz $common{endf} = $self->{eftext}->GetValue*1000; + # Scale KHz to Hz $common{nextf} = $common{beginf}; $common{step} = $self->{fstext}->GetValue; $common{squelch} = $self->{sltext}->GetValue; $common{pause} = $self->{sptext}->GetValue/1000; + # Scale to msec $common{listen} = $self->{lptext}->GetValue/1000; + # Scale to msec $common{scanstart} = 1; $self->{startbutton}->SetLabel("Scanning"); # + Change button label to indicate status }}); Wx::Event::EVT_BUTTON($self, $self->{stopbutton}, sub { my ($self, $event) = @_; if(!$common{connected}) { # Can't stop sc +anning if not connected Wx::MessageBox("Telnet is not connected\nCannot stop + scanning", "Telnet Connection Error", wxICON_ERROR, $self); } else { $common{scanstart} = 0; $common{scanstarted} = 0; $self->{startbutton}->SetLabel("Start Scanning"); + # Restore button label }}); Wx::Event::EVT_BUTTON($self, $self->{connectbutton}, sub { my ($self, $event) = @_; if(!$common{connected}) { $common{mode} = $self->{modbox}->GetStringSelection; $common{connect} = 1; $self->{connectbutton}->SetLabel("Connected"); # +Change button label to indicate status } }); Wx::Event::EVT_BUTTON($self, $self->{disconnectbutton}, sub { my ($self, $event) = @_; if(!$common{connected}) { # Can't dis +connect if not connected Wx::MessageBox("Telnet is not connected\nCannot Disc +onnect", "Telnet Connection Error", wxICON_ERROR, $self);} else { if($common{connected}) { $common{disconnect} = 1; $self->{connectbutton}->SetLabel("Connect"); + # Restore button label } if($common{scanstarted}) { $common{scanstart} = 0; $common{scanstarted} = 0; $self->{startbutton}->SetLabel("Start Scannin +g"); # Restore button label } }}); Wx::Event::EVT_BUTTON($self, $self->{quitbutton}, sub { my ($self, $event) = @_; $common{stopthreads} = 1; $self->Close; }); Wx::Event::EVT_TEXT($self, $self->{bftext}, sub { my ($self, $event) = @_; $self->{beginf} = $self->{bftext}->GetValue; }); Wx::Event::EVT_TEXT($self, $self->{eftext}, sub { my ($self, $event) = @_; $self->{endf} = $self->{eftext}->GetValue; }); Wx::Event::EVT_TEXT($self, $self->{fstext}, sub { my ($self, $event) = @_; $self->{step} = $self->{fstext}->GetValue; }); Wx::Event::EVT_TEXT($self, $self->{sltext}, sub { my ($self, $event) = @_; $self->{squelch} = $self->{sltext}->GetValue; }); Wx::Event::EVT_TEXT($self, $self->{sptext}, sub { my ($self, $event) = @_; $self->{pause} = $self->{sptext}->GetValue; }); Wx::Event::EVT_TEXT($self, $self->{lptext}, sub { my ($self, $event) = @_; $self->{listen} = $self->{lptext}->GetValue; }); Wx::Event::EVT_TEXT($self, $self->{rssitext}, sub { my ($self, $event) = @_; $self->{rssi} = $self->{rssitext}->GetValue; }); Wx::Event::EVT_RADIOBOX($self, $self->{modbox}, sub { my ($self, $event) = @_; $self->{mode} = $self->{modbox}->GetStringSelection; }); Wx::Event::EVT_TIMER($self, $self->{msgtimer}, sub { # +Display Error messages if($common{tnerror}) { # Telnet Error Wx::MessageBox("Telnet Connection Failed", "gqrx Lite +Scanner Error", wxICON_ERROR, $self); $self->{connectbutton}->SetLabel("Connect"); # Rest +ore button label $common{tnerror} = 0; } if($common{rssiupdate}) { $self->{rssitext}->SetValue($common{rssi}) +; $common{rssiupdate} = 0; } }); # Start Error Message Timer $self->{msgtimer}->Start(1000); # 1 second +period # Assign mainSizer to the Frame and trigger layout $mainSizer->Fit($self); $mainSizer->Layout(); return $self; } 1;

James

There's never enough time to do it right, but always enough time to do it over...

Replies are listed 'Best First'.
Re: SDR Scanner(aka Threaded wxPerl Example)
by Anonymous Monk on Apr 06, 2017 at 18:34 UTC

      Thank you. It was a fun project and learning exercise.

      Gqrx is now available as a normal Ubuntu package.

      James

      There's never enough time to do it right, but always enough time to do it over...

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: CUFP [id://1145372]
Approved by Athanasius
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others exploiting the Monastery: (4)
As of 2024-04-24 21:56 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found